<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	>

<channel>
	<title>Блогът на Ладжър</title>
	<atom:link href="http://blog.ladger.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.ladger.com</link>
	<description>тук сме всички и се надяваме да се забавляваме заедно :)</description>
	<pubDate>Fri, 19 Sep 2008 14:55:30 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.6.2</generator>
	<language>en</language>
			<item>
		<title>IE Binary Behaviors - Част 1 или &#8220;това к&#8217;во е?&#8221;</title>
		<link>http://blog.ladger.com/2008/09/19/ie-binary-behaviors-%d1%87%d0%b0%d1%81%d1%82-1-%d0%b8%d0%bb%d0%b8-%d1%82%d0%be%d0%b2%d0%b0-%d0%ba%d0%b2%d0%be-%d0%b5/</link>
		<comments>http://blog.ladger.com/2008/09/19/ie-binary-behaviors-%d1%87%d0%b0%d1%81%d1%82-1-%d0%b8%d0%bb%d0%b8-%d1%82%d0%be%d0%b2%d0%b0-%d0%ba%d0%b2%d0%be-%d0%b5/#comments</comments>
		<pubDate>Fri, 19 Sep 2008 14:53:03 +0000</pubDate>
		<dc:creator>admin</dc:creator>
		
		<category><![CDATA[Elements]]></category>

		<category><![CDATA[Общи]]></category>

		<category><![CDATA[Binary Behaviors]]></category>

		<category><![CDATA[C#]]></category>

		<category><![CDATA[Internet Explorer]]></category>

		<guid isPermaLink="false">http://blog.ladger.com/?p=57</guid>
		<description><![CDATA[Владислав Косев,
Development Manager
Ще започна първо с това, какво е това и какво може да се прави с него.

Какво е това?
Казано накратко, това е технология, която позволява да правите неща, които иначе правите с JavaScript, само че повече и по-бързи.
Вариант 1
Можете да си направите клас, който да &#8220;закачите&#8221; към някакъв таг, да речем някакъв &#60;div&#62;, който [...]]]></description>
			<content:encoded><![CDATA[<p><em>Владислав Косев,<br />
Development Manager</em></p>
<p>Ще започна първо с това, какво е това и какво може да се прави с него.</p>
<p><span id="more-57"></span></p>
<h2>Какво е това?</h2>
<p>Казано накратко, това е технология, която позволява да правите неща, които иначе правите с JavaScript, само че повече и по-бързи.</p>
<h3>Вариант 1</h3>
<p>Можете да си направите клас, който да &#8220;закачите&#8221; към някакъв таг, да речем някакъв &lt;div&gt;, който имате, и да му добавите допълнителни свойства и методи. Освен това можете да рисувате върху него с Windows GDI и GDI+.</p>
<h3>Вариант 2</h3>
<p>Можете директно да си направите собствен таг, да речем &lt;MyBetterDiv/&gt;, който може всичките гореизборени неща, можете да го направите да участва в POST-а на форма, можете да направите супер комплексно съдържание, без това да се вижда в главния документ като елементи (ViewLink, обяснено е <a title="Introduction to Viewlink" href="http://msdn.microsoft.com/en-us/library/ms531428(VS.85).aspx" target="_blank">тук</a>), можете целия да го нарисувате (вместо да използвате HTML елементи за тази цел), можете да му бъзикате фокуса, layout-а, парсването, можете да изпълнявате значително повече команди, отколкото при JS изпълнението на WYSIWYG редактор и още тон други неща.</p>
<h2>Как работи?</h2>
<p>Въпросният клас е всъщност COM клиент, а браузъра е COM сървър. Браузъра комуникира с нашия клас чрез интерфейси. Всичките интерфейси, необходими за комуникацията, се намират в mshtml.dll. За да се използва това в .NET обаче, трябва да се направи PIA (Primary Interop Assembly) - това е assembly, което представлява посредник, между .NET и COM сървъра. Такъв има направен от Microsoft и идва обикновено с Visual Studio (или някое от SDK-тата). Можете и сами да си направите, само че имайте предвид, че файла е колосален - 7 мегабайтов DLL, който някъде четох, че му трябват 4G (или сходна умопомрачителна цифра) RAM, за да се компилира.</p>
<p>Та. Как точно браузъра открива нашия файл, че да го load-не, че да намери кой отговаря за тага, че да ги върже двамката да си приказват?</p>
<h3>IElementBehaviorFactory и IElementNamespaceFactory2</h3>
<p>Има един подход на програмиране, който се казва Factory. При него, не се конструира директно класът, който ти трябва, а ми има втори клас, който играе точно ролята на фабрика, и има обикновено някакъв метод от сорта на .CreateЕдиЩоСи. Та, в нашия случай, в HEAD на HTML-а се паркира един &lt;object&gt; таг, който има един атрибут classid, в който пише един GUID. Тези GUID-ове са общо взето ID-та, по които се индентифицират класове и интерфейси в Windows и се записват в регистрите - един кой си GUID е един кой си клас/интефейс и се намира в едй-кой си файл, който се намира еди-къде си.</p>
<p>Та, браузъра вижда тоз таг, взима GUID-а и почва да рови в регистрите. Там намира, че това е еди-кой си файл (което е нашето assembly) и търси вътре клас с даденото име (това го пише в регистрите). След като докопа класа, пробва дали не имплементира IElementBehaviorFactory интерфейса (който общо взето казва - ако намериш таг, който не знаеш какво да го правиш, питаш мене). Тук има и един финт с namespace, слага се един &lt;?import namespace=&#8221;my&#8221; implementation=&#8221;(id-то на object тага)&#8221;?&gt; таг, който казва, че за всички тагове, които започват с my, трябва да пита factory-то, чийто object таг има даденто ID.</p>
<p>Например:</p>
<blockquote>
<pre>&lt;object id="CustomControls" classid="CLSID:3CF8817B-58DF-4C4A-96BB-21C0A8D822D7" &gt;&lt;/object&gt;
&lt;?import namespace="Ladger" implementation="#CustomControls" /&gt;</pre>
</blockquote>
<p>Нашият клас пък, започва така:</p>
<blockquote>
<pre>[ComVisible(true), ClassInterface(ClassInterfaceType.AutoDispatch), Guid("3CF8817B-58DF-4C4A-96BB-21C0A8D822D7"), ProgId("Elements.IE.ElementFactory")]
public class ElementFactory : IObjectSafety, IElementBehaviorFactory, IElementNamespaceFactory2</pre>
</blockquote>
<p>Този клас прави 2 неща: добавя тагове в namespace-а (тага &lt;?import ?&gt;) и създава класове срещу тагове.</p>
<p>Тагове се добавят в метода: </p>
<p style="PADDING-LEFT: 30px"> </p>
<blockquote>
<pre>public void CreateWithImplementation(IElementNamespace pNamespace, string bstrImplementation) {
     pNamespace.AddTag("line", 0);
}</pre>
</blockquote>
<p>Така казваме, че тагът line е наш, т.е. ние отговаряме за него. След това, когато браузъра намери такъв таг, извиква друг метод:</p>
<blockquote>
<pre>[return: MarshalAs(UnmanagedType.Interface)]
IElementBehavior FindBehavior(string behav, string behavUrl, COM.IElementBehaviorSite behavSite) {
   if(behav == "line"){
      return new LineTagClass();
   }
}</pre>
</blockquote>
<p>С този метод браузъра общо взето казва: &#8220;Глей ся, тука намерих един таг, твърди се, че е твой, така че ми дай, ако обичаш, клас, който имплементира IElementBehavior, който да вържа за този таг&#8221;. Ние, съответно, връщаме нашия клас и оттам нататък браузъра и този клас си говорят, фабриката повече не я занимават с проблемите си.</p>
<p>Идеята зад цялото нещо е следната: браузъра няма идея как да намира и конструира класовете, който отговарят за всеки таг, затова има един клас, с който си говори на тези теми.</p>
<p>Има и един интерфейс, който трябва да се имплементира и той е IObjectSafety, който казва дали компонента е Safe For Scripting (има таква настройка в security зоните). Той има два метода:</p>
<p> </p>
<blockquote>
<pre>public int GetInterfaceSafetyOptions(ref Guid riid, out int pdwSupportedOptions, out int pdwEnabledOptions) {
    pdwSupportedOptions = 0x00000001 | 0x00000002; // INTERFACESAFE_FOR_UNTRUSTED_CALLER and _DATA (Nathan, p. 677).
    pdwEnabledOptions = 0x00000001 | 0x00000002;
    return 0; // S_OK.
}</pre>
<pre>public int SetInterfaceSafetyOptions(ref Guid riid, int dwOptionsSetMaks, int dwEnabledOptions) {
    return 0; // S_OK;
}</pre>
</blockquote>
<p style="PADDING-LEFT: 30px"> </p>
<p>Това са трите интерфейса, който тряебва да имплементира един клас, за да може оттам нататък само да си правите HTML елементи. Във FindBehavior например, си паркирате един switch и според тага, връщате съответния клас.</p>
<h3>Интерфейси и COM</h3>
<p>Интерфейсите са основния компонент на COM (Component Object Model) архитектурата. Те са основния начин даден обект да предостави функционалности на друг. Интерфейсите са езико-независими, т.е., могат да се дефинират на C++, на C#  или общо взето на каквото си искате. В нашия случай, те са дефинирани на C++ (в mshtml.dll). Ние само използваме дефинициите (те са преведени на C# в PIA-то, но е съвсем лесно човек да си ги преведе сам, само трябва да знае кой COM тип в какъв .NET-ски се обръща). Има разни грешки в PIA-то на Microsoft (сбъркани типове на аргументи или връщан тип), хората из интернет са публикували общо взето тяхни корекции и не е трудно човек да си намери дефиници на коригираните интерфейси и да си ги ползва тях.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.ladger.com/2008/09/19/ie-binary-behaviors-%d1%87%d0%b0%d1%81%d1%82-1-%d0%b8%d0%bb%d0%b8-%d1%82%d0%be%d0%b2%d0%b0-%d0%ba%d0%b2%d0%be-%d0%b5/feed/</wfw:commentRss>
		</item>
		<item>
		<title>IE Binary Behаviors - Част 0, или &#8220;тоя JS само толкова ли може?&#8221;</title>
		<link>http://blog.ladger.com/2008/09/19/ie-binary-behviors-%d0%b8%d0%bb%d0%b8-%d1%82%d0%be%d1%8f-js-%d1%81%d0%b0%d0%bc%d0%be-%d1%82%d0%be%d0%bb%d0%ba%d0%be%d0%b2%d0%b0-%d0%bb%d0%b8-%d0%bc%d0%be%d0%b6%d0%b5-%d1%87%d0%b0%d1%81%d1%82-0/</link>
		<comments>http://blog.ladger.com/2008/09/19/ie-binary-behviors-%d0%b8%d0%bb%d0%b8-%d1%82%d0%be%d1%8f-js-%d1%81%d0%b0%d0%bc%d0%be-%d1%82%d0%be%d0%bb%d0%ba%d0%be%d0%b2%d0%b0-%d0%bb%d0%b8-%d0%bc%d0%be%d0%b6%d0%b5-%d1%87%d0%b0%d1%81%d1%82-0/#comments</comments>
		<pubDate>Fri, 19 Sep 2008 13:05:14 +0000</pubDate>
		<dc:creator>admin</dc:creator>
		
		<category><![CDATA[Elements]]></category>

		<category><![CDATA[Общи]]></category>

		<category><![CDATA[Binary Behaviors]]></category>

		<category><![CDATA[C#]]></category>

		<category><![CDATA[Internet Explorer]]></category>

		<guid isPermaLink="false">http://blog.ladger.com/?p=47</guid>
		<description><![CDATA[Владислав Косев,
Development Manager
Binary Behaviors технологията е много използвана в платформата ни - Elements. С нейна помощ получаваме много функционалности, разнообразен и гъвкав интерфейс и куп други неща на сравнително (с JavaScript, Flash или Silverlight, например) ниска цена като загуба на производителност.
Това е първият от серия блогове, които на тема Binary Behaviors в Internet Explorer. Целта [...]]]></description>
			<content:encoded><![CDATA[<p><em>Владислав Косев,<br />
Development Manager</em></p>
<p>Binary Behaviors технологията е много използвана в платформата ни - Elements. С нейна помощ получаваме много функционалности, разнообразен и гъвкав интерфейс и куп други неща на сравнително (с JavaScript, Flash или Silverlight, например) ниска цена като загуба на производителност.</p>
<p>Това е първият от серия блогове, които на тема Binary Behaviors в Internet Explorer. Целта на тези публикации е както да споделим опита си като фирма, така и да спестя на хора, които се интересуват от това и искат да го пробват с .NET четене и ръчкане в така или иначе малкото информация в интеренет по темата.</p>
<p><span id="more-47"></span></p>
<p>Когато ми е трябвала документация за JavaScript, обикновено съм ползвал MSDN Library. А там, както е известно, има още какво ли не. И често засичах в темите за Internet Explroer разни препратки към две технологии - HTC и Binary Behaviors и се зачитах. Но общо взето остана само като информация - да знам, че има нещо такова и къде да чета, ако ми потрявба.</p>
<p>Всъщност в началото това, което ми привлече интереса, беше функционалността за Print Preview, която се можеше да се достъпи и видях наистина интригуващи неща, които можеше да се правят. Само дето трябваше да прозореца да се отвори от dll файл.</p>
<p>И така, един ден ми омръзна само да чета и реших да пробвам да видя какво може да се направи. Да, ама всичките примери в MSDN бяха (и още са) на C++, а пък аз там не съм кой знае колко силен. След доста търсене намерих един-единствен пост в интернет, на един (чудесен, чудесен) човек за това как да си направим binary behavior с .NET и съответно веднага го свалих и пробвах. Оттам нататък (както казват по книгите, филмите, вестниците, и всякакви други писмени и видео издания), се занимава почти през цялото си свободно време с това (че и достатъчно от работното такова).</p>
<p>Аз лично предпочитам измежду .NET езиците C#. И това, което всъщност разбрах е, че са много малко нещата, които могат да се правят на C++, но не могат на C#. И стига човек да мисли малко, може да &#8221;преведе&#8221; успено пример на C++ на такъв на C#.</p>
<p>В тези блогове, ще се опитам да представя знанията си по темата по начин, по който ако ги бях намерил на времето някъде, сигурно щях да си спестя доста главоблъскане. Но пък и доста откриваници на топлата вода, от които не е сигурно, че искам да се откажа.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.ladger.com/2008/09/19/ie-binary-behviors-%d0%b8%d0%bb%d0%b8-%d1%82%d0%be%d1%8f-js-%d1%81%d0%b0%d0%bc%d0%be-%d1%82%d0%be%d0%bb%d0%ba%d0%be%d0%b2%d0%b0-%d0%bb%d0%b8-%d0%bc%d0%be%d0%b6%d0%b5-%d1%87%d0%b0%d1%81%d1%82-0/feed/</wfw:commentRss>
		</item>
		<item>
		<title>За разликите межу Elements и платформи като .NET (и други)</title>
		<link>http://blog.ladger.com/2008/09/19/%d0%b7%d0%b0-%d1%80%d0%b0%d0%b7%d0%bb%d0%b8%d0%ba%d0%b8%d1%82%d0%b5-%d0%bc%d0%b5%d0%b6%d1%83-elements-%d0%b8-%d0%bf%d0%bb%d0%b0%d1%82%d1%84%d0%be%d1%80%d0%bc%d0%b8-%d0%ba%d0%b0%d1%82%d0%be-net/</link>
		<comments>http://blog.ladger.com/2008/09/19/%d0%b7%d0%b0-%d1%80%d0%b0%d0%b7%d0%bb%d0%b8%d0%ba%d0%b8%d1%82%d0%b5-%d0%bc%d0%b5%d0%b6%d1%83-elements-%d0%b8-%d0%bf%d0%bb%d0%b0%d1%82%d1%84%d0%be%d1%80%d0%bc%d0%b8-%d0%ba%d0%b0%d1%82%d0%be-net/#comments</comments>
		<pubDate>Fri, 19 Sep 2008 12:48:20 +0000</pubDate>
		<dc:creator>admin</dc:creator>
		
		<category><![CDATA[Elements]]></category>

		<category><![CDATA[Общи]]></category>

		<category><![CDATA[JavaScript]]></category>

		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.ladger.com/?p=41</guid>
		<description><![CDATA[Владислав Косев,
Development Manager
Скоро имах разговор с човек, който се интересуваше от нашата платформа Елементс и беше чел описанието, публикувано тук. Тъй като въпросите бяха много интересни и явно имаше какво се дообяснява, се възползвам от възможността да публикувам част от обясненията тук.

Елементс е силно специализирана платформа, т.е. по това се различава от другите платформи - не [...]]]></description>
			<content:encoded><![CDATA[<p><em>Владислав Косев,<br />
Development Manager</em></p>
<p>Скоро имах разговор с човек, който се интересуваше от нашата платформа Елементс и беше чел описанието, публикувано <a href="http://elements.bg" target="_blank">тук</a>. Тъй като въпросите бяха много интересни и явно имаше какво се дообяснява, се възползвам от възможността да публикувам част от обясненията тук.</p>
<p><span id="more-41"></span></p>
<hr />Елементс е силно специализирана платформа, т.е. по това се различава от другите платформи - не можеш да си направиш някакви PHP скриптове и просто за забодеш платформата отдолу. За Java не съм много компетентен да говоря, защото не я познавам, но ADO-то е само data access, Elements e цялостно от data access-а, през интерфейса, та бизнес логка - всичко, все едно комбинацията .NET - Windows Forms - всички такива приложения изглеждат еднакво (игнорирам факта, че можеш да си пишеш твои контроли и да къстъмизираш тук-таме).</p>
<p>В Елементс няма нищо „общоупотребявно&#8221;, всичко е писано специално за целта, целия код е наш. Всичко е обектно-ориентирано написано - с класове.</p>
<p>Значи нещата са горе-долу на 3 слоя, що се касае обектите - data access, business logic, и UI. Първите два са заедно, в т.н. бизнес обекти, второто е в отделни класове за всеки интерфейс (примерно UI.Client.List.php за списъка на обекта Клиенти). Под тях имаш Core класовете на платформата (все едно .NET). Отделно има UI част към платформата с разни интерфейсни контроли (разбирай все едно Windows Forms), върху които стъпват всички интерфейсни класове на обектите - списъци, форми, и т.н.</p>
<p>&#8220;<em>Колко е трудно да се пише XML-а, как изглежда генерирания код и дали лесно се променя и къстъмизира?</em>&#8221;</p>
<p>Гледай сега, ти като започваш ново приложение (или нов обект, все тая), ти си правиш XML-а, описваши си вътре всичко - характеристиките, формата, списъка, свързаните обекти, всичко. Посто само го генерираш - той си прави таблиците, генерира си кода, регистрира се където трявба, всичко. Генерирания код не е предвиден да се коригира, защото като прегенерираш и го губиш. За целта обекта е „разслоен&#8221; на 3 слоя - един генериран (C__Client), един за базов код (C_Client) и един за къстъм код (CClient), които се наследяват взаимно. Базов и къстм код се отнасят за приложения, които ползваш с малки модификации на различни места - в базовия ти стои общото меду приложенията, в къстъм кода ти стои това, което е характерно само за дадено приложение. Трите класа не са задължителни, можеш да караш само с генерирания код - реално след генериране имаш готов обект за ползване - има си списък, има си форма, има си методи Add, Edit, Delete, може да си има статуси, всичко си му работи. Това, което прави, е да тъпче таблицата с данни. Можеш да си генерираш един куп неща, които редовно се ползват и се различават от просто „тъпчене&#8221; - autocomplete на полета едно от друго, свързване на полета (напрмиер полето Държава филтрира полето Град) и всякакви такива благинки. Оттам нататък, ако ти трявба къстъм код - например някакви нестандартни функционалности (например копче „Плати&#8221;, което прави плащане и сменя статус), просто си ги пишеш в бейс или къстм класа. Ако ти потрябва ново поле, просто си го добавяш в XML-а и прегенерираш. Т.е. XML-а не е за one time use, ти непрекъснато си прегенерираш и си го ъпдейтваш. Не можеш да започнеш от съществуваща база, при генерирането тя се съзадава при определени изисквания (ти реално можеш, но трябва да знаеш точно къде какво да сложиш и как да го именоваш). Като ти потрявба някакво къстъм query, просто си пишеш SQL. Генерирания код ти оправя главно Add/Edit/Delete/List операциите, за другото се оправяш сам.</p>
<p>„<em>Как се прави version control на custom applications разработени през интерфейса на платформата?</em>&#8221;</p>
<p>Ами за те са си файлове. Значи има експериментална функционалност за side by side версии на едно приложение, но оттам нататък сте си вие.</p>
<p>„<em>Какво ще правите след като Adobe пенсионират SVG viewera догодина?</em>&#8221;</p>
<p>Ами или Flash или Silverlight.</p>
<p>&#8220;<em>Има ли обособен ORM, или има само CRUD за вашите си обекти?</em>&#8221;</p>
<p>Относно ORM - доколкото схванах от четене по темата, това са обекти, които репрезентират релационна база данни. Нашето не е точно CRUD. Да, имаш перфектен съпорт на операциите с базата (CRUD), но към всеки обект имаш TypeDescriptor, от който можеш да получиш всичката информация, която съдържа обекта - какви полета има, какви са му релациите и с кого, как точно се филтрират тези релации (например обекта Client има връзка с Order, но с цел да покажеш списъка на поръчки към даден клиент по някакъв универсален начин, трябва някой да ти provide-не филтъра как се образува - в този случай: Order.ClientID=[parameter] например). Също така кой е PrimaryKey-а, абе всичко. Освен това тези бизнес обекти абстрактват повече от database логика - например т.н. &#8220;multiple&#8221; пропъртита - един клиент има повече от една категория, което очевидно се решава с релационна таблица между Client и Category. За бизнес обекта това продължава да си е едно пропърти, просто се хендълва различно. Освен това има много силно развита концепция за наследяване - например: имаме клиенти, доставчици, подизпълнители, банки и т.н.; всички те логически са контрагенти и като такива имат общи характеристики и функционалности и би следвало да има и общ списък. Елементс решава проблемът по следния начин: имаш таблица Contractor, която съдържа общите характеристики; после имаш таблици за Client, Supplier и Bank, които имат 1:1 релация с Contractor и съдържат характерните пропъртит само. В случая, за да добавиш клиент, практически правиш запис в 2 таблици минимум - Contractor и Client. На ниво PHP това са 2 класа, които се наследяват, и съответно имаш всички бенефити на стандартното наследяване. Когато конструрираш new CClient() клас и му насетнеш некви характеристики и викнеш Update(), Елементс сам си добавя записите където трявба и изобщо хендълва цялата логка по работата с тия данни вместо тебе. Има наследяване и на интерфейсно ниво и т.н.</p>
<p>&#8220;<em>Това нещо върви ли на Linux или Windows сървъри?</em>&#8221;</p>
<p>Ние си харесваме BSD-то :). Имаме клиенти, които са подкарвали успешно всичко на Slackware и RedHat, но пък сме имали бесни проблеми с Debian например, но предполагам, че всичко опира до добър администратор, защото Елементс не иска нищо друго, освен добре инсталирано PHP, eAccelerator и разни нормални библиотеки, заедно с някакъв web server. Но понеже това са &#8220;open source&#8221; истории, те се различават за всички операционни системи достатъчно, че да ти причинят главоболия - segmentation faults (личните ми фаворити) и всякакви други подобни. И така.</p>
<p>&#8220;<em>Всичко, което описва класове/контроли/база и т.н. е в конфигурация (xml файлове), а в базата не отива никаква application configuration information, нали така?&#8221;</em></p>
<p>XML файловете не са конфигурация, гледай ги като дефиниция. В базата има конфигурации, но това са такива, които потребителя може да променя (разбираемо, нали). Другото сме го изнесли във файлове, които се генерират, поради deployment съображения. Дори работим по идея, която ще разреши да копираш файловете някъде и то да си направи базата от нула със всикчки дефолтни записи и тям подобни неща, защото деплойването на промени по базата е един от най-големите бичове за нас.</p>
<p><em>&#8220;Трудно ли се мигрират данни?&#8221;</em></p>
<p>Значи мигрирането на данните не е кой знае какво, защото ние не сме си измислили някаква нова система за релационна база данни - тя си е ясна. Просто има определени конвенции за именоване, искаме си Foreign Keys и разни други дребни неща, но горе-долу това е. Но трбява да си го направиш ръчно. Ако не променяш структурата на съществуващото приложение всичко е окей, но пък Елементс взима определени решения за решаване на конкретни проблеми - например това, което ти описах за multiple пропъритата, което може да се различи с вашите. Но като цяло действаме като по учебниците :).</p>
<p><em>&#8220;Има ли API/възможност business objects сами да си говорят с базата, ако не наследяват от вашите data access класове?&#8221;</em></p>
<p>За толкова много ERP-та върху Елементс и още повече администрации на уеб сайтове не ни се е наложило да пишеме къстъм „приказки” на обекти с базата, не виждам защо и на някой друг ще се наложи. Освен това в Елементс има концепцията за Events – понеже в PHP концепция за делегати няма, ние общо взето си направихме такава. А всички обекти fire-ват AddComplete и EditComplete събития, както и можеш да Override-ваш Update метода, можеш да си симулираш get и set на характеристики така че имаш инструменти за къстъмизация на положението, не е като да няма. Имай предвид, че ние написахме платформата заради приложенията, не е обратното, тя се е развивала от нашите нужди, а смятам, че ERP-тата, бидейки сред най-големите приложения, покриват достатъчно много случаи. А и ние не обичаме да обикаляме проблемите, а да ги решаваме.</p>
<p>За къстъм бизнес обекти – мога да ти дам примера със справките – там имаш някакви ултра бясни queries, и като цяло имаш само интерфейсна част. Това няма смисъл да минава през бизнес обект, Елементс просто ти генерира скелет (който е нужен от гледна точка той да има информация що е то, като се касае до менюта, права и тям подобни информации), но оттам нататък, ти си пишеш бясното query и си го показваш или в списък, или като графика или по някакъв друг безумен начин.</p>
<p>Къстъм класове можеш да си добавяш на воля – ние имаме разни такива за разни приложения писани, които вършат общи задачи или държат информация просто, която се предава между обекти. Но бизнес обект да пишеш къстм просто не виждам смисъл – остави Елементс да ти генерира работата с базата и интеграцията със системата, пък ти си лющи после бизнес логика отгоре на воля.</p>
<p>Къстъм контроли можеш да си правиш, има заложена функционалност за това. Можеш да си комбинираш вече съществуващите само със сървърен код (както обикновено правим ние), а можеш да си комбинираш разни текстбоскове и комбота – ти си решаваш. Има си базов клас за такива неща. Контролите, които са binary-та, и правят HTTP заявки си се оправят в живота относно кодиране, декодиране и т.н. – даже имаме поддръжка за клиентски сертификати. Може би ако питаш нещо конкретно, ще мога да ти кажа по конкретно дали може или не – тъй общо ми е трудно да ти опиша какво може и какво не може.</p>
<p><em>&#8220;Как решавате проблеми с конфликти откъм клиентската страна с 3rd party javascript libraries &#8212; примерно любимите на всички prototype.js, производните и подобните му?&#8221;</em></p>
<p>Относно JS-а, аз не съм привърженик особен на тия „библиотеки”. Ние бая си умеем JS-а и си пишем каквото трявба сами, защото ако тръгнем да ползваме 2-3 библиотеки, трябва да почнем да се грижим за съвместимости и така нататък. Реално ти техничекси проблем нямаш да използваш каквото ти кефне, защото ние имаме политика при писането на такъв код с „namespaces” (var Clients = {}; Clients.DoSomething = function(){…}), въпреки, че има разни глобални функции, те са в друг Frame и оттам не би следвало да колидират с твоя код. Така че не виждам проблем. Но Елементс ползва доста js и добавянето на цяла библиотека може би ще има performance cost, но това са ми само размишления, не сме го пробвали никога. А и честно да ти кажа, имаме разни стандартни фунционалности – като например да извикаш метод на сървърен клас, да му пратиш аргументи и да получиш резултат, или да го конструираш и да си му имаш пропъртитата в клиентския скрипт и даже е желателно да се ползва това, вместо да си пишеш свое такова, защото то си е интегрирано и измислено така, че да работи добре с платформата и да е ефективно. Но пък никой не те спира да правиш каквто си искаш :).</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.ladger.com/2008/09/19/%d0%b7%d0%b0-%d1%80%d0%b0%d0%b7%d0%bb%d0%b8%d0%ba%d0%b8%d1%82%d0%b5-%d0%bc%d0%b5%d0%b6%d1%83-elements-%d0%b8-%d0%bf%d0%bb%d0%b0%d1%82%d1%84%d0%be%d1%80%d0%bc%d0%b8-%d0%ba%d0%b0%d1%82%d0%be-net/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Създаването на SeeNews</title>
		<link>http://blog.ladger.com/2008/09/19/%d1%81%d1%8a%d0%b7%d0%b4%d0%b0%d0%b2%d0%b0%d0%bd%d0%b5%d1%82%d0%be-%d0%bd%d0%b0-seenews/</link>
		<comments>http://blog.ladger.com/2008/09/19/%d1%81%d1%8a%d0%b7%d0%b4%d0%b0%d0%b2%d0%b0%d0%bd%d0%b5%d1%82%d0%be-%d0%bd%d0%b0-seenews/#comments</comments>
		<pubDate>Fri, 19 Sep 2008 12:00:56 +0000</pubDate>
		<dc:creator>admin</dc:creator>
		
		<category><![CDATA[Добри практики]]></category>

		<category><![CDATA[Общи]]></category>

		<category><![CDATA[Case Study]]></category>

		<category><![CDATA[JavaScript]]></category>

		<category><![CDATA[PHP]]></category>

		<category><![CDATA[Web]]></category>

		<category><![CDATA[Web Site]]></category>

		<guid isPermaLink="false">http://blog.ladger.com/?p=26</guid>
		<description><![CDATA[Създаването на порталът за новини SeeNews]]></description>
			<content:encoded><![CDATA[<p><em>Владислав Косев,<br />
Development Manager</em></p>
<p>Това е case study за портала за новини и профили на компании SeeNews. Поради това, че сайтът е голям и като цяло сложен като архитектура и изпълнение, решихме, че е хубаво да споделим с останалите опита си.</p>
<p><span id="more-26"></span></p>
<h2>A. Сървъри и приложения</h2>
<h3>A.1. Описание на проекта</h3>
<p>SeeNews е портал за новини и профили на компании, държави и хора. Материалите и профилите се попълват от специално CMS приложение, което освен сайта, изпраща материали и на други клиенти.</p>
<p>Сайтът има общ login (общ за SeeNews и SeeWire), който се управлява от сайта SeePassport. SeeWire е сайт само за бутикови новинарски материали.</p>
<h3>A.2. Схема</h3>
<p>Решението на SeeNews е разположено на 2 сървъра. На Server 1 са разположени CMS приложението, банер системата за сайта и 2 приложения, които публикуват материали към външните клиенти на AII Data Processing и SeeNews (Publisher и SeePublisher). На Server 2 са разположени сайтовете SeeNews, SeeWire, SeePassport и CMS-а на сайта.</p>
<p><a href="http://blog.ladger.com/wp-content/uploads/2008/09/seenewscasestudy1.jpg"><img class="alignnone size-medium wp-image-25" title="SeeNewsCaseStudy1" src="http://blog.ladger.com/wp-content/uploads/2008/09/seenewscasestudy1-300x148.jpg" alt="" width="300" height="148" /></a></p>
<h2>B. Процеси</h2>
<h3>B.1. Публикуване</h3>
<p>От CMS приложението се публикуват записи в две посоки – към директни (външни) клиенти на приложението (логика, която не зависи от уеб сайта) и към уеб сайта.</p>
<h4>B.1.1. Външни клиенти</h4>
<p>След публикуване на материал от CMS-а, по предварително дефинирани динамични правила се определя на кои клиенти трябва да се прати. Всеки клиент има изискване за определен формат, в който да се изпраща материалът и метода на доставка (FTP, e-mail), като цялата логика около определянето на форматите и адреси е динамична и се контролира от CMS-а. Файловете и адресите, на които трябва да се пратят се попълват в специална база данни. Самото пращане се извършва от приложението Publisher.</p>
<p>Publisher-а е написан на Perl и работи резидентно. Следи опашки от новини, които трябва да се доставят и се грижи те да бъдат доставени, като прави допълнителни проверки в зависимост от метода на доставка за да гарантира успешна доставка.</p>
<p>Към уеб сайта се публикува по-голямата част от информацията в CMS-a. Информацията се публикува на ниво база данни и по същество представлява репликиране на данните от единия на другия сървър. Има две основни логики на публикуване в CMS-а – изрично и по подразбиране. Определени типове данни минават дълъг процес на одобрение преди да бъдат публикувани и при тях процеса е изричен, като това се обслужва от специални скриптове, тъй като става дума за публикуване на информация в десетки релационни таблици наведнъж. Останалите типове данни се публикуват в момента на въвеждане в CMS-а. Кой тип данни се публикува и кой не, се определя също динамично от CMS приложението.</p>
<p>Публикуването на нов запис към уеб сайта е силно обвързано с кеширането на данните на сайта. Затова приложението, което публикува (SeePublisher) има възможност да му бъде указано, че след публикуването на записи от определени таблици да изпълнява определени команди на Server 2.</p>
<p>Публикуването на материали е относително просто, защото става дума само за 6 таблици, като информацията в 6-те таблици винаги се въвежда по едно и също време. В този случай единственото, което се указва, е уведомяването на Server 2 (което ще бъде разгледано по-надолу) за публикуването на нов материал.</p>
<p>От друга страна, публикуването на профилите предоставя няколко сериозни предизвикателства:</p>
<ul>
<li>Данните стоят в 20+ релационни таблици</li>
<li>След публикуване на информацията във всички таблици е необходимо да бъде уведомен Server 2 за това.</li>
</ul>
<p>Профилите на компании, индустрии и държави се публикуват изрично, т.е. след изрична итерация от потребителя в CMS-а. За целта на публикуването на профили към външни клиенти има изискване файла с информацията за профила да е в стила на уеб сайта. Тъй-като такава логика (по представянето на профила) имаше вече направена в уеб сайта беше непрактично да я повтаряме в CMS-а, защото става дума за огромно количество код (над 3000 реда) и логика. Затова решихме след публикуване на материал да взимаме output-а от уеб сайта и да го връщаме като файл на CMS сървъра. Тази операция се извършва от SeePublisher приложението. За да може да заработи тази логика обаче, трябва SeePublisher-а да знае точно кога е публикувал целия профил, за да може да извика скрипта за генериране на файла на профила, който да върне обратно и да уведоми локален процес, който пък поставя файла за одобрение от потребителите в CMS-а. За целта дефинирахме „партиди” – групи от записи в различни таблици, които фигурират срещу общо име и команди, които да се изпълняват при публикуване на всяка партида.</p>
<p>Освен записи от базата, SeePublisher-а публикува и файлове, качени в CMS-а – файловете се качват в приложението, resize-ват се, обработват се и всички резултатни файлове, заедно с кореспондиращия запис в базата се записват за публикуване.</p>
<h3>B.2. Борсова информация и време</h3>
<p>Борсовата информация и времето се импортват от отделни скриптове в cron-а на Server 2.</p>
<h4>B.2.1. Борсова информаци</h4>
<p>Тъй-като борсовата информация трябва да е за цяла Югоизточна Европа, данните се взимат от няколко различни доставчика – интегратори и борси. Данните се управляват от CRM приложението.</p>
<p>Логиката за това коя борса кога затваря е писана отделно за всеки случай.</p>
<h2>C. Уеб сайтове</h2>
<h3>C.1. Генериране и кеширане на информацията</h3>
<p>Още при проектирането на сайтовете бяхме наясно, че тук с обичайната логика (директна връзка с базата) няма да можем да постигнем желаният резултат и ще трябва да правим някакво кеширане на информацията.</p>
<h4>C.1.1. Url-та на сайта</h4>
<p>Доброто SEO на сайта изисква пътищата да бъдат „human-readable”. Тъй-като обаче сайтът има огромна структура преценихме, че използването на mod_rewrite на apache ще се окаже неефективно и не недостатъчно контолируемо - сайтът е с централизиран индекс файл и всички заявки минават през него, като се определя коя заявка кой template поема. Или трябваше да направим много малко rule-ове и да пращаме искания път към индекс файла (напр. news/latestnews), където той да се декодира, или да напишем декодиране. Другата опция беше да направим custom 404 страница и там да декодираме пътя. Това декодиране обаче щеше да е изпълнява на всяка заявка това щеше да товари базата данни – всички секции в сайта са динамични и се контролират от потребителите – съответно декодирането става чрез записи в базата. Затова решихме да генерираме физически директории за всички пътища, като във всяка директория има по един индекс файл, който препраща към главния и му предава информация за текущата секция. Това обаче ни отвори друг проблем – новините бяха доста над 200 000 и „ударихме” в ограничението на файловата система за брой директории. Тъй като за файлове ограничението е доста повече, решихме специално за материалите да генерираме директориите разпределени по групиращи директории и да правим symlink-ове към тях на нужното място.</p>
<p>При показването на линковете в сайта (тъй като те се дават по кодове на секции) се извършва кодиране – т.е. от код секция към Url. Тъй като това е също тежка работа (на челна страница има над 1000 линка), решихме освен да ги напълним в една таблица готовите Url-та за всяка секция и всеки запис, както и да кешираме използваните на всяка секция линкове във файлове. По този начин времето за генерирането на линкове се сведе до доста приемлива стойност.</p>
<h4>C.1.2. Информация в сайта</h4>
<p>След доста дебати първоначално решихме, че ще кешираме информацията в РНР масиви в отделни файлове.</p>
<p>За целта за всеки масив имаше специален скрипт, който генерира съответното количество файлове. Файловете се оказаха доста, защото освен, че имахме доста блокове, които ползваха масиви, сайта има 11 режима (по един филтриращ за всяка от 10 държави в Югоизточна Европа и един нефилтриран). Освен това има доста блокове, които показват информацията по индустрии или по теми (индустриите са около 130, а темите – 70), та се получиха доста файлове.</p>
<p>След това всеки блок в сайта include-ваше необходимия си файл и използваше данните от масива. Генерирахме масиви за почти всичко – новини, събития, търгове, навигацията на сайта, време, валутна информация, борсова информация.</p>
<p>Прегенерирането на информацията не беше проблем, защото цялата информация минава през SeePublisher-а, а той при всяко публикуване извиква Gateway приложението, което следи според това какво се публикува каква информация трябва да се прегенерира – всички масиви сложихме в базата заедно с условията им за инвалидиране.</p>
<p>Gateway процеса отговаря за няколко неща:</p>
<ul>
<li>Извикване на нужните скриптове за прегенериране на масивите;</li>
<li>Генериране на директории за новите записи на нужните места;</li>
<li>Изпращане на Alert-и към клиенти на уеб сайта;</li>
<li>Ново напълване на кеширащите таблици за SeeWire (описани са по-подробно в частта за Wire сайта);</li>
<li>Опресняване на гъстотата на ключовите думи в сайта (в колко новини се среща всяка една).</li>
</ul>
<p>Отделно скриптовете за борсовата информация, валутите и времето също викаха скриптовете за прегенериране.</p>
<p>След като сайта беше пуснат и натрупа достатъчно статистика за анализ се оказа, че не сме избрали коректен подход. Сайта се товареше от многото заявки и имаше усещането, че е бавен. Анализирайки статистиката намерихме възможности за оптимизация, както клиентска, така и сървърна.</p>
<h3>C.2. Оптимизация</h3>
<p>Затова решихме заедно с възложителя да оптимизираме сайта, за което избрахме друг подход за кеширане и да оптимизираме клиентската и сървърната скорост на приложението.</p>
<h4>C.2.1. Сървърна скорост</h4>
<p>След доста проучване и няколко теста решихме да се откажем от масивите и да генерираме направо HTML-а от всеки блок във файл. Логиката е следната:</p>
<ul>
<li>Всеки блок/темплейт си се пише със заявки към базата, без никакво кеширане;</li>
<li>Тъй-като всеки блок и темплейт е отделен клас, ако има нужда да се кешира той наследява общ базов клас (CachedBlock), който всъщност поддържа кеширането;</li>
<li>Всеки клас предоставя някакъв идентификатор (име), което служи за име и път до генерирания файл;</li>
<li>Всеки клас за блок или темплейт има специален метод Render, който генерира HTML кода. Преименувахме го на RenderInternal (който е protected) и оставихме Render само в кеширащия клас като final. Така там сложихме логика – ако има генериран файл директно се връща съдържането му, без да се извиква RenderInternal, а ако няма – минава по нормалната логика и output-а на RenderInternal се записва във файл.</li>
</ul>
<p>Има един общ клас (CacheManager), който съдържа логиката по конвертиране на имена към пътища на файлове, писане, четене и инвалидиране на кеширащи файлове.</p>
<p>Gateway-а беше „преквалифициран” да трие кеширащи файлове. Всички блокове сложихме в таблица, и по този начин динамично се определяше кои файлове да се изтрият. Например, ако се публикува материал, който е асоцииран с държавата България, се трият само файловете за нефилтрирания режим и този за България – очевидно тази новина няма да се вижда в другите режими и това не променя генерирания код за тях. След това първия потребител, който отвори сайта, практически „пуска” генератора за изтритите файлове. Това можехме да си го позволим, тъй като не съществува теза, в която генерирането е бавно и че няколко потребителя могат да стартират успоредно генериране.</p>
<p>Кеширането вдигна скоростта на сайта значително – постигнахме производителност, която е 30 до 35 пъти по-висока. Тъй като кеширахме реално всичко по страницата заявки към базата почти нямаше (освен един-два блока, които показваха неща на случаен принцип и нямаше как да ги кешираме). Където не можехме да кешираме (например контекстни новини към дадена новина, където комбинациите са милиони, а и логиката за триене на кеша щеше да е доста сложна), а дадения блок е бавен, просто го пускаме да се зарежда през AJAX, за да не бави страницата.</p>
<p>Логиката за изтриване е обща за SeeNews и SeeWire и по префикса на името на всеки блок в базата се разбира къде са файловете, които трябва да се изтрият.</p>
<p>Специално за SeeWire разработихме кеширане на второ ниво – база данни, понеже самите резултати от търсенето не могат да се кешират (или е твърде сложно). Тъй като в сайта може да се търси за последните 2 седмици, 1, 2, 3 и 6 месеца назад, направихме таблици с материали в тези периоди, които се прегенерират при всяко публикуване (отново отговорност на Gateway-a).</p>
<h4>C.2.2. Клиентска скорост</h4>
<p>След като оптимизирахме сървърната част на сайта се оказа, все още имахме проблем със зареждането на сайта – браузъра го показваше на части. Във SeeWire имахме същия проблем, само че доста по-изразен. Направихме тестове с няколко начина на сглобяване на HTML-а и общо взето разликите бяха минимални – единственото, което се оказа, че има някакъв видим ефект, е таблици със значително по големина съдържание. Също така стана ясно, че inline style атрибутите на таговете също нямаха никакво (или пренебрежително) отношение към клиентската скорост на сайта.</p>
<p>След доста тестове в крайна сметка открихме проблема – и двата сайта използват много javascript и поради това, че всеки блок сам си include-ва файловете, които ползва, и генерираше нужния му inline скрипт, сайтовете се оказваха осеяни с include-ване на JS файлове и inline javascript код. Ако този код се сложи в началото или в края на HTML-а, сайтовете просто „излитаха” (Всъщност доста време вярвахме, че проблемът е в количеството HTML – SeeNews е 170к, a Wire-a е 490к, но опитът ни с други по-големи сайтове ни отказа от тази ни концепция).</p>
<p>За да се справим проблема, разработихме система в двата сайта, която позволява на блоковете да „регистрират” използваните JS файлове и генерирания скрипт. След това целия този код се изсипва преди counter-ите накрая на страницата. С това постигнахме няколко неща:</p>
<ul>
<li>Намаляване на броя JS файлове в HEAD-а на сайта. Там оставихме само файлове, които се използват на всяка страница от сайта;</li>
<li>Премахване на скрипта от „средата” на страницата;</li>
<li>Намаляване на броя include-вания на JS файлове като цяло – ако два блока ползват един и същия файл, то при регистрирането на втория блок се проверяват досега регистрираните файлове и ако се намери същия файл, извикването се игнорира.</li>
</ul>
<p>Това решение обаче отвори два проблема – първия е: какво става между „рисуването” на даден блок от браузъра и „изсипването” на нужния му скрипт на края на страницата? Реално всички блокове трябваше да започнат да проверяват дали им е зареден файла. В крайна сметка обаче, тъй като клиентското време сериозно се подобри, това не беше кой знае какъв проблем, защото потребителя трудно щеше да уцели точно този момент да кликне.</p>
<p>Вторият проблем се оказа по-сериозен:</p>
<p><a href="http://blog.ladger.com/wp-content/uploads/2008/09/seenewscasestudy2.jpg"><img class="alignnone size-full wp-image-30" title="SeeNewsCaseStudy2" src="http://blog.ladger.com/wp-content/uploads/2008/09/seenewscasestudy2.jpg" alt="" width="499" height="280" /></a></p>
<p>Това е схема на извикванията методите Render() на блоковете – вижда се, че регистрирането на скриптовете става в същия код, където се генерира HTML-а и този код се извиква само когато не е намерен кеширащ файл. Съответно при наличие на такъв файл целия метод RenderInternal се пропуска и никой не регистрира кода и той не се добавя и блока остава без JS кода си, когато се използва кеш. За да се реши този проблем, класа CachedBlock добавя собствени методи RegisterScript и RegisterScriptFile и записва какъв код и файлове регистрира блока, който го е наследил. После, при писането на кеша се правят и два допълнителни файла – за inline кода и за файловете. В последствие когато се използва кеш, се използват двата файла за регистриране.</p>
<p>Изнасянето на скрипта реши огромна част от проблема. Оставаше да се направят стандартни оптимизации – почистване на CSS файловете (с цел намаляване на размера му) – той отнема прилична част от клиентското време и премахване на излишен HTML код.</p>
<p>Банерите също пречеха на доброто зареждане на страницата и с тях подходихме като с JS кода – изнесохме ги накрая. Този подход не е перфектен, защото ако се „рисуват” банерите off-screen, трудно после може да се познае кой банер за коя зона е, а няма как да се контролира document.write(), който използва банер системата. Но при наличието на 3-4 зони този проблем се избягва.</p>
<p>Ефектът на изнасянето на скриптовете беше особено виден при SeeWire, поради това, че той използва голямо количество JS (всички комбобоксове и scrollbar-ове са custom, не се използват стандартните контроли на браузъра) и като резултат сайта се зареждаше особено бавно клиентски и при изнасянето на кода разликата беше наистина голяма.</p>
<h3>C.3. SeePassport</h3>
<p>Този сайт служи изцяло за регистриране на потребителите и управление на профилите, кошницата, поръчките им, и абонаментите им за бюлетини и алерти. Цялата информация се записва и управлява от CRM приложението.</p>
<h4>C.3.1. Логване на потребители</h4>
<p>И трите сайта (SeeNews, SeeWire и SeePassport) ползват една и съща форма, предоставена от SeeWire, също и във вариант за floating layer. Самата логика по валидиране и логване също се изпълнява от SeePassport. Има разработена система, която предотвратява логване с един и същи акаунт от два компютъра едновременно.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.ladger.com/2008/09/19/%d1%81%d1%8a%d0%b7%d0%b4%d0%b0%d0%b2%d0%b0%d0%bd%d0%b5%d1%82%d0%be-%d0%bd%d0%b0-seenews/feed/</wfw:commentRss>
		</item>
		<item>
		<title>LH v.s. KR</title>
		<link>http://blog.ladger.com/2008/09/13/lh-vs-kr/</link>
		<comments>http://blog.ladger.com/2008/09/13/lh-vs-kr/#comments</comments>
		<pubDate>Fri, 12 Sep 2008 23:27:10 +0000</pubDate>
		<dc:creator>admin</dc:creator>
		
		<category><![CDATA[Общи]]></category>

		<category><![CDATA[Formula 1]]></category>

		<guid isPermaLink="false">http://blog.ladger.com/?p=16</guid>
		<description><![CDATA[Onboard camera
]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.youtube.com/watch?v=bbpLUZlV-A4">Onboard camera</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.ladger.com/2008/09/13/lh-vs-kr/feed/</wfw:commentRss>
		</item>
	</channel>
</rss>
