DEADSOFTWARE

network: it should find correct files on POSIX now (and substitute extensions too)
[d2df-sdl.git] / src / sfs / sfs.pas
1 // streaming file system (virtual)
2 {$MODE DELPHI}
3 {.$R-}
4 {.$DEFINE SFS_VOLDEBUG}
5 unit sfs;
7 interface
9 uses
10 SysUtils, Classes, Contnrs;
13 type
14 ESFSError = class(Exception);
16 TSFSChar = AnsiChar;
17 TSFSString = AnsiString;
19 TSFSVolume = class;
21 TSFSFileInfo = class
22 public
23 fOwner: TSFSVolume; // òàê, íà âñÿêèé ñëó÷àé
24 fPath: TSFSString; // ðàçäåëèòåëè êàòàëîãîâ -- "/"; êîðåíü íèêàê íå îáîçíà÷åí, åñëè íå ïóñòîå, îáÿçàíî çàâåðøàåòñÿ "/"
25 fName: TSFSString; // òîëüêî èìÿ
26 fSize: Int64; // unpacked
27 fOfs: Int64; // in VFS (many of 'em need this %-)
29 constructor Create (pOwner: TSFSVolume);
30 destructor Destroy (); override;
32 property path: TSFSString read fPath;
33 property name: TSFSString read fName;
34 property size: Int64 read fSize;
35 end;
37 // âèðòóàëüíàÿ ôàéëîâàÿ ñèñòåìà. ÒÎËÜÊÎ ÄËß ×ÒÅÍÈß!
38 // òîì ÍÅ ÄÎËÆÅÍ óáèâàòüñÿ íèêàê èíà÷å, ÷åì ïðè ïîìîùè ôàáðèêè!
39 TSFSVolume = class
40 protected
41 fRC: Integer; // refcounter for other objects
42 fFileName: TSFSString;// îáû÷íî èìÿ îðèãèíàëüíîãî ôàéëà
43 fFileStream: TStream; // îáû÷íî ïîòîê äëÿ ÷òåíèÿ îðèãèíàëüíîãî ôàéëà
44 fFiles: TObjectList; // TSFSFileInfo èëè íàñëåäíèêè
46 // ïðèøèáèòü âñå ñòðóêòóðû.
47 // íå äîëæíà ïàäàòü, åñëè å¸ âûçûâàþò íåñêîëüêî ðàç.
48 procedure Clear (); virtual;
50 // âûçûâàåòñÿ èç DoDirectoryRead() äëÿ çàïîëíåíèÿ ñïèñêà ôàéëîâ.
51 // ñ÷èòàåòñÿ, ÷òî âñå ìàãèêè óæå ïðîâåðåíû è ôàéë òî÷íî íàø.
52 // fFileName, fFileStream óæå óñòàíîâëåíû, fFiles ñîçäàí,
53 // â í¸ì, ñêîðåå âñåãî, íèêîãî íåò.
54 // ïîçèöèÿ ïîòîêà -- òà, ÷òî îñòàâèëà ôàáðèêà.
55 // ïðè îøèáêàõ êèäàòü èñêëþ÷åíèå, òîãäà òîì áóäåò ïðèáèò ôàáðèêîé.
56 // ðàçäåëèòåëè ïóòåé äîëæíû áûòü òîëüêî "/", êîðíåâîé "/" äîëæåí
57 // áûòü îïóùåí, ïóòè (åñëè íå ïóñòûå) äîëæíû çàâåðøàòüñÿ "/"!
58 // fName äîëæíî ñîäåðæàòü òîëüêî èìÿ, fPath -- òîëüêî ïóòü.
59 // â ïðèíöèïå, îá ýòîì ïîçàáîòèòñÿ DoDirectoryRead(), íî çà÷åì
60 // äàâàòü åìó ëèøíþþ ðàáîòó?
61 procedure ReadDirectory (); virtual; abstract;
63 // íàéòè ôàéë, âåðíóòü åãî èíäåêñ â fFiles.
64 // ýòà ïðîöåäóðà ìîæåò ìåíÿòü fFiles!
65 // fPath -- â ïðàâèëüíîé ôîðìå, ñ "/", êîðíåâîé "/" óáèò, ôèíàëüíûé äîáàâëåí.
66 // åñëè ôàéë íå íàéäåí, âåðíóòü -1.
67 function FindFile (const fPath, fName: TSFSString): Integer; virtual;
69 // âîçâðàùàåò êîëè÷åñòâî ôàéëîâ â fFiles
70 function GetFileCount (): Integer; virtual;
72 // âîçâðàùàåò ôàéë ñ èíäåêñîì index.
73 // ìîæåò âîçâðàùàòü NIL.
74 // íèêàêèõ ïàäåíèé íà íåïðàâèëüíûå èíäåêñû!
75 function GetFiles (index: Integer): TSFSFileInfo; virtual;
77 procedure removeCommonPath (); virtual;
79 public
80 // pSt íå îáÿçàòåëüíî çàïîìèíàòü, åñëè îí íå íóæåí.
81 constructor Create (const pFileName: TSFSString; pSt: TStream); virtual;
82 // fFileStream óíè÷òîæàòü íåëüçÿ, åñëè îí ðàâåí ïàðàìåòðó pSt êîíñòðóêòîðà.
83 destructor Destroy (); override;
85 // âûçûâàåò ReadDirectory().
86 // ýòà ïðîöåäóðà ñàìà ðàçáåð¸òñÿ ñ äóáëèêàòàìè èì¸í: ïîäîáàâëÿåò â
87 // êîíåö èì¸í-äóáëèêàòîâ ïîä÷¸ðêèâàíèå è äåñÿòè÷íûé íîìåð.
88 // òàêæå îíà íîðìàëèçóåò âèä èì¸í.
89 procedure DoDirectoryRead ();
91 // ïðè îøèáêàõ êèäàòüñÿ èñêëþ÷åíèÿìè.
92 function OpenFileByIndex (const index: Integer): TStream; virtual; abstract;
94 // åñëè íå ñìîãëî îòêóïîðèòü ôàéëî (èëè åù¸ ãäå îøèáëîñü), çàøâûðí¸ò èñêëþ÷åíèå.
95 function OpenFileEx (const fName: TSFSString): TStream; virtual;
97 property FileCount: Integer read GetFileCount; // ìîæåò âåðíóòü íîëü
98 // ìîæåò âîçâðàùàòü NIL.
99 // íèêàêèõ ïàäåíèé íà íåïðàâèëüíûå èíäåêñû!
100 property Files [index: Integer]: TSFSFileInfo read GetFiles;
101 end;
103 // ôàáðèêà òîìîâ. âñå SFS ïðè ñòàðòå äîáàâëÿþò ñâîè ôàáðèêè.
104 // áëàãîäàðÿ ýòîìó ìîæíî ñîçäàâàòü ðàçíûå âñÿêèå SFS ñòàíäàðòíûì
105 // âûçîâîì ñòàíäàðòíîé ïðîöåäóðû.
106 // ôàáðèêà ÍÅ ÄÎËÆÍÀ óáèâàòüñÿ íèêàê èíà÷å, ÷åì ïðè ïîìîùè âûçîâà
107 // SFSUnregisterVolumeFactory()! ýòî ãàðàíòèðóåò, ÷òî äâèæîê
108 // ïåðåä ðàññòðåëîì îòäàñò åé âñå å¸ òîìà.
109 TSFSVolumeFactory = class
110 public
111 // åñëè äîáàâëÿåì ôàéë äàííûõ ôàéë ñ èìåíåì òèïà "zip:....", òî
112 // SFS èçâëå÷¸ò ýòî "zip" è ïåðåäàñò â ñèþ ôóíêöèþ.
113 // åæåëè ôóíêöèÿ âåðí¸ò ïðàâäó, òî SFS âûçîâåò Produce äëÿ äàííîãî
114 // ôàéëà. åñëè íè îäíà ôàáðèêà ïðåôèêñ íå ïðèçíàåò, òî ôàéë íå îòêðîþò.
115 // èñïîëüçóåòñÿ äëÿ ñêèïàíèÿ àâòîäåòåêòà.
116 // SFS ÍÅ Ñ×ÈÒÀÅÒ ÏÐÅÔÈÊÑÎÌ ÑÒÐÎÊÓ ÊÎÐÎ×Å ÒÐ¨Õ ÑÈÌÂÎËÎÂ!
117 function IsMyVolumePrefix (const prefix: TSFSString): Boolean; virtual; abstract;
118 // ïðîâåðÿåò, ìîæåò ëè ôàáðèêà ñäåëàòü òîì äëÿ äàííîãî ôàéëà.
119 // st -- îòêðûòûé äëÿ ÷òåíèÿ ôàéëîâé ïîòîê. óêàçàòåëü ÷òåíèÿ ñòîèò â íà÷àëå.
120 // ýòîò ïîòîê íåëüçÿ çàêðûâàòü!
121 // prefix: òî, ÷òî áûëî ïåðåäàíî â IsMyVolumePrefix() èëè ''.
122 // èñêëþ÷åíèå ñ÷èòàåòñÿ îøèáêîé, âîçâðàò NIL ñ÷èòàåòñÿ îøèáêîé.
123 function Produce (const prefix, fileName: TSFSString; st: TStream): TSFSVolume; virtual; abstract;
124 // êîãäà òîì áîëüøå íå íóæåí, îí áóäåò îòäàí ôàáðèêå íà ïåðåðàáîòêó.
125 // äàëåå äâèæîê íå áóäåò þçàòü ñåé òîì.
126 procedure Recycle (vol: TSFSVolume); virtual; abstract;
127 end;
129 // "èòåðàòîð", âîçâðàùàåìûé SFSFileList()
130 TSFSFileList = class
131 protected
132 fVolume: TSFSVolume;
134 function GetCount (): Integer;
135 function GetFiles (index: Integer): TSFSFileInfo;
137 public
138 constructor Create (const pVolume: TSFSVolume);
139 destructor Destroy (); override;
141 property Volume: TSFSVolume read fVolume;
142 property Count: Integer read GetCount;
143 // ïðè íåïðàâèëüíîì èíäåêñå ìîë÷à âåðí¸ò NIL.
144 // ïðè ïðàâèëüíîì òîæå ìîæåò âåðíóòü NIL!
145 // î÷åíü íå ñîâåòóþ ìåíÿòü ñîäåðæèìîå ïîëó÷åííîãî êëàññà.
146 // êîíå÷íî, ÿ ìîã áû âîçâðàùàòü íîâóþ ñòðóêòóðó èëè íå÷òî ïîõîæåå,
147 // íî áëèí, åñëè òû èäèîò è íå óìååøü äàæå êîììåíòû ÷èòàòü, òî
148 // êàêîãî òû âîîáùå â ïðîãðàììèíã ïîëåç?
149 property Files [index: Integer]: TSFSFileInfo read GetFiles; default;
150 end;
153 procedure SFSRegisterVolumeFactory (factory: TSFSVolumeFactory);
154 // ýòà ôóíêöèÿ àâòîìàòè÷åñêè ïðèáü¸ò factory.
155 procedure SFSUnregisterVolumeFactory (factory: TSFSVolumeFactory);
157 // äîáàâèòü ñáîðíèê â ïîñòîÿííûé ñïèñîê.
158 // åñëè ñáîðíèê ñ òàêèì èìåíåì óæå îòêðûò, òî íå îòêðûâàåò åãî ïîâòîðíî.
159 // íèêîãäà íå êèäàåò èñêëþ÷åíèé.
160 // top: äîáàâèòü â íà÷àëî ñïèñêà ïîèñêà.
161 // âåðí¸ò ëîæü ïðè îøèáêå.
162 // ñïîñîáíî îòêðûâàòü ñáîðíèêè â ñáîðíèêàõ ïðè ïîìîùè êðóòûõ èì¸í a-la:
163 // "zip:pack0::pack:pack1::wad2:pack2".
164 // â äàëüíåéøåì ñëåäóåò îáðàùàòüñÿ ê ñáîðíèêó êàê "pack2::xxx".
165 // èëè ìîæíî íàïèñàòü:
166 // "zip:pack0::pack:pack1::wad2:pack2|datafile".
167 // è îáðàùàòüñÿ êàê "datafile::xxx".
168 // "||" ïðåîáðàçóþòñÿ â ïðîñòîé "|" è ðàçäåëèòåëåì íå ñ÷èòàþòñÿ.
169 // ïðèíèìàåòñÿ âî âíèìàíèå òîëüêî ïîñëåäíÿÿ òðóáà.
170 function SFSAddDataFile (const dataFileName: TSFSString; top: Boolean=false): Boolean;
172 // äîáàâèòü ñáîðíèê âðåìåííî
173 function SFSAddDataFileTemp (const dataFileName: TSFSString; top: Boolean=false): Boolean;
175 // äîáàâèòü â ïîñòîÿííûé ñïèñîê ñáîðíèê èç ïîòîêà ds.
176 // åñëè âîçâðàùàåò èñòèíó, òî SFS ñòàíîâèòñÿ âëÿäåëüöåì ïîòîêà ds è ñàìà
177 // óãðîáèò ñåé ïîòîê ïî íåîáõîäèìîñòè.
178 // virtualName ñòàíîâèòñÿ èìåíåì ñáîðíèêà äëÿ îïåðàöèè îòêðûòèÿ ôàéëà òèïà
179 // "packfile:file.ext".
180 // åñëè êàêîé-íèáóäü ñáîðíèê ñ èìåíåì virtualName óæå îòêðûò, âåðí¸ò false.
181 // íèêîãäà íå êèäàåò èñêëþ÷åíèé.
182 // top: äîáàâèòü â íà÷àëî ñïèñêà ïîèñêà.
183 // âåðí¸ò ëîæü ïðè îøèáêå.
184 // îòêðûâàåò ñáîðíèê èç ïîòîêà. dataFileName -- ÂÈÐÒÓÀËÜÍÎÅ èìÿ.
185 // ò.å. íà ñàìîì äåëå òàêîãî ôàéëà ìîæåò è íå áûòü íà äèñêå.
186 function SFSAddSubDataFile (const virtualName: TSFSString; ds: TStream; top: Boolean=false): Boolean;
188 // øâûðÿåòñÿ èñêëþ÷åíèÿìè.
189 // åñëè fName íå èìååò óêàçàíèÿ íà ôàéë äàííûõ (ýòî òî, ÷òî îòäåëåíî îò
190 // îñòàëüíîãî èìåíè äâîåòî÷èåì), òî èùåì ñíà÷àëà ïî âñåì çàðåãèñòðèðîâàííûì
191 // ôàéëàì äàííûõ, ïîòîì â òåêóùåì êàòàëîãå, ïîòîì â êàòàëîãå, îòêóäà ñòàðòîâàëè.
192 // åñëè íè÷åãî íå íàøëè, êèäàåì èñêëþ÷åíèå.
193 function SFSFileOpenEx (const fName: TSFSString): TStream;
195 // ïðè îøèáêå -- NIL, è íèêàêèõ èñêëþ÷åíèé.
196 function SFSFileOpen (const fName: TSFSString): TStream;
198 // âîçâðàùàåò NIL ïðè îøèáêå.
199 // ïîñëå èñïîëüçîâàíèÿ, íàòóðàëüíî, èòåðàòîð íàäî ãðîõíóòü %-)
200 function SFSFileList (const dataFileName: TSFSString): TSFSFileList;
202 // çàïðåòèòü îñâîáîæäåíèå âðåìåííûõ òîìîâ (ìîæíî âûçûâàòü ðåêóðñèâíî)
203 procedure sfsGCDisable ();
205 // ðàçðåøèòü îñâîáîæäåíèå âðåìåííûõ òîìîâ (ìîæíî âûçûâàòü ðåêóðñèâíî)
206 procedure sfsGCEnable ();
208 // for completeness sake
209 procedure sfsGCCollect ();
212 function SFSReplacePathDelims (const s: TSFSString; newDelim: TSFSChar): TSFSString;
213 // èãíîðèðóåò ðåãèñòð ñèìâîëîâ
214 function SFSStrEqu (const s0, s1: TSFSString): Boolean;
216 // ðàçîáðàòü òîëñòîå èìÿ ôàéëà, âåðíóòü âèðòóàëüíîå èìÿ ïîñëåäíåãî ñïèñêà
217 // èëè ïóñòóþ ñòîðîêó, åñëè ñïèñêîâ íå áûëî.
218 function SFSGetLastVirtualName (const fn: TSFSString): string;
220 // ïðåîáðàçîâàòü ÷èñëî â ñòðîêó, êðàñèâî ðàçáàâëÿÿ çàïÿòûìè
221 function Int64ToStrComma (i: Int64): string;
223 // `name` will be modified
224 // return `true` if file was found
225 function sfsFindFileCI (path: string; var name: string): Boolean;
227 // Wildcard matching
228 // this code is meant to allow wildcard pattern matches. tt is VERY useful
229 // for matching filename wildcard patterns. tt allows unix grep-like pattern
230 // comparisons, for instance:
231 //
232 // ? Matches any single characer
233 // + Matches any single characer or nothing
234 // * Matches any number of contiguous characters
235 // [abc] Matches a or b or c at that position
236 // [!abc] Matches anything but a or b or c at that position
237 // [a-e] Matches a through e at that position
238 //
239 // 'ma?ch.*' -Would match match.exe, mavch.dat, march.on, etc
240 // 'this [e-n]s a [!zy]est' -Would match 'this is a test', but would
241 // not match 'this as a yest'
242 //
243 function WildMatch (pattern, text: TSFSString): Boolean;
244 function WildListMatch (wildList, text: TSFSString; delimChar: AnsiChar=':'): Integer;
245 function HasWildcards (const pattern: TSFSString): Boolean;
247 function SFSUpCase (ch: Char): Char;
249 function utf8to1251 (s: TSFSString): TSFSString;
252 var
253 // ïðàâäà: ðàçðåøåíî èñêàòü ôàéëî íå òîëüêî â ôàéëàõ äàííûõ, íî è íà äèñêå.
254 sfsDiskEnabled: Boolean = true;
255 // ïðàâäà: åñëè ôàéë íå ïðåôèêñîâàí, òî ñíà÷àëà èùåì ôàéëî íà äèñêå,
256 // ïîòîì â ôàéëàõ äàííûõ.
257 sfsDiskFirst: Boolean = true;
258 // ïðàâäà: äàæå äëÿ ïðåôèêñîâàíûõ ôàéëîâ ñíà÷àëà ïðîñìîòðèì äèñê
259 // (åñëè óñòàíîâëåí ôëàæîê sfsDiskFirst è sfsDiskEnabled).
260 sfsForceDiskForPrefixed: Boolean = false;
261 // ñïèñîê äèñêîâûõ êàòàëîãîâ äëÿ ïîèñêà ôàéëà. åñëè ïóñò -- èùåì òîëüêî â
262 // òåêóùåì. êàòàëîãè ðàçäåëÿþòñÿ òðóáîé ("|").
263 // <currentdir> çàìåíÿåòñÿ íà òåêóùèé êàòàëîã (ñ çàâåðøàþùèì "/"),
264 // <exedir> çàìåíÿåòñÿ íà êàòàëîã, ãäå ñèäèò .EXE (ñ çàâåðøàþùèì "/").
265 sfsDiskDirs: TSFSString = '<currentdir>|<exedir>';
268 implementation
270 uses
271 xstreams;
274 function Int64ToStrComma (i: Int64): string;
275 var
276 f: Integer;
277 begin
278 Str(i, result);
279 f := Length(result)+1;
280 while f > 4 do
281 begin
282 Dec(f, 3); Insert(',', result, f);
283 end;
284 end;
287 // `name` will be modified
288 function sfsFindFileCI (path: string; var name: string): Boolean;
289 var
290 sr: TSearchRec;
291 bestname: string = '';
292 begin
293 if length(path) = 0 then path := '.';
294 while (length(path) > 0) and (path[length(path)] = '/') do Delete(path, length(path), 1);
295 if (length(path) = 0) or (path[length(path)] <> '/') then path := path+'/';
296 if FileExists(path+name) then begin result := true; exit; end;
297 if FindFirst(path+'*', faAnyFile, sr) = 0 then
298 repeat
299 if (sr.name = '.') or (sr.name = '..') then continue;
300 if (sr.attr and faDirectory) <> 0 then continue;
301 if sr.name = name then
302 begin
303 FindClose(sr);
304 result := true;
305 exit;
306 end;
307 if (length(bestname) = 0) and SFSStrEqu(sr.name, name) then bestname := sr.name;
308 until FindNext(sr) <> 0;
309 FindClose(sr);
310 if length(bestname) > 0 then begin result := true; name := bestname; end else result := false;
311 end;
314 const
315 // character defines
316 WILD_CHAR_ESCAPE = '\';
317 WILD_CHAR_SINGLE = '?';
318 WILD_CHAR_SINGLE_OR_NONE = '+';
319 WILD_CHAR_MULTI = '*';
320 WILD_CHAR_RANGE_OPEN = '[';
321 WILD_CHAR_RANGE = '-';
322 WILD_CHAR_RANGE_CLOSE = ']';
323 WILD_CHAR_RANGE_NOT = '!';
326 function HasWildcards (const pattern: TSFSString): Boolean;
327 begin
328 result :=
329 (Pos(WILD_CHAR_ESCAPE, pattern) <> 0) or
330 (Pos(WILD_CHAR_SINGLE, pattern) <> 0) or
331 (Pos(WILD_CHAR_SINGLE_OR_NONE, pattern) <> 0) or
332 (Pos(WILD_CHAR_MULTI, pattern) <> 0) or
333 (Pos(WILD_CHAR_RANGE_OPEN, pattern) <> 0);
334 end;
336 function MatchMask (const pattern: TSFSString; p, pend: Integer; const text: TSFSString; t, tend: Integer): Boolean;
337 var
338 rangeStart, rangeEnd: AnsiChar;
339 rangeNot, rangeMatched: Boolean;
340 ch: AnsiChar;
341 begin
342 // sanity checks
343 if (pend < 0) or (pend > Length(pattern)) then pend := Length(pattern);
344 if (tend < 0) or (tend > Length(text)) then tend := Length(text);
345 if t < 1 then t := 1;
346 if p < 1 then p := 1;
347 while p <= pend do
348 begin
349 if t > tend then
350 begin
351 // no more text. check if there's no more chars in pattern (except "*" & "+")
352 while (p <= pend) and
353 ((pattern[p] = WILD_CHAR_MULTI) or
354 (pattern[p] = WILD_CHAR_SINGLE_OR_NONE)) do Inc(p);
355 result := (p > pend);
356 exit;
357 end;
358 case pattern[p] of
359 WILD_CHAR_SINGLE: ;
360 WILD_CHAR_ESCAPE:
361 begin
362 Inc(p);
363 if p > pend then result := false else result := (pattern[p] = text[t]);
364 if not result then exit;
365 end;
366 WILD_CHAR_RANGE_OPEN:
367 begin
368 result := false;
369 Inc(p); if p > pend then exit; // sanity check
370 rangeNot := (pattern[p] = WILD_CHAR_RANGE_NOT);
371 if rangeNot then begin Inc(p); if p > pend then exit; {sanity check} end;
372 if pattern[p] = WILD_CHAR_RANGE_CLOSE then exit; // sanity check
373 ch := text[t]; // speed reasons
374 rangeMatched := false;
375 repeat
376 if p > pend then exit; // sanity check
377 rangeStart := pattern[p];
378 if rangeStart = WILD_CHAR_RANGE_CLOSE then break;
379 Inc(p); if p > pend then exit; // sanity check
380 if pattern[p] = WILD_CHAR_RANGE then
381 begin
382 Inc(p); if p > pend then exit; // sanity check
383 rangeEnd := pattern[p]; Inc(p);
384 if rangeStart < rangeEnd then
385 begin
386 rangeMatched := (ch >= rangeStart) and (ch <= rangeEnd);
387 end
388 else rangeMatched := (ch >= rangeEnd) and (ch <= rangeStart);
389 end
390 else rangeMatched := (ch = rangeStart);
391 until rangeMatched;
392 if rangeNot = rangeMatched then exit;
394 // skip the rest or the range
395 while (p <= pend) and (pattern[p] <> WILD_CHAR_RANGE_CLOSE) do Inc(p);
396 if p > pend then exit; // sanity check
397 end;
398 WILD_CHAR_SINGLE_OR_NONE:
399 begin
400 Inc(p);
401 result := MatchMask(pattern, p, pend, text, t, tend);
402 if not result then result := MatchMask(pattern, p, pend, text, t+1, tend);
403 exit;
404 end;
405 WILD_CHAR_MULTI:
406 begin
407 while (p <= pend) and (pattern[p] = WILD_CHAR_MULTI) do Inc(p);
408 result := (p > pend); if result then exit;
409 while not result and (t <= tend) do
410 begin
411 result := MatchMask(pattern, p, pend, text, t, tend);
412 Inc(t);
413 end;
414 exit;
415 end;
416 else result := (pattern[p] = text[t]); if not result then exit;
417 end;
418 Inc(p); Inc(t);
419 end;
420 result := (t > tend);
421 end;
424 function WildMatch (pattern, text: TSFSString): Boolean;
425 begin
426 if pattern <> '' then pattern := AnsiLowerCase(pattern);
427 if text <> '' then text := AnsiLowerCase(text);
428 result := MatchMask(pattern, 1, -1, text, 1, -1);
429 end;
431 function WildListMatch (wildList, text: TSFSString; delimChar: AnsiChar=':'): Integer;
432 var
433 s, e: Integer;
434 begin
435 if wildList <> '' then wildList := AnsiLowerCase(wildList);
436 if text <> '' then text := AnsiLowerCase(text);
437 result := 0;
438 s := 1;
439 while s <= Length(wildList) do
440 begin
441 e := s; while e <= Length(wildList) do
442 begin
443 if wildList[e] = WILD_CHAR_RANGE_OPEN then
444 begin
445 while (e <= Length(wildList)) and (wildList[e] <> WILD_CHAR_RANGE_CLOSE) do Inc(e);
446 end;
447 if wildList[e] = delimChar then break;
448 Inc(e);
449 end;
450 if s < e then
451 begin
452 if MatchMask(wildList, s, e-1, text, 1, -1) then exit;
453 end;
454 Inc(result);
455 s := e+1;
456 end;
457 result := -1;
458 end;
461 type
462 TVolumeInfo = class
463 fFactory: TSFSVolumeFactory;
464 fVolume: TSFSVolume;
465 fPackName: TSFSString; // äëÿ îäíîãî è òîãî æå ôàéëà áóäåò òîëüêî îäèí òîì!
466 fStream: TStream; // ôàéëîâûé ïîòîê äëÿ ñáîðíèêà
467 fPermanent: Boolean; // èñòèíà -- íå áóäåò óãðîáëåíà, åñëè íå îñòàíåòñÿ íè îäíîãî îòêðûòîãî òîìà
468 // èñòèíà -- ýòîò òîì áûë ñîçäàí èç ïîòîêà è íå èìååò äèñêîâîãî ôàéëà, ïîòîìó ôàáðèêå áóäåò ïåðåäàíî íå èìÿ ñáîðíèêà, à ïóñòàÿ ñòðîêà
469 fNoDiskFile: Boolean;
470 fOpenedFilesCount: Integer;
472 destructor Destroy (); override;
473 end;
475 TOwnedPartialStream = class (TSFSPartialStream)
476 protected
477 fOwner: TVolumeInfo;
479 public
480 constructor Create (pOwner: TVolumeInfo; pSrc: TStream; pPos, pSize: Int64; pKillSrc: Boolean);
481 destructor Destroy (); override;
482 end;
485 var
486 factories: TObjectList; // TSFSVolumeFactory
487 volumes: TObjectList; // TVolumeInfo
488 gcdisabled: Integer = 0; // >0: disabled
491 procedure sfsGCCollect ();
492 var
493 f, c: Integer;
494 vi: TVolumeInfo;
495 used: Boolean;
496 begin
497 // collect garbage
498 f := 0;
499 while f < volumes.Count do
500 begin
501 vi := TVolumeInfo(volumes[f]);
502 if vi = nil then continue;
503 if (not vi.fPermanent) and (vi.fVolume.fRC = 0) and (vi.fOpenedFilesCount = 0) then
504 begin
505 // this volume probably can be removed
506 used := false;
507 c := volumes.Count-1;
508 while not used and (c >= 0) do
509 begin
510 if (c <> f) and (volumes[c] <> nil) then
511 begin
512 used := (TVolumeInfo(volumes[c]).fStream = vi.fStream);
513 if not used then used := (TVolumeInfo(volumes[c]).fVolume.fFileStream = vi.fStream);
514 if used then break;
515 end;
516 Dec(c);
517 end;
518 if not used then
519 begin
520 {$IFDEF SFS_VOLDEBUG}writeln('000: destroying volume "', TVolumeInfo(volumes[f]).fPackName, '"');{$ENDIF}
521 volumes.extract(vi); // remove from list
522 vi.Free; // and kill
523 f := 0;
524 continue;
525 end;
526 end;
527 Inc(f); // next volume
528 end;
529 end;
531 procedure sfsGCDisable ();
532 begin
533 Inc(gcdisabled);
534 end;
536 procedure sfsGCEnable ();
537 begin
538 Dec(gcdisabled);
539 if gcdisabled <= 0 then
540 begin
541 gcdisabled := 0;
542 sfsGCCollect();
543 end;
544 end;
547 // ðàçáèòü èìÿ ôàéëà íà ÷àñòè: ïðåôèêñ ôàéëîâîé ñèñòåìû, èìÿ ôàéëà äàííûõ,
548 // ñîáñòâåííî èìÿ ôàéëà
549 // èìÿ âûãëÿäèò êàê:
550 // (("sfspfx:")?"datafile::")*"filename"
551 procedure SplitFName (const fn: string; out dataFile, fileName: string);
552 var
553 f: Integer;
554 begin
555 f := Length(fn)-1;
556 while f >= 1 do
557 begin
558 if (fn[f] = ':') and (fn[f+1] = ':') then break;
559 Dec(f);
560 end;
561 if f < 1 then begin dataFile := ''; fileName := fn; end
562 else
563 begin
564 dataFile := Copy(fn, 1, f-1);
565 fileName := Copy(fn, f+2, maxInt-10000);
566 end;
567 end;
569 // ñàéäýôôåêò: âûðåçàåò âèðòóàëüíîå èìÿ èç dataFile.
570 function ExtractVirtName (var dataFile: string): string;
571 var
572 f: Integer;
573 begin
574 f := Length(dataFile); result := dataFile;
575 while f > 1 do
576 begin
577 if dataFile[f] = ':' then break;
578 if dataFile[f] = '|' then
579 begin
580 if dataFile[f-1] = '|' then begin Dec(f); Delete(dataFile, f, 1); end
581 else
582 begin
583 result := Copy(dataFile, f+1, Length(dataFile));
584 Delete(dataFile, f, Length(dataFile));
585 break;
586 end;
587 end;
588 Dec(f);
589 end;
590 end;
592 // ðàçáèòü èìÿ ñáîðíèêà íà ÷àñòè: ïðåôèêñ ôàéëîâîé ñèñòåìû, èìÿ ôàéëà äàííûõ,
593 // âèðòóàëüíîå èìÿ. åñëè âèðòóàëüíîãî èìåíè íå äàíî, îíî áóäåò ðàâíî dataFile.
594 // èìÿ âûãëÿäèò êàê:
595 // [sfspfx:]datafile[|virtname]
596 // åñëè ïåðåä äâîåòî÷èåì ìåíüøå òð¸õ áóêâ, òî ýòî ñ÷èòàåòñÿ íå ïðåôèêñîì,
597 // à èìåíåì äèñêà.
598 procedure SplitDataName (const fn: string; out pfx, dataFile, virtName: string);
599 var
600 f: Integer;
601 begin
602 f := Pos(':', fn);
603 if f <= 3 then begin pfx := ''; dataFile := fn; end
604 else
605 begin
606 pfx := Copy(fn, 1, f-1);
607 dataFile := Copy(fn, f+1, maxInt-10000);
608 end;
609 virtName := ExtractVirtName(dataFile);
610 end;
612 // íàéòè ïðîèçâîäèòåëÿ äëÿ ýòîãî ôàéëà (åñëè ôàéë óæå îòêðûò).
613 // onlyPerm: òîëüêî "ïîñòîÿííûå" ïðîèçâîäèòåëè.
614 function FindVolumeInfo (const dataFileName: TSFSString; onlyPerm: Boolean=false): Integer;
615 var
616 f: Integer;
617 vi: TVolumeInfo;
618 begin
619 f := 0;
620 while f < volumes.Count do
621 begin
622 if volumes[f] <> nil then
623 begin
624 vi := TVolumeInfo(volumes[f]);
625 if not onlyPerm or vi.fPermanent then
626 begin
627 if SFSStrEqu(vi.fPackName, dataFileName) then
628 begin
629 result := f;
630 exit;
631 end;
632 end;
633 end;
634 Inc(f);
635 end;
636 result := -1;
637 end;
639 // íàéòè èíôó äëÿ ýòîãî òîìà.
640 // õîðîøåå èìÿ, ïðàâäà? %-)
641 function FindVolumeInfoByVolumeInstance (vol: TSFSVolume): Integer;
642 begin
643 result := volumes.Count-1;
644 while result >= 0 do
645 begin
646 if volumes[result] <> nil then
647 begin
648 if TVolumeInfo(volumes[result]).fVolume = vol then exit;
649 end;
650 Dec(result);
651 end;
652 end;
654 function SFSUpCase (ch: Char): Char;
655 begin
656 if ch < #128 then
657 begin
658 if (ch >= 'a') and (ch <= 'z') then Dec(ch, 32);
659 end
660 else
661 begin
662 if (ch >= #224) and (ch <= #255) then
663 begin
664 Dec(ch, 32);
665 end
666 else
667 begin
668 case ch of
669 #184, #186, #191: Dec(ch, 16);
670 #162, #179: Dec(ch);
671 end;
672 end;
673 end;
674 result := ch;
675 end;
677 function SFSStrEqu (const s0, s1: TSFSString): Boolean;
678 var
679 i: Integer;
680 begin
681 //result := (AnsiCompareText(s0, s1) == 0);
682 result := false;
683 if length(s0) <> length(s1) then exit;
684 for i := 1 to length(s0) do
685 begin
686 if SFSUpCase(s0[i]) <> SFSUpCase(s1[i]) then exit;
687 end;
688 result := true;
689 end;
691 // adds '/' too
692 function normalizePath (fn: string): string;
693 var
694 i: Integer;
695 begin
696 result := '';
697 i := 1;
698 while i <= length(fn) do
699 begin
700 if (fn[i] = '.') and ((length(fn)-i = 0) or (fn[i+1] = '/') or (fn[i+1] = '\')) then
701 begin
702 i := i+2;
703 continue;
704 end;
705 if (fn[i] = '/') or (fn[i] = '\') then
706 begin
707 if (length(result) > 0) and (result[length(result)] <> '/') then result := result+'/';
708 end
709 else
710 begin
711 result := result+fn[i];
712 end;
713 Inc(i);
714 end;
715 if (length(result) > 0) and (result[length(result)] <> '/') then result := result+'/';
716 end;
718 function SFSReplacePathDelims (const s: TSFSString; newDelim: TSFSChar): TSFSString;
719 var
720 f: Integer;
721 begin
722 result := s;
723 for f := 1 to Length(result) do
724 begin
725 if (result[f] = '/') or (result[f] = '\') then
726 begin
727 // avoid unnecessary string changes
728 if result[f] <> newDelim then result[f] := newDelim;
729 end;
730 end;
731 end;
733 function SFSGetLastVirtualName (const fn: TSFSString): string;
734 var
735 rest, tmp: string;
736 f: Integer;
737 begin
738 rest := fn;
739 repeat
740 f := Pos('::', rest); if f = 0 then f := Length(rest)+1;
741 tmp := Copy(rest, 1, f-1); Delete(rest, 1, f+1);
742 result := ExtractVirtName(tmp);
743 until rest = '';
744 end;
747 { TVolumeInfo }
748 destructor TVolumeInfo.Destroy ();
749 var
750 f, me: Integer;
751 used: Boolean; // ôëàæîê çàþçàíîñòè ïîòîêà êåì-òî åù¸
752 begin
753 if fFactory <> nil then fFactory.Recycle(fVolume);
754 if fVolume <> nil then used := (fVolume.fRC <> 0) else used := false;
755 fVolume := nil;
756 fFactory := nil;
757 fPackName := '';
759 // òèïà ìóñîðîñáîðíèê: åñëè íàø ïîòîê áîëåå íèêåì íå þçàåòñÿ, òî óãðîáèòü åãî íàôèã
760 if not used then
761 begin
762 me := volumes.IndexOf(self);
763 f := volumes.Count-1;
764 while not used and (f >= 0) do
765 begin
766 if (f <> me) and (volumes[f] <> nil) then
767 begin
768 used := (TVolumeInfo(volumes[f]).fStream = fStream);
769 if not used then
770 begin
771 used := (TVolumeInfo(volumes[f]).fVolume.fFileStream = fStream);
772 end;
773 if used then break;
774 end;
775 Dec(f);
776 end;
777 end;
778 if not used then FreeAndNil(fStream); // åñëè áîëüøå íèêåì íå þçàíî, ïðèøèá¸ì
779 inherited Destroy();
780 end;
783 { TOwnedPartialStream }
784 constructor TOwnedPartialStream.Create (pOwner: TVolumeInfo; pSrc: TStream;
785 pPos, pSize: Int64; pKillSrc: Boolean);
786 begin
787 inherited Create(pSrc, pPos, pSize, pKillSrc);
788 fOwner := pOwner;
789 if pOwner <> nil then Inc(pOwner.fOpenedFilesCount);
790 end;
792 destructor TOwnedPartialStream.Destroy ();
793 var
794 f: Integer;
795 begin
796 inherited Destroy();
797 if fOwner <> nil then
798 begin
799 Dec(fOwner.fOpenedFilesCount);
800 if (gcdisabled = 0) and not fOwner.fPermanent and (fOwner.fOpenedFilesCount < 1) then
801 begin
802 f := volumes.IndexOf(fOwner);
803 if f <> -1 then
804 begin
805 {$IFDEF SFS_VOLDEBUG}writeln('001: destroying volume "', TVolumeInfo(volumes[f]).fPackName, '"');{$ENDIF}
806 volumes[f] := nil; // this will destroy the volume
807 end;
808 end;
809 end;
810 end;
813 { TSFSFileInfo }
814 constructor TSFSFileInfo.Create (pOwner: TSFSVolume);
815 begin
816 inherited Create();
817 fOwner := pOwner;
818 fPath := '';
819 fName := '';
820 fSize := 0;
821 fOfs := 0;
822 if pOwner <> nil then pOwner.fFiles.Add(self);
823 end;
825 destructor TSFSFileInfo.Destroy ();
826 begin
827 if fOwner <> nil then fOwner.fFiles.Extract(self);
828 inherited Destroy();
829 end;
832 { TSFSVolume }
833 constructor TSFSVolume.Create (const pFileName: TSFSString; pSt: TStream);
834 begin
835 inherited Create();
836 fRC := 0;
837 fFileStream := pSt;
838 fFileName := pFileName;
839 fFiles := TObjectList.Create(true);
840 end;
842 procedure TSFSVolume.removeCommonPath ();
843 begin
844 end;
846 procedure TSFSVolume.DoDirectoryRead ();
847 var
848 f, c: Integer;
849 sfi: TSFSFileInfo;
850 tmp: TSFSString;
851 begin
852 fFileName := ExpandFileName(SFSReplacePathDelims(fFileName, '/'));
853 ReadDirectory();
854 fFiles.Pack();
856 f := 0;
857 while f < fFiles.Count do
858 begin
859 sfi := TSFSFileInfo(fFiles[f]);
860 // normalize name & path
861 sfi.fPath := SFSReplacePathDelims(sfi.fPath, '/');
862 if (sfi.fPath <> '') and (sfi.fPath[1] = '/') then Delete(sfi.fPath, 1, 1);
863 if (sfi.fPath <> '') and (sfi.fPath[Length(sfi.fPath)] <> '/') then sfi.fPath := sfi.fPath+'/';
864 tmp := SFSReplacePathDelims(sfi.fName, '/');
865 c := Length(tmp); while (c > 0) and (tmp[c] <> '/') do Dec(c);
866 if c > 0 then
867 begin
868 // split path and name
869 Delete(sfi.fName, 1, c); // cut name
870 tmp := Copy(tmp, 1, c); // get path
871 if tmp = '/' then tmp := ''; // just delimiter; ignore it
872 sfi.fPath := sfi.fPath+tmp;
873 end;
874 sfi.fPath := normalizePath(sfi.fPath);
875 if (length(sfi.fPath) = 0) and (length(sfi.fName) = 0) then sfi.Free else Inc(f);
876 end;
877 removeCommonPath();
878 end;
880 destructor TSFSVolume.Destroy ();
881 begin
882 Clear();
883 FreeAndNil(fFiles);
884 inherited Destroy();
885 end;
887 procedure TSFSVolume.Clear ();
888 begin
889 fRC := 0; //FIXME
890 fFiles.Clear();
891 end;
893 function TSFSVolume.FindFile (const fPath, fName: TSFSString): Integer;
894 begin
895 if fFiles = nil then result := -1
896 else
897 begin
898 result := fFiles.Count;
899 while result > 0 do
900 begin
901 Dec(result);
902 if fFiles[result] <> nil then
903 begin
904 if SFSStrEqu(fPath, TSFSFileInfo(fFiles[result]).fPath) and
905 SFSStrEqu(fName, TSFSFileInfo(fFiles[result]).fName) then exit;
906 end;
907 end;
908 result := -1;
909 end;
910 end;
912 function TSFSVolume.GetFileCount (): Integer;
913 begin
914 if fFiles = nil then result := 0 else result := fFiles.Count;
915 end;
917 function TSFSVolume.GetFiles (index: Integer): TSFSFileInfo;
918 begin
919 if fFiles = nil then result := nil
920 else
921 begin
922 if (index < 0) or (index >= fFiles.Count) then result := nil
923 else result := TSFSFileInfo(fFiles[index]);
924 end;
925 end;
927 function TSFSVolume.OpenFileEx (const fName: TSFSString): TStream;
928 var
929 fp, fn: TSFSString;
930 f, ls: Integer;
931 begin
932 fp := fName;
933 // normalize name, find split position
934 if (fp <> '') and ((fp[1] = '/') or (fp[1] = '\')) then Delete(fp, 1, 1);
935 ls := 0;
936 for f := 1 to Length(fp) do
937 begin
938 if fp[f] = '\' then fp[f] := '/';
939 if fp[f] = '/' then ls := f;
940 end;
941 fn := Copy(fp, ls+1, Length(fp));
942 fp := Copy(fp, 1, ls);
943 f := FindFile(fp, fn);
944 if f = -1 then raise ESFSError.Create('file not found: "'+fName+'"');
945 result := OpenFileByIndex(f);
946 if result = nil then raise ESFSError.Create('file not found: "'+fName+'"');
947 end;
950 { TSFSFileList }
951 constructor TSFSFileList.Create (const pVolume: TSFSVolume);
952 var
953 f: Integer;
954 begin
955 inherited Create();
956 ASSERT(pVolume <> nil);
957 f := FindVolumeInfoByVolumeInstance(pVolume);
958 ASSERT(f <> -1);
959 fVolume := pVolume;
960 Inc(TVolumeInfo(volumes[f]).fOpenedFilesCount); // íå ïîçâîëèì óáèòü çàïèñü!
961 end;
963 destructor TSFSFileList.Destroy ();
964 var
965 f: Integer;
966 begin
967 f := FindVolumeInfoByVolumeInstance(fVolume);
968 ASSERT(f <> -1);
969 if fVolume <> nil then Dec(fVolume.fRC);
970 Dec(TVolumeInfo(volumes[f]).fOpenedFilesCount);
971 // óáü¸ì çàïèñü, åñëè îíà âðåìåííàÿ, è â íåé íåò áîëüøå íè÷åãî îòêðûòîãî
972 if (gcdisabled = 0) and not TVolumeInfo(volumes[f]).fPermanent and (TVolumeInfo(volumes[f]).fOpenedFilesCount < 1) then
973 begin
974 {$IFDEF SFS_VOLDEBUG}writeln('002: destroying volume "', TVolumeInfo(volumes[f]).fPackName, '"');{$ENDIF}
975 volumes[f] := nil;
976 end;
977 inherited Destroy();
978 end;
980 function TSFSFileList.GetCount (): Integer;
981 begin
982 result := fVolume.fFiles.Count;
983 end;
985 function TSFSFileList.GetFiles (index: Integer): TSFSFileInfo;
986 begin
987 if (index < 0) or (index >= fVolume.fFiles.Count) then result := nil
988 else result := TSFSFileInfo(fVolume.fFiles[index]);
989 end;
992 procedure SFSRegisterVolumeFactory (factory: TSFSVolumeFactory);
993 var
994 f: Integer;
995 begin
996 if factory = nil then exit;
997 if factories.IndexOf(factory) <> -1 then
998 raise ESFSError.Create('duplicate factories are not allowed');
999 f := factories.IndexOf(nil);
1000 if f = -1 then factories.Add(factory) else factories[f] := factory;
1001 end;
1003 procedure SFSUnregisterVolumeFactory (factory: TSFSVolumeFactory);
1004 var
1005 f: Integer;
1006 c: Integer;
1007 begin
1008 if factory = nil then exit;
1009 f := factories.IndexOf(factory);
1010 if f = -1 then raise ESFSError.Create('can''t unregister nonexisting factory');
1011 c := 0; while c < volumes.Count do
1012 begin
1013 if (volumes[c] <> nil) and (TVolumeInfo(volumes[c]).fFactory = factory) then volumes[c] := nil;
1014 Inc(c);
1015 end;
1016 factories[f] := nil;
1017 end;
1020 function SFSAddDataFileEx (dataFileName: TSFSString; ds: TStream; top, permanent: Integer): Integer;
1021 // dataFileName ìîæåò èìåòü ïðåôèêñ òèïà "zip:" (ñì. âûøå: IsMyPrefix).
1022 // ìîæåò âûêèíóòü èñêëþ÷åíèå!
1023 // top:
1024 // <0: äîáàâèòü â íà÷àëî ñïèñêà ïîèñêà.
1025 // =0: íå ìåíÿòü.
1026 // >0: äîáàâèòü â êîíåö ñïèñêà ïîèñêà.
1027 // permanent:
1028 // <0: ñîçäàòü "âðåìåííûé" òîì.
1029 // =0: íå ìåíÿòü ôëàæîê ïîñòîÿíñòâà.
1030 // >0: ñîçäàòü "ïîñòîÿííûé" òîì.
1031 // åñëè ds <> nil, òî ñîçäà¸ò ñáîðíèê èç ïîòîêà. åñëè ñáîðíèê ñ èìåíåì
1032 // dataFileName óæå çàðåãèñòðèðîâàí, òî ïàäàåò íàôèã.
1033 // âîçâðàùàåò èíäåêñ â volumes.
1034 // óìååò äåëàòü ðåêóðñèþ.
1035 var
1036 fac: TSFSVolumeFactory;
1037 vol: TSFSVolume;
1038 vi: TVolumeInfo;
1039 f: Integer;
1040 st, st1: TStream;
1041 pfx: TSFSString;
1042 fn, vfn, tmp: TSFSString;
1043 begin
1044 f := Pos('::', dataFileName);
1045 if f <> 0 then
1046 begin
1047 // ðåêóðñèâíîå îòêðûòèå.
1048 // ðàçîáü¸ì dataFileName íà èìÿ ñáîðíèêà è îñòàòîê.
1049 // pfx áóäåò èìåíåì ñáîðíèêà, dataFileName -- îñòàòêîì.
1050 pfx := Copy(dataFileName, 1, f-1); Delete(dataFileName, 1, f+1);
1051 // ñíà÷àëà îòêðîåì ïåðâûé ñïèñîê...
1052 result := SFSAddDataFileEx(pfx, ds, 0, 0);
1053 // ...òåïåðü ïðîäîëæèì ñ îñòàòêîì.
1054 // óçíàåì, êàêîå ôàéëî îòêðûâàòü.
1055 // âûêîâûðÿåì ïåðâûé "::" ïðåôèêñ (ýòî áóäåò èìÿ ôàéëà).
1056 f := Pos('::', dataFileName); if f = 0 then f := Length(dataFileName)+1;
1057 fn := Copy(dataFileName, 1, f-1); Delete(dataFileName, 1, f-1);
1058 // dataFileName õðàíèò îñòàòîê.
1059 // èçâëå÷¸ì èìÿ ôàéëà:
1060 SplitDataName(fn, pfx, tmp, vfn);
1061 // îòêðîåì ýòîò ôàéë
1062 vi := TVolumeInfo(volumes[result]); st := nil;
1063 try
1064 st := vi.fVolume.OpenFileEx(tmp);
1065 st1 := TOwnedPartialStream.Create(vi, st, 0, st.Size, true);
1066 except
1067 FreeAndNil(st);
1068 // óäàëèì íåèñïîëüçóåìûé âðåìåííûé òîì.
1069 if (gcdisabled = 0) and not vi.fPermanent and (vi.fOpenedFilesCount < 1) then volumes[result] := nil;
1070 raise;
1071 end;
1072 // óðà. îòêðûëè ôàéë. êèäàåì â âîçäóõ ÷åï÷èêè, ïðîäîëæàåì ðàçâëå÷åíèå.
1073 fn := fn+dataFileName;
1074 try
1075 st1.Position := 0;
1076 result := SFSAddDataFileEx(fn, st1, top, permanent);
1077 except
1078 st1.Free(); // à âîò íå çàëàäèëîñü. çàêðûëè îòêðûòîå ôàéëî, âûëåòåëè.
1079 raise;
1080 end;
1081 exit;
1082 end;
1084 // îáûêíîâåííîå íåðåêóðñèâíîå îòêðûòèå.
1085 SplitDataName(dataFileName, pfx, fn, vfn);
1087 f := FindVolumeInfo(vfn);
1088 if f <> -1 then
1089 begin
1090 if ds <> nil then raise ESFSError.Create('subdata name conflict');
1091 if permanent <> 0 then TVolumeInfo(volumes[f]).fPermanent := (permanent > 0);
1092 if top = 0 then result := f
1093 else if top < 0 then result := 0
1094 else result := volumes.Count-1;
1095 if result <> f then volumes.Move(f, result);
1096 exit;
1097 end;
1099 if ds <> nil then st := ds
1100 else st := TFileStream.Create(fn, fmOpenRead or {fmShareDenyWrite}fmShareDenyNone);
1101 st.Position := 0;
1103 volumes.Pack();
1105 fac := nil; vol := nil;
1106 try
1107 for f := 0 to factories.Count-1 do
1108 begin
1109 fac := TSFSVolumeFactory(factories[f]);
1110 if fac = nil then continue;
1111 if (pfx <> '') and not fac.IsMyVolumePrefix(pfx) then continue;
1112 st.Position := 0;
1113 try
1114 if ds <> nil then vol := fac.Produce(pfx, '', st)
1115 else vol := fac.Produce(pfx, fn, st);
1116 except
1117 vol := nil;
1118 end;
1119 if vol <> nil then break;
1120 end;
1121 if vol = nil then raise ESFSError.Create('no factory for "'+dataFileName+'"');
1122 except
1123 if st <> ds then st.Free();
1124 raise;
1125 end;
1127 vi := TVolumeInfo.Create();
1128 try
1129 if top < 0 then
1130 begin
1131 result := 0;
1132 volumes.Insert(0, vi);
1133 end
1134 else result := volumes.Add(vi);
1135 except
1136 vol.Free();
1137 if st <> ds then st.Free();
1138 vi.Free();
1139 raise;
1140 end;
1142 vi.fFactory := fac;
1143 vi.fVolume := vol;
1144 vi.fPackName := vfn;
1145 vi.fStream := st;
1146 vi.fPermanent := (permanent > 0);
1147 vi.fNoDiskFile := (ds <> nil);
1148 vi.fOpenedFilesCount := 0;
1149 end;
1151 function SFSAddSubDataFile (const virtualName: TSFSString; ds: TStream; top: Boolean=false): Boolean;
1152 var
1153 tv: Integer;
1154 begin
1155 ASSERT(ds <> nil);
1156 try
1157 if top then tv := -1 else tv := 1;
1158 SFSAddDataFileEx(virtualName, ds, tv, 0);
1159 result := true;
1160 except
1161 result := false;
1162 end;
1163 end;
1165 function SFSAddDataFile (const dataFileName: TSFSString; top: Boolean=false): Boolean;
1166 var
1167 tv: Integer;
1168 begin
1169 try
1170 if top then tv := -1 else tv := 1;
1171 SFSAddDataFileEx(dataFileName, nil, tv, 1);
1172 result := true;
1173 except
1174 result := false;
1175 end;
1176 end;
1178 function SFSAddDataFileTemp (const dataFileName: TSFSString; top: Boolean=false): Boolean;
1179 var
1180 tv: Integer;
1181 begin
1182 try
1183 if top then tv := -1 else tv := 1;
1184 SFSAddDataFileEx(dataFileName, nil, tv, 0);
1185 result := true;
1186 except
1187 result := false;
1188 end;
1189 end;
1193 function SFSExpandDirName (const s: TSFSString): TSFSString;
1194 var
1195 f, e: Integer;
1196 es: TSFSString;
1197 begin
1198 f := 1; result := s;
1199 while f < Length(result) do
1200 begin
1201 while (f < Length(result)) and (result[f] <> '<') do Inc(f);
1202 if f >= Length(result) then exit;
1203 e := f; while (e < Length(result)) and (result[e] <> '>') do Inc(e);
1204 es := Copy(result, f, e+1-f);
1206 if es = '<currentdir>' then es := GetCurrentDir
1207 else if es = '<exedir>' then es := ExtractFilePath(ParamStr(0))
1208 else es := '';
1210 if es <> '' then
1211 begin
1212 if (es[Length(es)] <> '/') and (es[Length(es)] <> '\') then es := es+'/';
1213 Delete(result, f, e+1-f);
1214 Insert(es, result, f);
1215 Inc(f, Length(es));
1216 end
1217 else f := e+1;
1218 end;
1219 end;
1221 function SFSFileOpenEx (const fName: TSFSString): TStream;
1222 var
1223 dataFileName, fn: TSFSString;
1224 f: Integer;
1225 vi: TVolumeInfo;
1226 diskChecked: Boolean;
1227 ps: TStream;
1229 function CheckDisk (): TStream;
1230 // ïðîâåðèì, åñòü ëè ôàëî fn ãäå-òî íà äèñêàõ.
1231 var
1232 dfn, dirs, cdir: TSFSString;
1233 f: Integer;
1234 begin
1235 result := nil;
1236 if diskChecked or not sfsDiskEnabled then exit;
1237 diskChecked := true;
1238 dfn := SFSReplacePathDelims(fn, '/');
1239 dirs := sfsDiskDirs; if dirs = '' then dirs := '<currentdir>';
1240 while dirs <> '' do
1241 begin
1242 f := 1; while (f <= Length(dirs)) and (dirs[f] <> '|') do Inc(f);
1243 cdir := Copy(dirs, 1, f-1); Delete(dirs, 1, f);
1244 if cdir = '' then continue;
1245 cdir := SFSReplacePathDelims(SFSExpandDirName(cdir), '/');
1246 if cdir[Length(cdir)] <> '/' then cdir := cdir+'/';
1247 try
1248 result := TFileStream.Create(cdir+dfn, fmOpenRead or {fmShareDenyWrite}fmShareDenyNone);
1249 exit;
1250 except
1251 end;
1252 end;
1253 end;
1255 begin
1256 SplitFName(fName, dataFileName, fn);
1257 if fn = '' then raise ESFSError.Create('invalid file name: "'+fName+'"');
1259 diskChecked := false;
1261 if dataFileName <> '' then
1262 begin
1263 // ïðåôèêñîâàíûé ôàéë
1264 if sfsForceDiskForPrefixed then
1265 begin
1266 result := CheckDisk();
1267 if result <> nil then exit;
1268 end;
1270 f := SFSAddDataFileEx(dataFileName, nil, 0, 0);
1271 vi := TVolumeInfo(volumes[f]);
1273 try
1274 result := vi.fVolume.OpenFileEx(fn);
1275 ps := TOwnedPartialStream.Create(vi, result, 0, result.Size, true);
1276 except
1277 result.Free();
1278 if (gcdisabled = 0) and not vi.fPermanent and (vi.fOpenedFilesCount < 1) then volumes[f] := nil;
1279 result := CheckDisk(); // îáëîì ñ datafile, ïðîâåðèì äèñê
1280 if result = nil then raise ESFSError.Create('file not found: "'+fName+'"');
1281 exit;
1282 end;
1283 //Inc(vi.fOpenedFilesCount);
1284 result := ps;
1285 exit;
1286 end;
1288 // íåïðåôèêñîâàíûé ôàéë
1289 if sfsDiskFirst then
1290 begin
1291 result := CheckDisk();
1292 if result <> nil then exit;
1293 end;
1294 // èùåì ïî âñåì ïåðìàíåíòíûì ïðåôèêñàì
1295 f := 0;
1296 while f < volumes.Count do
1297 begin
1298 vi := TVolumeInfo(volumes[f]);
1299 if (vi <> nil) and vi.fPermanent then
1300 begin
1301 if vi.fVolume <> nil then
1302 begin
1303 result := vi.fVolume.OpenFileEx(fn);
1304 if result <> nil then
1305 begin
1306 try
1307 ps := TOwnedPartialStream.Create(vi, result, 0, result.Size, true);
1308 result := ps;
1309 //Inc(vi.fOpenedFilesCount);
1310 except
1311 FreeAndNil(result);
1312 end;
1313 end;
1314 if result <> nil then exit;
1315 end;
1316 end;
1317 Inc(f);
1318 end;
1319 result := CheckDisk();
1320 if result = nil then raise ESFSError.Create('file not found: "'+fName+'"');
1321 end;
1323 function SFSFileOpen (const fName: TSFSString): TStream;
1324 begin
1325 try
1326 result := SFSFileOpenEx(fName);
1327 except
1328 result := nil;
1329 end;
1330 end;
1332 function SFSFileList (const dataFileName: TSFSString): TSFSFileList;
1333 var
1334 f: Integer;
1335 vi: TVolumeInfo;
1336 begin
1337 result := nil;
1338 if dataFileName = '' then exit;
1340 try
1341 f := SFSAddDataFileEx(dataFileName, nil, 0, 0);
1342 except
1343 exit;
1344 end;
1345 vi := TVolumeInfo(volumes[f]);
1347 try
1348 result := TSFSFileList.Create(vi.fVolume);
1349 Inc(vi.fVolume.fRC);
1350 except
1351 if (gcdisabled = 0) and not vi.fPermanent and (vi.fOpenedFilesCount < 1) then volumes[f] := nil;
1352 end;
1353 end;
1356 // ////////////////////////////////////////////////////////////////////////// //
1357 // utils
1358 // `ch`: utf8 start
1359 // -1: invalid utf8
1360 function utf8CodeLen (ch: Word): Integer;
1361 begin
1362 if ch < $80 then begin result := 1; exit; end;
1363 if (ch and $FE) = $FC then begin result := 6; exit; end;
1364 if (ch and $FC) = $F8 then begin result := 5; exit; end;
1365 if (ch and $F8) = $F0 then begin result := 4; exit; end;
1366 if (ch and $F0) = $E0 then begin result := 3; exit; end;
1367 if (ch and $E0) = $C0 then begin result := 2; exit; end;
1368 result := -1; // invalid
1369 end;
1372 function utf8Valid (s: string): Boolean;
1373 var
1374 pos, len: Integer;
1375 begin
1376 result := false;
1377 pos := 1;
1378 while pos <= length(s) do
1379 begin
1380 len := utf8CodeLen(Byte(s[pos]));
1381 if len < 1 then exit; // invalid sequence start
1382 if pos+len-1 > length(s) then exit; // out of chars in string
1383 Dec(len);
1384 Inc(pos);
1385 // check other sequence bytes
1386 while len > 0 do
1387 begin
1388 if (Byte(s[pos]) and $C0) <> $80 then exit;
1389 Dec(len);
1390 Inc(pos);
1391 end;
1392 end;
1393 result := true;
1394 end;
1397 // ////////////////////////////////////////////////////////////////////////// //
1398 const
1399 // TODO: move this to a separate file
1400 uni2wint: array [128..255] of Word = (
1401 $0402,$0403,$201A,$0453,$201E,$2026,$2020,$2021,$20AC,$2030,$0409,$2039,$040A,$040C,$040B,$040F,
1402 $0452,$2018,$2019,$201C,$201D,$2022,$2013,$2014,$003F,$2122,$0459,$203A,$045A,$045C,$045B,$045F,
1403 $00A0,$040E,$045E,$0408,$00A4,$0490,$00A6,$00A7,$0401,$00A9,$0404,$00AB,$00AC,$00AD,$00AE,$0407,
1404 $00B0,$00B1,$0406,$0456,$0491,$00B5,$00B6,$00B7,$0451,$2116,$0454,$00BB,$0458,$0405,$0455,$0457,
1405 $0410,$0411,$0412,$0413,$0414,$0415,$0416,$0417,$0418,$0419,$041A,$041B,$041C,$041D,$041E,$041F,
1406 $0420,$0421,$0422,$0423,$0424,$0425,$0426,$0427,$0428,$0429,$042A,$042B,$042C,$042D,$042E,$042F,
1407 $0430,$0431,$0432,$0433,$0434,$0435,$0436,$0437,$0438,$0439,$043A,$043B,$043C,$043D,$043E,$043F,
1408 $0440,$0441,$0442,$0443,$0444,$0445,$0446,$0447,$0448,$0449,$044A,$044B,$044C,$044D,$044E,$044F
1409 );
1412 function decodeUtf8Char (s: TSFSString; var pos: Integer): char;
1413 var
1414 b, c: Integer;
1415 begin
1416 (* The following encodings are valid, except for the 5 and 6 byte
1417 * combinations:
1418 * 0xxxxxxx
1419 * 110xxxxx 10xxxxxx
1420 * 1110xxxx 10xxxxxx 10xxxxxx
1421 * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
1422 * 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
1423 * 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
1424 *)
1425 result := '?';
1426 if pos > length(s) then exit;
1428 b := Byte(s[pos]);
1429 Inc(pos);
1430 if b < $80 then begin result := char(b); exit; end;
1432 // mask out unused bits
1433 if (b and $FE) = $FC then b := b and $01
1434 else if (b and $FC) = $F8 then b := b and $03
1435 else if (b and $F8) = $F0 then b := b and $07
1436 else if (b and $F0) = $E0 then b := b and $0F
1437 else if (b and $E0) = $C0 then b := b and $1F
1438 else exit; // invalid utf8
1440 // now continue
1441 while pos <= length(s) do
1442 begin
1443 c := Byte(s[pos]);
1444 if (c and $C0) <> $80 then break; // no more
1445 b := b shl 6;
1446 b := b or (c and $3F);
1447 Inc(pos);
1448 end;
1450 // done, try 1251
1451 for c := 128 to 255 do if uni2wint[c] = b then begin result := char(c and $FF); exit; end;
1452 // alas
1453 end;
1456 function utf8to1251 (s: TSFSString): TSFSString;
1457 var
1458 pos: Integer;
1459 begin
1460 if not utf8Valid(s) then begin result := s; exit; end;
1461 pos := 1;
1462 while pos <= length(s) do
1463 begin
1464 if Byte(s[pos]) >= $80 then break;
1465 Inc(pos);
1466 end;
1467 if pos > length(s) then begin result := s; exit; end; // nothing to do here
1468 result := '';
1469 pos := 1;
1470 while pos <= length(s) do result := result+decodeUtf8Char(s, pos);
1471 end;
1474 initialization
1475 factories := TObjectList.Create(true);
1476 volumes := TObjectList.Create(true);
1477 //finalization
1478 //volumes.Free(); // it fails for some reason... Runtime 217 (^C hit). wtf?!
1479 //factories.Free(); // not need to be done actually...
1480 end.