Vše o pluginech typu archivátor pro Altap Salamander
====================================================
Autor: ALTAP, spol. s r.o.

Obsah
 1. Vznik a historie archivátorů
 2. Budoucnost archivátorů
 3. Hlavní a vedlejší funkce archivátorů
 4. Krátký úvod do pluginů
 5. Instalace archivátorů do Salamandera
 6. Jak vidí archivátory uživatel Salamandera
 7. Rozdíly mezi archivátory a pluginy typu file systém
 8. Vrstvená architektura archivátoru a jejich engine
 9. Životní cyklus pluginu
10. Úvod do implementace archivátoru
11. Držení listingu archivu: kontext
12. Optimalizované listování archivu
13. Vrstva Input/Output
14. Ošetření vstupů a výstupů: CSalamanderSafeFileAbstract    
15. Minimalizace fragmentování disku během zápisu souborů
16. Čeho se vyvarovat během implementace archivátoru
17. Testování archivátorů


1. Vznik a historie archivátorů
===============================
Archivátory, respektive packery a unpackery dnes tvoří největší skupinu pluginů
Salamandera. Salamander ve verzi 1.52 (poslední freeware verze) archivátory
nepodporoval v žádné formě a uživatelé na nás vyvíjeli silný tlak. Největší
poptávka byla po formátu ZIP, který je nejrošířenějším typem komprese.

Do verze 1.6 beta 1 (březen 1999) jsme implementovali podporu pro práci se ZIP 
archivy. Vše bylo řešeno v rámci jádra Salamandera s využitím knihoven zip32.dll
a unzip32.dll z freeware balíku InfoZIP. Tyto dvě knihovny se během beta testování
ukázaly jako závažný zdroj chyb, z části pro jejich multiplatformní zaměření. 
Zároveň byla připojena knihovna tardll.dll, zárodek budoucího pluginu TAR 
a knihovna pak.dll, která byla později převedena na plugin PAK.

Ve stejnou dobu jsme implementovali podporu pro externí archivátory. Externí
archivátory jsou postaveny na parsování listingů různých pakovačů volaných z 
příkazové řádky. Tento přístup přináší mnohá úskalí, protože externí archivátory
běží jako samostatné procesy, narozdíl od pluginu běžícího v rámci procesu
Salamandera. Jejich ovladatelnost se tím prudce snižuje. Listingy se lišily 
verze od verze externího pakovače, měnily se jeho návratové hodnoty, není 
možné zobrazovat progress během pakování a rozpakování atd. 

Postupem času jsme nejpoužívanější externí archivátory převedli na pluginy typu
archivátor. Externí archivátory jsou podporovány Salamanderem dodnes a jejich
hlavní těžiště spočívá ve vytváření archivů. Zatímco kód pro rozbalování a
procházení archivů je často free (ať už formou zdrojového kódu nebo nějaké
knihovny), pro jejich vytváření je třeba vlastnit licenci na archivátor, která
svou cenou často přesahuje cenu celého Salamandera. Salamander proto dokáže
vyhledat externí archivátory na disku uživatele a během pakování je zavolat. 
Žádný externí archivátor však není součástí distribuce Salamandera.

Salamander ve verzi 1.6 beta 4 (červen 2000) obsahoval první implementaci pluginů
ve formě, jak jí známe dnes. Zdrojový kód jádra se implementací pluginového
rozhraní  zvětšil o 1 MB. V tuto dobu jsme vyvinuli první plugin typu archivátor: 
náš vlastní ZIP. Z InfoZIPu přebírá pouze algoritmy (jako Inflate a Deflate),
vše ostatní je naprogramováno znovu. 

Pokud v následujícím textu budeme zmiňovat archivátor, máme na mysli plugin
typu archivátor, tedy plugin umožňující procházení a vybalování archivů, případně
jejich vytváření. Pojmem externí archivátor se rozumí samostatný pakovač 
(jako je ARJ.EXE), který není součástí distribuce Salamandera, ale Salamander
ho dokáže spouštět, tedy zná jeho specifické switche, listingy a návratové kódy.

Pluginy typu archivátor (archivátory) vznikaly postupně:
  1.6 beta 4:  ZIP, TAR
  1.6 beta 6:  PAK, UnACE, UnARJ a UnRAR 
  (finální verze 1.6 nikdy nevznikla, byla přejmenvána na verzi 2.0)
  2.0:         UnCAB 
  2.5 beta 1:  UnLHA a UnMIME
  2.5 beta 3:  Encrypt a UnISO
  2.5 beta 4:  UnFAT

Jak postupoval vývoj Salamandera, rozšiřovalo se jeho rozhraní pro pluginy a
zároveň se měnil obraz ideální implementace archivátoru. Tyto změny umožnily
přesunutí některých opakujících se částí kódu z pluginů do jádra Salamandera
a odstranění zbytečných redundancí. Zároveň se zvýšila volnost v držení a 
předávání dat mezi Salamanderem a archivátorem.

Ke změnám v rozhraní Salamandera nedocházelo samovolně. Některé změny si vyžádala
implementace nových funkcí na straně Salamandera. Jiné změny byly provedeny při
vzniku nových archivátorů pro specifické potřeby jejich autorů. Staré pluginy
většinou nereflektovaly tyto změny a nyní používají překonané nebo neefektivní
techniky.

Během testování nových archivátorů narážíme na opakující se skupiny problémů
a chyb. Jak roste počet archivátorů, přestává pro nás být únosné pečlivě 
otestovat všechny nástrahy a každému z autorů je vysvětlovat jednotlivě. 
Proto mimo jiné vzniká tento dokument, který má potenciální problémy zdokumentovat
a navrhnout jejich řešení. Autoři archivátorů tak budou schopni své projekty
lépe ošetřit a protestovat.


2. Budoucnost archivátorů
=========================
Rozhraní pro archivátory zatím nedozrálo do finálního stavu a čeká nás
ještě několik jeho obrození. K nejžádanějším a plánovaným funkcím patří:
 -práce s archivy na pozadí (jako u základních operací Salamandera copy/move/del...)
 -podpora pro zanořené archivy (nested archives)
 -hledání v archivech (také ve vnořených archivech), hledání v obsahu
 -možnost vstupu do SFX/neasociovaného archivu pomocí Ctrl+PgDn
 -přejmenování souboru a adresáře přímo v archivu
 -možnost označit v panelu větší množství archivů a vybalit je jedním příkazem
 -implementace metod Inflate a Deflate na úrovni jádra Salamandera a vyvezení
  jejich rozhraní, abychom předešli redundanci kódu v pluginech
 -zobrazování compress ratio ve zvláštním sloupci v panelu
 -komprimace souborů a adresářů nalezených Find dialogem
 -podpoření dalších formatů, jako je CHM (kompilát HTML souborů sloužící jako
  helpy ve Windows), DBX (Outlook Express archiv), DEB (distribuční balíky Debian)
  a jiste přijdou další kandidáti
 -porovnání archivu a adresáře na disku také podle obsahu souborů

Některé funkce budeme snad schopni implementovat bez zásahu do rozhraní (jako
nedávno přidanou podporu pro drag&drop), ale některé budou znamenat úpravy na
straně archivátorů. Nezbytná budoucí údržba archivátorů je dalším důvodem k napsání
tohoto dokumentu. Pokud máme provádět změny nad dvaceti archivátory, pak ať jsou
si jejich zdrojové kódy a jejich chování pokud možno co nejpodobnější.


3. Hlavní a vedlejší funkce archivátorů
=======================================
Plugin typu archivátor primárně slouží k vytváření, procházení a rozbalování
archivů. Archiv je soubor (nebo množina souborů), který obsahuje kompilaci 
souborů a adresářů, většinou ve stromové hierarchii. Archivy mají různé
účely, nejčastěji se jedná o komprimaci a archivaci souborů. Za archiv
lze považovat i image CD nebo diskety, stejne jako email s přílohou ve
formatu EML.

Archivátory dělíme podle nabízených funkcí na packery a unpackery. Unpackery
umějí pouze procházet a vybalovat obsah archivu (používáme pro to pojmy expand,
unpack, extract, explode, rozbalení, extrakce atd.) a jejich název má prefix 
Un (UnARJ, UnCAB, ...). Pluginy ZIP, PAK, Encrypt a 7-Zip umějí navíc archivy
vytvářet a omezeně modifikovat (packery). Omezenou modifikací archivu myslíme
přidávání souborů a adresářu do existujícího archivu a mazání souborů a
adresářů z archivu. Při instalaci plugin Salamanderu sdělí, které skupiny
funkcí podporuje.

Jeden plugin typu archivátor je často asociován s větším množstvím formátů. 
Dobrým příkladem je právě TAR, který dokáže rozbalit formáty TAR, TGZ, GZ,
TBZ, BZ, BZ2, RPM, CPIO, TAZ a Z. Snažíme se sdružovat tématicky podobné
formáty pod jeden plugin z důvodu společného enginu pro přístup k datům,
případně pro logickou příbuznost formátů.

Archivátory mají možnost přidat nové sloupce do panelu a zobrazit tak například
Packed Size nebo Method (viz plugin 7-Zip).

Některé archivátory jsou zároveň registrované jako viewery a dokáží zobrazit
informace o archivu, které nám obyčejný listing v panelu nedovolí prezentovat.
Například UnISO plugin dokáže zobrazit počet sessions a tracků obsažených
v obrazu CD.

Pokud je archiv otevřen v panelu, umožňuje Salamander porovnat jeho obsah
s adresářem na disku (Compare Directories). Zatím není podporováno porovnání
podle obsahu, které bude muset jednotlivé soubory vybalovat. 

Archivátory mohou přidat své příkazy do menu Plugins a zpřístupnit tak některé
speciální funkce. ZIP plugin nabízí možnost zobrazit a změnit komentář
ZIPu, případně otestovat jeho integritu. 


4. Krátký úvod do pluginů
=========================
Pluginy slouží k rozšiřování funkcionality Salamandera. K jejich implementaci
jsme přistoupili po několikatýdenní diskuzi. Jejich původní návrh zamýšlel
pluginy minimálně o řád mocnější a obecnější, než je jejich současná implementace.
K tomu naštěstí nedošlo, byl by to s určitostí konec Salamandera. I podpora
pro současnou zjednodušenou formu pluginů na straně Salamandera nas stála přes
rok práce a programování pluginů typu file system rozhodně není procházkou
růžovým sadem.

Přes všechna úskalí a problémy považujeme zvolenou pluginovou architekturu
za krok správným směrem. Důvody shrnu v krátkých bodech.

Výhody modulární architektury proti monolitické aplikaci:
-S rostoucím množstvím implementovaných funkcí se aplikace stávají méně
 přehledné a přijatelné pro nové uživatele. Modulární architektura umožňuje
 nainstalovat pouze pluginy, potažmu příkazy, které uživatel potřebuje.
 Lze připravit několik verzí instalace (Lite/Standard/Full).
-Pluginy lze zavádět do paměti až v okamžiku potřeby. Tím se zkracuje
 startovací čas aplikace a zmenšují se její paměťové nároky.
-Díky pluginům jsme schopni uřídit jejich autory při implementaci nových
 funkcí Salamandera. V dnešní době leží většina zdrojového kódu právě
 v pluginech. Jádro se od verze 1.0 stále vyvíjí a rozšiřuje, nikdy
 nebylo přepsáno. Zanesení změn do něj se stáva černou magií, když si
 na určitou triviální změnu uděláme třikrát víc času, většinou neprohloupíme.
-Díky SDK mají další subjekty možnost implementovat vlastní pluginy, třeba i pro
 vnitřní potřebu (žádané jsou zejména archivátory pro proprietární datové formáty)
-Pluginy lze snadno upgradovat formou malého instalačního balíčku.
-Do komerční close-source aplikace lze při vhodně zvolené licenci (LGPL)
 začlenit open-source moduly.
-Je tu slušná šance na vznik programátorské komunity, která naimplementuje
 pluginy, které nebudou v našich silách. Samozřejmě je pak otázkou jejich
 kvalita. Tento model se silně projevil u Total Commandera a FARa.

Nevýhody modulární architektury:
-Do jádra Salamandera bylo třeba implementovat rozhraní pro pluginy.
-Vznikají redundance v kódu pluginů, například Inflate/Deflate algoritmus
 je dnes minimálně na pěti místech.
-Pokud rozhraní Salamandera nepublikuje určitou funkci, plugin ji nemá dostupnou.
-Monolitické aplikace jsou lépe přijímány uživatelem: pokud si může na
 disketu nakopírovat jedno EXE a jeden INI soubor, je uživatel štastný.
 Ze složité stromové struktury se mu obrací žaludek.

Plugin Salamandera je DLL knihovna, která exportuje dva entry pointy:
SalamanderPluginEntry a SalamanderPluginGetReqVer. SalamanderPluginEntry slouží
k navázání komunikace mezi jádrem Salamandera a pluginem. Druhý export slouží 
k ověření vzájemné kompatibility určité verze pluginu a Salamandera.

Pluginy jsou standardně umístěny v adresáři plugins pod domovským adresářem
Salamandera. Každý plugin má svůj podadresář. V něm je umístěn vlastní plugin
s příponou SPL (Salamander PLugin). Pluginy můžou být i v jakémkoliv jiném
adresáři, což se hodí např. při jejich vývoji.

Konfigurace pluginu je uložena v Registry jako podstrom konfigurace Salamandera
a plugin k ní přistupuje prostřednictvím vyhrazeného rozhraní. Z uživatelského
hlediska je konfigurace pluginů oddělena od konfiguračního okna Salamandera
(Options / Configuration). Konfigurační okna pluginů lze otevřít z menu
Options / Plugin Configuration, případně z okna Plugins Manager. Samozřejmě
nás lákala myšlenka integrovat konfiguraci pluginů jako jednu ze stránek 
konfigurace Salamandera, ale po zralé úvaze jsme tuto variantu opustili.
Přineslo by to obrovské množství komplikací.

Rozlišujeme několik základních skupin pluginů podle funkcí, které nabízejí.
Skupiny se dle potřeby v pluginech různě prolínají. Jsou to:
-viewery
-archivátory (packery a unpackery)
-file systémy (WinSCP, FTP, Registry Editor, Windows Mobile)
-rozšíření menu (File Comparator, Check for new version, Split & Combine, ...)
-načítače thumbnailů (PictView, Eroiica)

Viewery, archivátory a file systémy mají své chování v rámci Salamandera 
pevně dáno. Viewery se aktivují příkazem View (F3), archivátory příkazem
Pack (Alt+F5), Unpack (Alt+F9) nebo při otevření archivu v panelu (Enter
na archivu). File systémy se aktivují z Change Drive menu (Alt+F1/2).
Pluginy rozšiřující menu mají své příkazy v menu Plugins, kde mají svá
submenu. Pluginy načítačů thumbnailů se registrují pro skupinu masek
souborů a Salamander je pak pro tyto soubory postupně volá (panel v režimu
Thumbnails).


5. Instalace archivátorů do Salamandera
=======================================
Archivátory se z hlediska instalace ničím neliší od ostatních pluginů.

Existují tři cesty, jak se Salamander dozví o existenci pluginu. První
cestou je nová instalace Salamandera, kdy Salamander prochazí adresář plugins
ve svém domovského adresáři a snaží se všechny soubory s příponou .SPL přidat
mezi pluginy.

Druhá cesta je variantou té první a používáme jí při samostatné distribuci
nových nebo aktualizovaných pluginů. Uživatel si z www stránek stáhne instalační
balíček (.EXE), který ho před instalací vyzve k ukončení Salamandera. Následně
umístí plugin do adresáře plugins a modifikuje textový soubor plugins\plugins.ver
tak, aby po novém spuštění Salamandera došlo k automatickému přidání pluginu.

Třetí cesta vede přes ruční správu pluginů. V menu Plugins je příkaz Plugins
Manager, který otevře okno určené k prohlížení, přidávání, odebírání a konfiguraci
pluginů. 

Konfigurace Salamandera je uložena v Registry. Každá verze Salamandera
(včetně beta verzí) má svůj vlastní nezávislý klíč. Například
REG:\HKEY_CURRENT_USER\Software\Altap\Altap Salamander 2.5
Během instalace nové verze Salamandera má uživatel možnost importovant některou
ze starších verzí konfigurace. Důvodem je komfort uživatele, který nemusí
s instalací každé nové verze znovu procházet všechny volby konfigurace.

Schopnost načíst všechny staré verze konfigurace a konvertovat je na konfiguraci
novou tak padá také na pluginy. Otázkám konfiguračních záznamů pluginů a jejich
konverzi se budeme věnovat ve zvláštní kapitole.


6. Jak vidí archivátory uživatel Salamandera
============================================
Z pohledu uživatele existují dva rozdílné přístupy k rozbalování archivu (případně
pakování do něj).

Jeden přístup je interaktivní. Uživatel vyhledá archiv a klávesou Enter do něj
vstoupí. Obsah archivu se zobrazí v panelu. Uživatel může příkazem View prohlížet
obsah souborů, klávesou Space sečíst velikost souborů a na závěr například
některé soubory označit a překopírovat je příkazem Copy do protějšího panelu.
Archivátor může v tomto interaktivním režimu nabízet pouze read-only přístup,
tedy procházení (listování) archivu nebo může podporovat také změnu archivu (editaci).
V tomto případě lze do archivu nakopírovat soubory a adresáře, případně existující
označit a smazat. Během interaktivní práce s archivem lze použít i drag&drop pro
operace se soubory a adresáři. Další možností je příkaz Edit, který umožnuje
editovat zvolený soubor asociovaným editorem a následně změněný soubor zabalit
zpět do archivu. Interaktivní přístup má za cíl uživatele maximálně odstínit
od faktu, že pracuje s archivem. Salamander se chová, jako by se jednalo o virtuální
adresář obsahující soubory a adresáře.

Druhý přístup je neinteraktivní. Uživatel nemusí před vybalováním chtít prohlížet
obsah archivu, nebo častěji chce označené soubory na disku zabalit do nového archivu.
Pro tento neinteraktivní přístup slouží příkazy Pack (Alt+F5) a Unpack (Alt+F9).
Uživatel označí soubory na disku a zavolá příkaz Pack. Následuje dialog, kde
si zvolí název cílového archivu (combobox automaticky nabízí alternativní umístění)
a vybere jeden z nabízených packerů. Navíc lze zabalené soubory nechat smazat z disku.
Rozbalování probíhá obdobným způsobem. Uživatel vyhledá archiv a příkazem Unpack otevře
dialog. V něm má možnost nastavit cílovou cestu pro vybalované soubory, zvolit unpacker
(podle přípony se jeden nabídne automaticky). Navíc lze rozbalování omezit pouze na
soubory zadané maskou.

Obecné chování archivátorů má uživatel možnost ovlivnit v konfiguraci Salamandera
(Options / Configuration / Archivers)

Stránka Archivers obsahuje čtyři volby. Use simple icons in archives se vztahuje
k interaktivnímu přístupu, kdy je obsah archivu zobrazen v panelu. Ostatní tři volby
se týkají příkazů Pack a Unpack.

Stránka Archives Associations in Panels slouží k asociaci archivátoru pro
interaktivní režim, tedy otevření archivu v panelu. Seznam obsahuje na
každém řádku jednu nebo více extended masek. Extended masky mají zvláštní
syntaxi a znak # v nich odpovídá libovolné číslici. Ke každému řádku je
přiřazen archivátor (plugin nebo externí) pro read-only operace jako je
listování a rozpakování a zvlášť pro edit operace, jako je zapakování a
mazání z archivu. Pokud uživatel smaže asociaci například pro RAR, bude
se na Enter na .RAR archivu spouštět příslušná aplikace asociovaná ve
Windows. Zároveň může zůstat zachována možnost vytvářet a rozbalovat RAR
archivy pomocí neinteraktivního přístupu, tedy příkazů Pack a Unpack.
Soubory s příponou odpovídající některé z masek na této stránce budou
v panelu zobrazeny se speciální ikonkou archivu.

Stránka External Archivers Locations slouží k nastavení cest k jednotlivým
externím archivátorům.

Stránky Packers in Pack dialog box a Unpackers in Unpack Dialog box slouží
ke konfiguraci archivátorů v neinteraktivním režimu.

Chování jednotlivých archivátorů lze často ovlivnit v jejich pluginové konfiguraci
(Options / Plugins Configuration / název_pluginu).

Dalším místem, kde uživatel přijde do styku s archivátory je Plugins / Plugin Manager.
Po vybrání konkrétního archivátoru budou zobrazeny podporované přípony a funkce.


7. Rozdíly mezi archivátory a pluginy typu file systém
======================================================
Pluginem typu file systém (FS) rozumíme pluginy jako jsou FTP klient, WinSCP/SFTP,
Registry Editor, Windows Mobile. Jejich úloha spočíva v prezentovaní položek
a adresářů v panelu Salamandera. Položky mohou odpovídat souborům (FTP) nebo
se může jednat o hodnoty v Registry. Žádaný je například FS plugin, který by
v panelu zobrazil seznam procesů ve stylu Správce úloh pod Windows.

FS pluginy nemají neinteraktivní přístup jako archivátory přes příkazy Pack a Unpack.

Významný rozdíl mezi archivátory a FS leží pod povrchem a z pohledu uživatele není
zřejmý. Salamander po archivátorech během otevírání archivu v panelu vyžaduje
enumeraci celého listingu archivu, včetně všech podadresářů. Takto získaný listing
si drží Salamander ve vlastních datových strukturách. Jak uživatel prohlíží obsah
archivu a prochází jeho adresáři, Salamander už nevolá archivátor a vše prezentuje
z vlastních dat. Stejná data využije v případě, že uživatel porovnává archiv s
diskem příkazem Compare Directories (Ctrl+F10) nebo v případě výpočtu velikosti
souborů přes podadresáře (Calculate Directories Sizes).

FS pluginy naopak poskytují jen listingy aktuálních cest a to průběžně, jak
uživatel mění cestu v panelu nebo se provádí Refresh obsahu panelu. FS pluginy
by sami měly získávat listingy průběžně, protože většinou je nutné počítat
s tím, že je zároveň mohou měnit třetí osoby (např. obsah FTP serveru mění
zároveň i jiní uživatelé). Případně z důvodu rychlostních optimalizací může
plugin listingy cest ukládat do vlastní cache a vracet je přímo z této cache,
pak přirozeně hrozí neaktuálnost cachovaného listingu.

FS pluginy jsou implementačně řádově komplikovanější než pluginy typu
archivátor. Je to dáno volností, kterou FS plugin má. Díky této volnosti
se však Salamander stává pouze jakýmsi terminálem pluginu, jeho displejem
a klávesnicí. Veškeré služby a logika musí být implementována na úrovni
pluginu. Tomu odpovídá komplikované rozhraní pro FS pluginy proti rozhraní
pro archivátory, které má jen asi deset metod.

Díky jednoduchému rozhraní pro archivátory a jasně vymezeným pravidlům,
jak může vypadat struktura archivu, bylo možné v případě pluginů typu archivátor
přesunout mnoho funkcí do jádra Salamandera. Jedná se o tyto funkce:
-příkaz View
 Jádro si nechá vybalit zvolený soubor do dočasného adresáře a následně
 zavolá klasický příkaz View. Dočasný adresář slouží jako cache a opakovaný
 příkaz View pro stejný soubor už nevyvolá rozpakování z archivu.
-příkaz Edit
 Jádro opět vybalí soubor do dočasného adresáře, a spustí asociovaný editor.
 Ve chvíli, kdy se uživatel pokusí opustit archiv, Salamander vybídne k
 uložení editovaného souboru a nabídne jeho zabalení zpět do archivu.
 Malá odbočka: protože nejsme schopni rozpoznat, kdy editace skončila (nemusí
 jít vždy o ukončení procesu editoru), museli jsme nabídku zpětné komprimace
 přesunout právě na opouštění archivu.
-automatický Refresh
 Během prohlížení archivu Salamander hlídá změny a pokud dojde k aktualizaci
 archivu, je automaticky znovu vylistován a obnoven obsah panelu.
-příkazy pro výpočet velikostí souborů
 Space, Calculate Occupied Space, Calculate Directory Sizes
 Tyto příkazy řeší kompletně jádro a archivátor během nich není volán.
-příkaz Compare Directories
 Díky tomu, že po otevření archivu v panelu zná Salamander jeho kompletní
 strukturu, nebylo problematické implementovat porovnání archivu a adresáře
 na disku nebo dokonce dvou archivů mezi sebou.
-navigace v archivu
 Nad panelem je zobrazena takzvaná Directory Line, která zobrazuje aktuální
 cestu v archivu. Uživatel může myší přeskočit do libovolného nadřazeného
 adresáře tím, že na něj ukáže a klikne. Tato funkce byla velice dobře přijata
 uživateli. Opět stojí na faktu, že Salamander zná obsah prohlíženého archivu.
 V případě FS pluginu je její implementace složitější, protože plugin musí
 jádru říct, co jsou jednotlivé složky cesty. Cesta je tam Salamanderem chápána
 jako řetězec, nikoliv jako stromová struktura a je na FS pluginu, aby identifikoval
 co jsou jednotlivé adresáře.


8. Vrstvená architektura archivátoru a jejich engine
====================================================
Každý archivátor má minimálně dvě vrstvy. Jedna vrstva zodpovídá za komunikaci
s jádrem Salamandera a implementuje pluginovou část rozhraní. Tato vrstva je téměř
shodná pro všechny archivátory. Liší se sadou metod, kterou implementuje (podle
toho, zda je archivátor unpacker nebo i packer).

Druhá vrstva je to, co dělá archivátor unikátním. Jedná se o vlastní engine,
který dokáže otevřít archiv (na úrovni souboru), načíst jeho strukturu a nakrmit
jádro Salamandera daty. V případě packerů je tento engine zodpovědný také
za modifikaci archivů, tedy zápis do nich.

Engine může používat důmyslné pakovací algoritmy nebo se může jednat o triviální
změnu kódování znaku, jako je tomu v případě pluginu UnMIME.

Existuje několik forem jak připojit engine k archivátoru:
-zdrojový kód enginu
-statická knihovna .LIB
-dynamická knihovna .DLL
-jiná forma (například volání OLE)

Záleží na povaze formátu archivu, pro kterou z možností se rozhodneme.
Mnoho formátů je proprietárních a neexistují od nich specifikace. Klasickým
zástupcem je RAR nebo Microsoft CAB. V takovém případě se spoléháme na
knihovnu dodávanou majitelem formátu. V případě RARu je to dynamická
knihovna unrar.dll a v případe MS CABu staticky linkovaná knihovna z
MS CAB SDK. Licence pro tyto knihovny bývají často zcela neomezené, protože
se autoři formátů snaží ukousnout co největší podíl trhu. Většinou se
jedná pouze o rozbalovací části algoritmů.

Engine na úrovni zdrojových kódů lze nejlépe integrovat do archivátoru.
Máme plně v rukách alokace paměti, zobrazování chybových hlášek, přístup
ke všem datům z archivu atd. Pokud však formát archivu není dostatečně
zakonzervovaný a co půl rok přicházejí jeho nové verze, může se tento
přístup snadno stát noční můrou. V takovém případě se snažíme zvolit
spíš měně pružnou DLL nebo LIB knihovnu, kde máme jistotu, že autoři
vydají nové verze reflektující změny ve formátu archivu.


9. Životní cyklus pluginu
=========================

Postup načítání (připojování) pluginu do Salamandera:
  - načtení .SPL modulu pluginu
  - volání funkce SalamanderPluginGetReqVer: ukončení načítání pokud plugin není
    postaven pro verzi Salamandera kompatibilní s právě spuštěným Salamanderem
  - volání SalamanderPluginEntry: ukončení načítání pokud není v .SPL modulu taková
    funkce (export) nebo pokud nevolá SetBasicPluginData nebo pokud vrátí NULL
    (nevrátí rozhraní pluginu, viz CPluginInterface v DemoPlug pluginu)
  - volání metody LoadConfiguration získaného rozhraní pluginu: pokud plugin podporuje
    "load/save configuration" (viz FUNCTION_LOADSAVECONFIGURATION) načte konfiguraci
    z přiděleného soukromého klíče z registry nebo použije defaultní hodnoty (pokud
    "load/save configuration" nepodporuje)
  - volání metody Connect získaného rozhraní pluginu

Postup odpojení pluginu:
  - pokud plugin podporuje "load/save configuration" volá se metoda SaveConfiguration
    získaného rozhraní pluginu
  - volání metody Release získaného rozhraní pluginu: vrátí-li FALSE dostane uživatel
    možnost donutit plugin k odpojení (další volání Release s parametrem force=TRUE)
    (ukončení odpojování pokud uživatel nechce nutit plugin k odpojení)
  - uvolnění (unload) .SPL modulu pluginu


10. Úvod do implementace archivátoru
====================================
Jako zdroj informací a základ pro nový plugin archivátoru poslouží
demonstrační plugin DemoPlug. Pokud byste měli pocit, že by vám lépe
posloužily zdrojové kódy jiného pluginu archivátoru, zkuste nás
kontaktovat.

Salamander se snaží co možná nejdéle pluginy nenačítat do paměti. Motivací je
rychlejší start aplikace a zároveň nižší paměťové nároky. Pro ilustraci např.
ve verzi Salamander 2.5 STANDARD (verze neobsahující velké pluginy Eroiica a
WinSCP) je rozdělení velikostí načítaných modulů 1,9MB (jádro) ku 4,2MB
(pluginy).

Salamander tedy musí o každém pluginu držet základní informace ve své vlastní
konfiguraci. Jde hlavně o jméno pluginu, umístění modulu pluginu (.SPL),
podporované funkce (archivátor, FS, viewer, atd.) a položky přidávané do menu
(včetně ikon). Dále Salamander drží v konfiguraci kde a jak se používají
pluginové funkce. Např. v prioritním seznamu masek pro viewery (viz konfigurační
stránka Viewers) je uvedené pro jaké masky se má používat viewer pluginu,
podobně je uložené pro jaké přípony se používá archivátor pluginu, atd.

Všechna tato data o pluginech se získají při prvním načtení pluginu (instalaci)
do Salamandera a při dalších načteních pluginu je možné tato data upravovat
(viz metoda CPluginInterfaceAbstract::Connect). Úprava je citlivá záležitost,
protože jde o data, která může měnit i uživatel a plugin by tedy neměl uživatele
o jeho změny připravit. Např. uživatel si promaže (nebo doplní) seznam přípon
pro viewer z pluginu nebo promaže přípony archivů, které si přeje zpracovávat
jinou aplikací (např. WinRARem).

V SalamanderPluginEntry při volání SetBasicPluginData plugin určí,
které skupiny funkcí bude podporovat. Nejčastěji to bude kombinace
FUNCTION_PANELARCHIVERVIEW a FUNCTION_CUSTOMARCHIVERUNPACK pro unpackery.
V případě packerů doplněné o FUNCTION_PANELARCHIVEREDIT a
FUNCTION_CUSTOMARCHIVERPACK. Konstanty FUNCTION_PANELARCHIVER(VIEW a
EDIT) patří interaktivnímu přístupu (tedy otevření archivu v panelu),
zatímco konstanty FUNCTION_CUSTOMARCHIVER(PACK a UNPACK) náleží
neinteraktivnímu přístupu (tedy zastoupení pluginu v příkazech Pack
a Unpack).

Dalším důležitým krokem je implementace potomka rozhraní
CPluginInterfaceForArchiverAbstract. Potomka pojmenujme například
CPluginInterfaceForArchiver.

Unpacker implementuje jeho metody ListArchive, UnpackArchive, UnpackOneFile a 
UnpackWholeArchive. Ostatní metody rozhraní budou pouze vracet návratovou hodnotu.
Metody ListArchive, UnpackArchive, UnpackOneFile patří ke konstantě
FUNCTION_PANELARCHIVERVIEW a jsou nezbytné pro interaktivní režim.
Metoda UnpackWholeArchive patří ke konstantě FUNCTION_CUSTOMARCHIVERUNPACK
a bude sloužit pro režim neinteraktivní, tedy bude volána z příkazu Unpack.

Packer musí implementovat navíc metody PackToArchive a DeleteFromArchive pro
interaktivní režim, případně PackToArchiv pro neinteraktivní režim.
PackToArchive je sdílená oběma přístupy.


11. Držení listingu archivu: kontext
====================================
V oblasti držení listingu došlo během vývoje Salamandera k největším změnám.
Staré verze rozhraní CPluginInterfaceForArchiverAbstract neumožnovaly pluginu
elegantně udržovat kontext během volání interaktivních (panelových) metod
ListArchive, UnpackArchive, UnpackOneFile, PackToArchive a DeleteFromArchive.
To je historický důvod, proč do všech metod vstupuje cesta k archivu.

Pod tlakem autorů archivátorů postupně dostaly tyto metody parametr
CPluginDataInterfaceAbstract *pluginData, který lze (mimo jiné) použít
právě pro držení kontextu.

Starý bezkontextový přístup je stále podporovaný a autor archivátoru se
může rozhodnout, zda kontext využije.

Kontextem rozumíme nějaký objekt, který drží informace potřebné během
přítomnosti archivu v panelu, tedy při interaktivním přístupu. Později
si ukážeme, jak lze kontext využít v případě neinteraktivního příkazu
Unpack. Kontext může například obsahovat heslo k šifrovaným souborům
archivu nebo informace vyčtené z hlavičky archivu během jeho otevírání.

Kontext vzniká během volání metody ListArchive, využije se v metodách
UnpackArchive, UnpackOneFile, PackToArchive a DeleteFromArchive. Kontext
zaniká, když uživatel mění cestu v panelu z archivu ven. Tehdy jádro
Salamandera zavolá CPluginInterfaceAbstract::ReleasePluginDataInterface.
Existuje jedna výjimka, kdy ReleasePluginDataInterface není zavoláno při
opuštění archivu. Ta nastane pokud uživatel nakopíroval některé soubory
nebo adresáře do schránky (Ctrl+C) a Salamander pak odloží uvolnění dat
na příkaz Paste.

POZOR: nepoužívat CPluginInterfaceForArchiverAbstract::CanCloseArchive 
pro rušení kontextu. I v případě, že z metody CanCloseArchive vrátíme
TRUE, archiv může zůstat otevřený v panelu, protože se nemusí podařit
vylistovat novou cestu.

Jak pracovat s kontextem:
1) Při otevírání archivu v panelu jádro Salamandera volá metodu pluginu
   ListArchive. Ta je zodpovědná za kompletní enumeraci archivu a předání
   takto získaného listingu jádru Salamandera prostřednictvím parametru 
   CSalamanderDirectoryAbstract *dir. 
   
   Vytvoříme objekt-kontext, v případě pluginu UnFAT půjde o instanci
   třídy CFATImage, která zapouzdřuje práci s FAT 12, 16 a 32 obrazem.

   Ukazatel na kontext uložíme do potomka rozhraní CPluginDataInterfaceAbstract,
   kterého pojmenujme například CPluginDataInterface. Zatím se nebudeme
   zabývat implementací metod tohoto rozhraní a pouze vrátíme návratovou
   hodnotu ve smyslu OK.
   
   class CPluginDataInterface: public CPluginDataInterfaceAbstract
   {
     protected:
       CFATImage *FATImage; // ukazatel na kontext
 
     public:
       CPluginDataInterface(CFATImage *fatImage)
       {
         FATImage = fatImage;
       }
       
       ~CPluginDataInterface()
       {
         if (FATImage != NULL)
         {
           delete (FATImage);
           FATImage = NULL;
         }
       }

       .... // prazdna implementace metod CPluginDataInterfaceAbstract
 
       CFATImage *GetFATImage() {return FATImage;}
   };
   
   V metodě ListArchive alokujeme instanci objektu CPluginDataInterface
   a vrátíme jí přes parametr CPluginDataInterfaceAbstract *&pluginData
   jádru Salamandera:
   
   CFATImage *img = new CFATImage();           // alokujeme kontext
   pluginData = new CPluginDataInterface(img); // schovame si ho do pluginData
   img->Open(fileName);                        // nechame kontext otevrit archiv
   img->ListImage(dir);                        // pozadame o listing archivu
   return TRUE;                                // a nezapomene osetrit vsechny chybove stavy ;-)
    
2) Pokud jádro Salamandera nyní zavolá kteroukoliv z metod interaktivních (panelových)
   metod UnpackArchive, UnpackOneFile, PackToArchive nebo DeleteFromArchive, předá v
   parametru 'pluginData' ukazatel na náš alokovaný objekt, v kterém máme uložen ukazatel
   na kontext.
   
   Pro vytažení ukazatele je třeba přetypování ukazatele z CPluginDataInterfaceAbstract: 
   CFATImage *img = ((CPluginDataInterface*)pluginData)->GetFATImage();

3) V okamžiku kdy uživatel opustí archiv (změnou cesty v panelu) zavolá Salamander metodu
   pluginu, ve které provedeme destrukci kontextu.

   void 
   CPluginInterface::ReleasePluginDataInterface(CPluginDataInterfaceAbstract *pluginData)
   {
     delete ((CPluginDataInterface *)pluginData);
   }

Kontext lze použít také v neinteraktivním přístupu, tedy při implementaci metody UnpackWholeArchive:

  CPluginInterfaceForArchiver::UnpackWholeArchive(CSalamanderForOperationsAbstract *salamander, 
                                                  const char *fileName, const char *mask,
                                                  const char *targetDir, BOOL delArchiveWhenDone,
                                                  CDynamicString *archiveVolumes)
  {
    // do teto docasne promenne nechame naplnit obsah archivu
    CSalamanderDirectoryAbstract *dir = SalamanderGeneral->AllocSalamanderDirectory(FALSE);

    // drzak ukazatele na kontext
    CPluginDataInterfaceAbstract *pluginData = NULL;
    if (ListArchive(salamander, fileName, dir, pluginData))
    {
      // v 'dir' mame vsechny polozky archivu a muzeme provest extrakci...
    
      // na zaver po sobe uklidime
      PluginInterface.ReleasePluginDataInterface(pluginData);
    }
    SalamanderGeneral->FreeSalamanderDirectory(dir);
  }

O proměnné CFileData::PluginData
Metoda CFATImage::ListImage, která je zodpovědná za enumeraci položek archivu,
použije svůj parametr CSalamanderDirectoryAbstract *dir k předání listing jádru
Salamandera. Slouží k tomu metody CSalamanderDirectoryAbstract::AddFile
a ::AddDir. Informace o souborech a adresářích se do těchto metod předávají
pomocí struktury CFileData. Zajímavá je její proměnná CFileData::PluginData,
kterou plugin může využít pro uložení svých 'custom dat' k jednotlivým souborům
a adresářům. Proměnná existuje výhradně pro potřeby pluginu a jádro Salamandera
nijak nerozumí jejímu obsahu.

Proměnná CFileData::PluginData může být prostý DWORD např. s prvním sektorem
souboru ve FAT obrazu nebo se může jednat např. o ukazatel na alokovaný objekt.
V tomto případě byl takový objekt alokován v metodě ListArchive a je třeba
jej při opouštění archivu dealokovat. Protože Salamander nezná význam
CFileData::PluginData, zeptá se pluginu prostřednictvím metod
CPluginDataInterfaceAbstract::CallReleaseForFiles a ::CallReleaseForDirs
zda má pro každý soubor a adresář zavolat metodu ::ReleasePluginData.
Tím umožní pluginu dealokovat CFileData::PluginData.

Popsané přístupy se z pohledu archivátorů možná jeví poněkud neohrabaně, což
je dáno jejich současným využitím v pluginech typu file system.


12. Optimalizované listování archivu
====================================
Jádro Salamandera obdrží od pluginu listing archivu prostřednictvím metod
CSalamanderDirectoryAbstract::AddFile a ::AddDir volaných v metodě
ListArchive. Plugin předává jádru adresáře a soubory s plnou cestou. Jádro
Salamandera si je však rozhazuje do stromové struktury, ve které je pak
drží.

Zařazení příchozího souboru nebo adresáře do interní stromové struktury znamená
rozklad plné cesty a prohledávání stromu. Tento overhead se snažíme minimalizovat
optimalizací, kterou je však třeba podpořit na straně pluginu.

Optimalizace je postavena na cachování posledně přidávané plné cesty a jí 
odpovídajícímu místu v interní stromové struktuře. Cache je implementována
na úrovni metod AddFile a AddDir a z hlediska pluginu je transparentní.

Pokud se má cache uplatnit, musí plugin přidávat soubory a adresáře z jednoho
adresáře v rámci jedné dávky. Adresáře a soubory mohou být v rámci této dávky
na přeskáčku, důležité je jen jejich umístění na stejné cestě.

Optimální přístup z hlediska cache je tedy přidání všech souborů a adresářů
v rámci jedné dávky a teprve následný sestup do podadresářů. Pro tyto účely lze
během přidávání adresářů budovat jejich dočasný seznam, který se po rekurzivním
sestupu dealokuje.


13. Vrstva Input/Output
=======================
Vstupně výstupní operace se soubory by se měly v maximální možné míře
blížit implementaci v jádře Salamandera. Jedná se především o ošetření
chybových stavů. Každá IO operace má tři fáze: otevření souboru,
čtení/zápis a na závěr jeho zavření. Během prvních dvou fází může dojít
k celé škále chyb: nedostupná cesta, nedostatečná práva, málo místa
na disku, chyby výměnných médii a další.

Všechny chybové stavy tedy musí být detekovány a zobrazeny.

Nabízí se nám několik rodin funkcí, které problematiku čtení a zápisu řeší:
-Memory Mapped Files: CreateFileMapping, MapViewOfFile, UnmapViewOfFile
-Klasické Win32 API: ReadFile, WriteFile
-C Run-Time Library: fread, fwrite
-Jiné, například COM interface IStream

Pokud jako engine archivátoru použijeme existující zdrojové kódy, pravděpodobně
nebudeme měnit jejich I/O vrstvu. Pokud však programujeme plugin od základů, je
rozhodnutí na nás. Následuje několik postřehů k jednotlivým přístupům.

Memory Mapped Files (MMF)
-------------------------
(+) Umožují mapovat soubor přímo do adresního prostoru procesu. K souboru se následně
přistupuje pouhým čtením nebo zápisem paměti.

(+) Jeden soubor může být současně mapován do paměti více procesů. To je jediný
rozumný způsob, jak mohou dva procesy pod Windows NT sdílet paměť. Tato technika
se používá například v případě DLL knihoven, kde OS jejich sdílené segmenty zavede
do fyzické paměti pouze jednou a procesům je namapuje do jejich adresního prostoru.
To je mimochodem dobrý důvod, proč běžně nepoužívat in-process kompresory typu UPX.

(+) Pokud aplikace přistupuje k souboru bajt za bajtem, má přístup do MMF mnohem
menší overhead než volání fread nebo ReadFile pro velikost bufferu několi bajtů.

(-) Každý MMF si ukousne část systémových prostředků. Tento argument však nelze
použít proti MMF, protože pod Windows NT jsou klasické API ReadFile/WriteFile
řešeny kernelem právě přes MMF.

(-) Starý argument, že MMF se skvěle hodí pro random přístup do velkých souborů
je dnes lichý, protože soubory nad 4GB jsou zcela běžné a nám nezbývá než k nim
přistupovat mapováním po částech, stejně jako při klasickém čtení/zápisu.

(-) POZOR, toto je největší nástraha MMF, kterou se Microsoft neobtěžoval v MSDN slušně
zdokumentovat. Z podstaty MMF nelze ošetřit chybové stavy (například vadný sektor na médiu,
přerušní síťové vrstvy) v místě jejich vzniku, tedy přístupu do mapované paměti.
Pokud k takové chybě dojde, je generována exception. Pokud ji proces nechytne do
try/except bloku, aplikace spadne.

Jediná zastrčená poznámka v MSDN na toto téma je v Using File Mapping/Reading and Writing:
> Note Reading from or writing to a file view can cause an exception. For example,
> accessing a mapped file that resides on a remote server can generate an exception
> if the connection to the server is lost. Exceptions can also occur because of a full
> disk, because the file is shared and a different process has locked a byte range,
> or because of an underlying device failure or memory allocation failure. To guard
> against exceptions due to input and output (I/O) errors, all attempts to access memory
> mapped files should be wrapped in structured exception handlers.
MSDN zapomíná zmínit, jaké exceptions mohou nastat a co tedy máme chytat.

(*) Asi jste vytušili, že MMF zrovna nemilujeme. Pokud máte něco na jejich obhajobu,
dejte nám vědět.

Win32 API: ReadFile, WriteFile
------------------------------
(+) Jasně definované a snadno ošetřitelné chybové stavy. Z toho plynoucí možnost
zapouzdřit I/O operace do knihovny, která bude řešit Retry/Skip all/Overwrite all
stavy, viz interface CSalamanderSafeFileAbstract.

(+) Pod Windows NT virtuálně shodný výkon s MMF, protože kernel interně převádí 
volání ReadFile a WriteFile právě na MMF.

(-) Při čtení/zápisu po malých segmentech paměti (do desítek bajtů) vzniká velice
nepříjemný overhead ve srovnání s RTL fread/fwrite, kde existuje na úrovni RTL
defaultní 16 KB I/O cache. Tento nedostatek lze řešit čtením po větších blocích,
zkrátka cache posunout do aplikace. V ALTAPu se nám tento problém projevil při
čtení a zápisu souboru na síťovém disku, kde prohloubil overhedem síťové vrstvy.
Zavedení malé I/O cache problém odstranilo a rychlost čtení a zápisu se řádově
zrychlila.

Run-Time Library: fread, fwrite
-------------------------------
(+) Jasně definované a snadno ošetřitelné chybové stavy.

(+) Zabudovaná cache má dobrý dopad při čtení a zápisu po malých částech paměti.

(-) Nekompatibilita s Win32 API. RTL funkce sice interně volají API, ale na venek
mají ANSI C interface. Mají tedy jiné návratové hodnoty, jiné konvence pro
zpracování chybových stavů. Navíc nezpřístupňují aplikaci všechny parametry
API funkcí. Některé API nemají v této rodině funkcí ekvivalenty a jejich zavolání
se stává problémem, protože nemáme potřebný handle souboru.

Závěr
-----
Pokud si můžete vybrat, zvolte API funkce ReadFile a WriteFile. Pokud potřebujete
číst nebo zapisovat po několika bajtech, zaveďte si I/O cache a obejděte tak
zbytečný overhead, který se může drasticky projevit na síťových discích 
(můžete si to snadno ověřit experimentem na vaší lokální síti).


14. Ošetření vstupů a výstupů: CSalamanderSafeFileAbstract    
==========================================================
Pluginy Salamandera by se komfortem nabízených funkcí a robustností jejich zpracování
měly co nejvíce blížit provedení jádra Salamandera a jeho základních operací, jako
jsou Copy, Move, Delete, Rename atd. V ideální případě by uživatel neměl pozorovat
ztrátu komfortu při použití funkcí z pluginů proti jejich protějškům v jádře
Salamandera.

V případě archivátorů se jedná především o ošetření vstupně výstupních operací.
Pokud během otevírání, čtení nebo zápisu souboru dojde k chybě, existují tři
scénáře, jak problém řešit:
(1) chybu nezobrazit, ale korektně zpracovat, tedy vycouvat z operace ven
(2) zobrazit chybu a nabídnout tlačítko OK, které přeruší prováděnou operaci
(3) zobrazit chybu a nabídnout tlačítka jako Retry, Skip, Skip All, Cancel
    (různé jejich kombinace viz konstanty BUTTONS_xxx)

Scénář (1) je vlastně tichou variantou scénáře (2) a využíváme ho pouze pro
speciální případy. Například archivátor kombinovaný s viewerem, kde je pro
vieweru potřeba implementovat metodu CPluginInterfaceForViewerAbstract::CanViewFile.
Tato metoda se pokusí otevřít, případně zkusmo vylistovat archiv a pokud nastane
nějaký problém, tiše vrátí FALSE. 

Scénář (2) nasazujeme na místech, kde by scénář (3) neměl smysl. Pokud například
uživatel stiskne Enter v panelu, aby vstoupil do archivu (případně dá příkaz Unpack),
je nesmysl zobrazit pod chybové hlášení "Error Opening File"/"(5) Access is denied"
tlačítka Retry/Cancel. Sem patří pouze tlačítko OK, protože uživatel akci snadno
po odstranění problému zopakuje. Pod tento scénář zároveň patří prohlížení a spouštění
souborů z archivu. Salamander v tomto případě volá metodu pluginu
CPluginInterfaceForArchiverAbstract::UnpackOneFile.

Scénář (3) se uplatní všude tam, kde přerušení operace komplikuje uživatelům život.
Jde například o případ, kdy uživatel vybaluje archiv obsahující stovky souborů
a někde uprostřed dojde místo na cílovém disku. Pokud bychom pod chybové hlášení
"Error Writing File"/"(112) There is not enough space on the disk." zobrazili pouze
tlačítko OK, nedáme uživateli možnost problém odstranit a operaci dokončit. Taková
chyba by měla mít tlačítka Retry/Skip/Skip all/Cancel. Stejná situace nastane,
pokud během pakování velkého množství souborů nemůžeme některý soubor přečíst.
Pokud v chybové hlášce nenabídneme tlačítko Skip/Skip all, bude celá operace
znehodnocena.

Ve většině případů tedy uplatníme scénář (3) s maximálním komfortem pro uživatele.
Pokud (3) nemá smysl, použijeme zjednodušený scénář (2). Ve speciálních případech
použijeme tichý scénář (1).

Kvalitní ošetření chybových stavů I/O operací není úplně triviální a značně
znepřehledňuje zbytek kódu (vnořené cykly, případně tvrdé skoky pro Retry).
Z tohoto důvodu jsme zavedli sadu funkcí řešící nejčastější problémy a sdružili
je pod rozhraní CSalamanderSafeFileAbstract. Funkce to nejsou nijak pěkné
(ani uvnitř, ani díky množství parametrů), ale problém řeší. Podrobnosti
k jejich použití najdete v komentářích v souboru spl_file.h. 
Do rozhraní jsme přestěhovali i starou a ošklivou metodu SafeCreateFile.

Pokud budete I/O operace řešit vlastní silou, rádi dodáme zdrojové kódy, kde je
vidět řešení různých nástrah, na které jsme přišli během provozu Salamandera.
Například metody SafeFileRead a SafeFileWrite v případě chyby a stisku tlačítka
Retry zavírají handle souboru a pokoušejí se ho znovu otevřít. Důvodem jsou
chyby na síti, kdy i po obnovení přetrženého spojení není starý handle funkční
a jeho zavření a nové otevření je jediná možnost, jak na operaci navázat.
Samozřejmě to znamená spravné nastavení ukazovátka v souboru atd.

Několik pravidel k zobrazovaným chybám. (Pokud použijete pro práci se soubory
rozhraní CSalamanderSafeFileAbstract, dostanete ošetření většiny chyb zdarma.)

-Pokud vznikne chyba, snažíme se jí správně pojmenovat. Například nepíšeme 
 suché "Error", ale "Error Reading File".
 
-Pokud je k dispozici hodnota z API GetLastError, zobrazujeme ji před chybovou
 hláškou (Error: (5) Access is denied.). Pokud nám například italský uživatel
 pošle screenshot nebo přepis chybového hlášení v italštině, dokážeme z číselné
 reprezentace zjistit, o jakou chybu se jedná.
 
-Chyby se snažíme zobrazovat ve stejné formě, v jaké je zobrazuje Salamander
 během jeho vestavěných operací (pokud Salamander takové precedenty má).
 Většinou tedy nepoužijeme obyčejný MessageBox, ale mocnější DialogError, 
 DialogOverwrite nebo DialogQuestion. Tyto dialogy mimo jiné řeší zkrácení
 příliš dlouhého názvu souboru, který by MessageBox natáhnul.
 
-Snažíme se zobrazit název souboru, ve kterém k chybě došlo. Mnoho operací
 pracuje minimálně nad dvěma soubory (zdroj a cíl), takže pojmenování souboru
 v chybovém hlášení usnadní uživatelům navigaci.

-Pokud chyba vznikla v důsledku nekompatibilního formátu (například jsme v
 archivu nenašli centrální adresář nebo je daný soubor zabalen neznámou kompresní
 metodou nebo data prostě nejsou tam kde by být měla), opět se snažíme problém 
 přesně pojmenovat místo suché hlášky "Error". Ušetří nám to hromadu vrásek 
 v budoucnosti, až nám uživatelé chyby začnou posílat zpět.

Pokud si během implementace nebudete jistí, jak by se plugin v dané situaci měl
chovat, zkuste navodit obdobnou situaci během některé z vestavěných operací 
Salamandera. Případně nás kontaktujte a rozhodneme se jak situaci řešit.


15. Minimalizace fragmentování disku během zápisu souborů
=========================================================
Soubory nejsou na disku většinou uloženy vcelku, ale po částech. Tento jev
se označuje jako fragmentace souborů a má nepříznivý dopad na rychlost diskových
operací. K fragmentaci dochází z podstaty souborového systému: na disku vzniknou
postupně soubory A, B1, C; potom soubor B1 smažeme a zůstane po něm díra, která 
je využitá nově vznikajícím souborem B2. Ten je však větší a co se nevejde do díry
po B1, bude připojeno do volného prostoru za souborem C. 

Pokud má být fragmentovaný soubor B2 následně načten do paměti, musí disk
po načtení první části vystavit hlavičky do části druhé. Tím se snižuje
rychlost čtení. Operační systémy se snaží problém fragmentace rešit softy,
jako je Disk Defragmentator pod W2K. Tyto utility přeskupí soubory tak, 
aby byly uloženy na disku vcelku. Disk Defragmentator dokáže na tlačítko
Analyze posoudit míru fragmentace souborů, volného místa a zobrazit seznam
nejfragmentovanějších souborů (včetně počtu fragmentů). 

Počet fragmentů souborů lze zobrazit také pomocí naší utilitky fragment.exe,
která je k dispozici na vývojářské stránce. Rozumný počet fragmentů vytvářeného
souboru je jeden nebo dva. Zkuste případně příkaz Copy v Salamanderu 2.5 nebo
novějším. Měli byste se na daném disku dostat na stejný počet fragmentů.

Klasický způsob ukládání dat do souboru vypadá takto:
  CreateFile    // vytvorime/prepiseme soubor
    WriteFile   // zapiseme buffer
    WriteFile   // zapiseme buffer
    ...
    WriteFile   // zapiseme posledni cast dat
  CloseFile

Operační systém předem nezná finální velikost vznikajícího souborů, takže
se řídí velikostí zapisovaného bufferu předávaného do funkce WriteFile. Postupně
se tak vyplňují díry mezi soubory. Při běžném provozu disku je volné místo
často fragmentované a nově vznikající soubor tedy bude také fragmentovaný. 
Salamander do verze 2.5 během nakopírovaní 700MB souboru na disk běžně
vytvořil soubor složený z tisíce fragmentů. Problém se projevoval také během
zápisu velkých souborů na disketu nebo jiné pomalé médium. Pokud soubor zapisujeme
například po 60KB blocích, musí OS po zapsání každého bloku přemístit hlavičku do
FAT a zanést informaci o nově alokovaných clusterech. Pokud pomineme devastaci
mechaniky, je tato metoda velmi pomalá a hlučná.

Pokud předem známe výslednou velikost souboru, můžeme použít
LEPŠÍ ZPŮSOB zápisu souboru:

  CreateFile              // vytvorime/prepiseme soubor
    SetFilePointer(END)   // ukazovatko nastavime na konec souboru
    SetEndOfFile          // nechame OS nafouknout soubor
    SetFilePointer(BEGIN) // ukazovatko vratime na zacatek souboru
    WriteFile             // zapiseme buffer
    WriteFile             // zapiseme buffer
    ...
    WriteFile             // zapiseme posledni cast dat
  CloseFile

OS nám pomoci funkce SetFilePointer umožňuji vystavit ukazovátko i do míst
"za koncem souboru". Funkce SetEndOfFile pak slouží jako commit, kterým požádáme
systém, aby pro soubor zabral volné clustery a provedl zápis do FAT nebo
jejího ekvivalentu. Protože je známa výsledná velikost, může OS provést
alokaci volného místa efektivněji a výsledný soubor bude minimálně
fragmentovaný (za předpokladu, že existuje dostatek nefragmentovaného volného
místa, což vetšinou platí).

Popsaná technika má několik nástrah proti běžnému způsobu zápisu:
(1) Soubor se po celou dobu zápisu jeví jako kompletní, což může zmást uživatele.
(2) Pokud během zápisu dojde k chybě nebo přerušení operace uživatelem, je potřeba 
    zapisovaný soubor zkrátit na úroveň zapsaných dat nebo na nulovou velikost, 
    protože jinak OS uloží data od aktualního ukazovátka do konce souboru. Dobře
    viditelné to je na disketách, kde po zavolání CloseHandle systém ještě patnáct
    vteřin ukládal "náhodná" data, aby vyplnil předalokovaný soubor. V případě
    predčasného ukončení tedy voláme:
      SetEndOfFile(hFile);                        // zkratime soubor na zapsanou velikost
      CloseHandle(hFile)                          // nyni uz ho muzeme zavrit
(3) SetEndOfFile techniku nemá smysl použít v případě, že výsledná velikost souboru
    je menší nebo rovna velikosti bufferu pro zápis.
(4) Máme zdokumentovaný případ, kdy na síťovém disku neprojde operace
    SetFilePointer(BEGIN) a následně zapsaná data ze připojí na konec souboru.
    Tento problém proto explicitně testujeme na prvním souboru dávky a pokud ho detekujeme,
    zakážeme SetEndOfFile optimalizaci pro všechny soubory v této várce (například
    rozbalované soubory směřující do jedné destinace).
(5) Pokud na cílovém disku není dostatek místa, metoda selže a stejně jako ve (4)
    zapisujeme klasickou postupnou metodou.
    
Technika předalokace místa ve vytvářeném souboru je podpořena rozhraním
CSalamanderSafeFileAbstract, viz funkce SafeFileCreate a její parametr 'allocateWholeFile'. 
Použití je vidět v pluginu UnFAT. Pokud se rozhodnete pro vlastní implementaci, požádejte
si o zdrojové kódy od CSalamanderSafeFileAbstract.


16. Čeho se vyvarovat během implementace archivátoru
====================================================
Pozor na globální proměnné, které by časem mohly být překážkou při rozšiřování
architektury pluginů. Jedná se například o současné spouštění metod pluginů, které
by způsobovalo přepisování takových globálních proměnných. 

Kontext archivu držíme navázaný na CPluginDataInterfaceAbstract, takže tady nebezpečí
nehrozí. Pokud bychom kontext drželi v globální proměnné, narazíme již v současné
verzi Salamandera, protože jeden archivátor může být současně zastoupen v levém i
pravém panelu a docházelo by ke kolizi. 

Funkce plnící a vracející řetězec nenecháme vracet ukazatel na statický buffer,
ale řetězec do nich předáme jako parametr, společně s jeho maximálním počtem
znaků.


17. Testování archivátorů
=========================
Jak rostl počet implementací pluginu typu archivátor, naráželi jsme během jejich
testování stále na stejnou skupinu problémů. Tyto nejčastější problémy jsme popsali
v souboru checklist.txt, aby je mohl snadno odstranit přímo autor pluginu.
