DEADSOFTWARE

Added SFS support (resource wads only) (#4)
[d2df-editor.git] / src / sfs / sfs.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *)
16 // streaming file system (virtual)
17 {$INCLUDE ../shared/a_modes.inc}
18 {$SCOPEDENUMS OFF}
19 {.$R+}
20 {.$DEFINE SFS_VOLDEBUG}
21 unit sfs;
23 interface
25 uses
26 SysUtils, Classes, Contnrs;
29 type
30 ESFSError = class(Exception);
32 TSFSVolume = class;
34 TSFSFileInfo = class
35 public
36 fOwner: TSFSVolume; // òàê, íà âñÿêèé ñëó÷àé
37 fPath: AnsiString; // ðàçäåëèòåëè êàòàëîãîâ -- "/"; êîðåíü íèêàê íå îáîçíà÷åí, åñëè íå ïóñòîå, îáÿçàíî çàâåðøàòüñÿ "/"
38 fName: AnsiString; // òîëüêî èìÿ
39 fSize: Int64; // unpacked
40 fOfs: Int64; // in VFS (many of 'em need this %-)
42 constructor Create (pOwner: TSFSVolume);
43 destructor Destroy (); override;
45 property path: AnsiString read fPath;
46 property name: AnsiString read fName;
47 property size: Int64 read fSize; // can be -1 if size is unknown
48 end;
50 // âèðòóàëüíàÿ ôàéëîâàÿ ñèñòåìà. ÒÎËÜÊÎ ÄËß ×ÒÅÍÈß!
51 // òîì ÍÅ ÄÎËÆÅÍ óáèâàòüñÿ íèêàê èíà÷å, ÷åì ïðè ïîìîùè ôàáðèêè!
52 TSFSVolume = class
53 protected
54 fFileName: AnsiString;// îáû÷íî èìÿ îðèãèíàëüíîãî ôàéëà
55 fFileStream: TStream; // îáû÷íî ïîòîê äëÿ ÷òåíèÿ îðèãèíàëüíîãî ôàéëà
56 fFiles: TObjectList; // TSFSFileInfo èëè íàñëåäíèêè
58 // ïðèøèáèòü âñå ñòðóêòóðû.
59 // íå äîëæíà ïàäàòü, åñëè å¸ âûçûâàþò íåñêîëüêî ðàç.
60 procedure Clear (); virtual;
62 // âûçûâàåòñÿ èç DoDirectoryRead() äëÿ çàïîëíåíèÿ ñïèñêà ôàéëîâ.
63 // ñ÷èòàåòñÿ, ÷òî âñå ìàãèêè óæå ïðîâåðåíû è ôàéë òî÷íî íàø.
64 // fFileName, fFileStream óæå óñòàíîâëåíû, fFiles ñîçäàí,
65 // â í¸ì, ñêîðåå âñåãî, íèêîãî íåò.
66 // ïîçèöèÿ ïîòîêà -- òà, ÷òî îñòàâèëà ôàáðèêà.
67 // ïðè îøèáêàõ êèäàòü èñêëþ÷åíèå, òîãäà òîì áóäåò ïðèáèò ôàáðèêîé.
68 // ðàçäåëèòåëè ïóòåé äîëæíû áûòü òîëüêî "/", êîðíåâîé "/" äîëæåí
69 // áûòü îïóùåí, ïóòè (åñëè íå ïóñòûå) äîëæíû çàâåðøàòüñÿ "/"!
70 // fName äîëæíî ñîäåðæàòü òîëüêî èìÿ, fPath -- òîëüêî ïóòü.
71 // â ïðèíöèïå, îá ýòîì ïîçàáîòèòñÿ DoDirectoryRead(), íî çà÷åì
72 // äàâàòü åìó ëèøíþþ ðàáîòó?
73 procedure ReadDirectory (); virtual; abstract;
75 // íàéòè ôàéë, âåðíóòü åãî èíäåêñ â fFiles.
76 // ýòà ïðîöåäóðà ìîæåò ìåíÿòü fFiles!
77 // fPath -- â ïðàâèëüíîé ôîðìå, ñ "/", êîðíåâîé "/" óáèò, ôèíàëüíûé äîáàâëåí.
78 // åñëè ôàéë íå íàéäåí, âåðíóòü -1.
79 function FindFile (const fPath, fName: AnsiString): Integer; virtual;
81 // âîçâðàùàåò êîëè÷åñòâî ôàéëîâ â fFiles
82 function GetFileCount (): Integer; virtual;
84 // âîçâðàùàåò ôàéë ñ èíäåêñîì index.
85 // ìîæåò âîçâðàùàòü NIL.
86 // íèêàêèõ ïàäåíèé íà íåïðàâèëüíûå èíäåêñû!
87 function GetFiles (index: Integer): TSFSFileInfo; virtual;
89 public
90 // pSt íå îáÿçàòåëüíî çàïîìèíàòü, åñëè îí íå íóæåí.
91 constructor Create (const pFileName: AnsiString; pSt: TStream); virtual;
92 // fFileStream óíè÷òîæàòü íåëüçÿ, åñëè îí ðàâåí ïàðàìåòðó pSt êîíñòðóêòîðà.
93 destructor Destroy (); override;
95 // âûçûâàåò ReadDirectory().
96 // ýòà ïðîöåäóðà ñàìà ðàçáåð¸òñÿ ñ äóáëèêàòàìè èì¸í: ïîäîáàâëÿåò â
97 // êîíåö èì¸í-äóáëèêàòîâ ïîä÷¸ðêèâàíèå è äåñÿòè÷íûé íîìåð.
98 // òàêæå îíà íîðìàëèçóåò âèä èì¸í.
99 procedure DoDirectoryRead ();
101 // ïðè îøèáêàõ êèäàòüñÿ èñêëþ÷åíèÿìè.
102 function OpenFileByIndex (const index: Integer): TStream; virtual; abstract;
104 // åñëè íå ñìîãëî îòêóïîðèòü ôàéëî (èëè åù¸ ãäå îøèáëîñü), çàøâûðí¸ò èñêëþ÷åíèå.
105 function OpenFileEx (const fName: AnsiString): TStream; virtual;
107 property FileCount: Integer read GetFileCount; // ìîæåò âåðíóòü íîëü
108 // ìîæåò âîçâðàùàòü NIL.
109 // íèêàêèõ ïàäåíèé íà íåïðàâèëüíûå èíäåêñû!
110 property Files [index: Integer]: TSFSFileInfo read GetFiles;
111 end;
113 // ôàáðèêà òîìîâ. âñå SFS ïðè ñòàðòå äîáàâëÿþò ñâîè ôàáðèêè.
114 // áëàãîäàðÿ ýòîìó ìîæíî ñîçäàâàòü ðàçíûå âñÿêèå SFS ñòàíäàðòíûì
115 // âûçîâîì ñòàíäàðòíîé ïðîöåäóðû.
116 // ôàáðèêà ÍÅ ÄÎËÆÍÀ óáèâàòüñÿ íèêàê èíà÷å, ÷åì ïðè ïîìîùè âûçîâà
117 // SFSUnregisterVolumeFactory()! ýòî ãàðàíòèðóåò, ÷òî äâèæîê
118 // ïåðåä ðàññòðåëîì îòäàñò åé âñå å¸ òîìà.
119 TSFSVolumeFactory = class
120 public
121 // åñëè äîáàâëÿåì ôàéë äàííûõ ôàéë ñ èìåíåì òèïà "zip:....", òî
122 // SFS èçâëå÷¸ò ýòî "zip" è ïåðåäàñò â ñèþ ôóíêöèþ.
123 // åæåëè ôóíêöèÿ âåðí¸ò ïðàâäó, òî SFS âûçîâåò Produce äëÿ äàííîãî
124 // ôàéëà. åñëè íè îäíà ôàáðèêà ïðåôèêñ íå ïðèçíàåò, òî ôàéë íå îòêðîþò.
125 // èñïîëüçóåòñÿ äëÿ ñêèïàíèÿ àâòîäåòåêòà.
126 // SFS ÍÅ Ñ×ÈÒÀÅÒ ÏÐÅÔÈÊÑÎÌ ÑÒÐÎÊÓ ÊÎÐÎ×Å ÒÐ¨Õ ÑÈÌÂÎËÎÂ!
127 function IsMyVolumePrefix (const prefix: AnsiString): Boolean; virtual; abstract;
128 // ïðîâåðÿåò, ìîæåò ëè ôàáðèêà ñäåëàòü òîì äëÿ äàííîãî ôàéëà.
129 // st -- îòêðûòûé äëÿ ÷òåíèÿ ôàéëîâé ïîòîê. óêàçàòåëü ÷òåíèÿ ñòîèò â íà÷àëå.
130 // ýòîò ïîòîê íåëüçÿ çàêðûâàòü!
131 // prefix: òî, ÷òî áûëî ïåðåäàíî â IsMyVolumePrefix() èëè ''.
132 // èñêëþ÷åíèå ñ÷èòàåòñÿ îøèáêîé, âîçâðàò NIL ñ÷èòàåòñÿ îøèáêîé.
133 function Produce (const prefix, fileName: AnsiString; st: TStream): TSFSVolume; virtual; abstract;
134 // êîãäà òîì áîëüøå íå íóæåí, îí áóäåò îòäàí ôàáðèêå íà ïåðåðàáîòêó.
135 // äàëåå äâèæîê íå áóäåò þçàòü ñåé òîì.
136 procedure Recycle (vol: TSFSVolume); virtual; abstract;
137 end;
139 // "èòåðàòîð", âîçâðàùàåìûé SFSFileList()
140 TSFSFileList = class
141 protected
142 fVolume: TSFSVolume;
144 function GetCount (): Integer;
145 function GetFiles (index: Integer): TSFSFileInfo;
147 public
148 constructor Create (const pVolume: TSFSVolume);
149 destructor Destroy (); override;
151 property Volume: TSFSVolume read fVolume;
152 property Count: Integer read GetCount;
153 // ïðè íåïðàâèëüíîì èíäåêñå ìîë÷à âåðí¸ò NIL.
154 // ïðè ïðàâèëüíîì òîæå ìîæåò âåðíóòü NIL!
155 // î÷åíü íå ñîâåòóþ ìåíÿòü ñîäåðæèìîå ïîëó÷åííîãî êëàññà.
156 // êîíå÷íî, ÿ ìîã áû âîçâðàùàòü íîâóþ ñòðóêòóðó èëè íå÷òî ïîõîæåå,
157 // íî áëèí, åñëè òû èäèîò è íå óìååøü äàæå êîììåíòû ÷èòàòü, òî
158 // êàêîãî òû âîîáùå â ïðîãðàììèíã ïîëåç?
159 property Files [index: Integer]: TSFSFileInfo read GetFiles; default;
160 end;
163 procedure SFSRegisterVolumeFactory (factory: TSFSVolumeFactory);
164 // ýòà ôóíêöèÿ àâòîìàòè÷åñêè ïðèáü¸ò factory.
165 procedure SFSUnregisterVolumeFactory (factory: TSFSVolumeFactory);
167 // äîáàâèòü ñáîðíèê â ïîñòîÿííûé ñïèñîê.
168 // åñëè ñáîðíèê ñ òàêèì èìåíåì óæå îòêðûò, òî íå îòêðûâàåò åãî ïîâòîðíî.
169 // íèêîãäà íå êèäàåò èñêëþ÷åíèé.
170 // top: äîáàâèòü â íà÷àëî ñïèñêà ïîèñêà.
171 // âåðí¸ò ëîæü ïðè îøèáêå.
172 // ñïîñîáíî îòêðûâàòü ñáîðíèêè â ñáîðíèêàõ ïðè ïîìîùè êðóòûõ èì¸í a-la:
173 // "zip:pack0::pack:pack1::wad2:pack2".
174 // â äàëüíåéøåì ñëåäóåò îáðàùàòüñÿ ê ñáîðíèêó êàê "pack2::xxx".
175 // èëè ìîæíî íàïèñàòü:
176 // "zip:pack0::pack:pack1::wad2:pack2|datafile".
177 // è îáðàùàòüñÿ êàê "datafile::xxx".
178 // "||" ïðåîáðàçóþòñÿ â ïðîñòîé "|" è ðàçäåëèòåëåì íå ñ÷èòàþòñÿ.
179 // ïðèíèìàåòñÿ âî âíèìàíèå òîëüêî ïîñëåäíÿÿ òðóáà.
180 function SFSAddDataFile (const dataFileName: AnsiString; top: Boolean=false): Boolean;
182 // äîáàâèòü ñáîðíèê âðåìåííî
183 function SFSAddDataFileTemp (const dataFileName: AnsiString; top: Boolean=false): Boolean;
185 // äîáàâèòü â ïîñòîÿííûé ñïèñîê ñáîðíèê èç ïîòîêà ds.
186 // åñëè âîçâðàùàåò èñòèíó, òî SFS ñòàíîâèòñÿ âëÿäåëüöåì ïîòîêà ds è ñàìà
187 // óãðîáèò ñåé ïîòîê ïî íåîáõîäèìîñòè.
188 // virtualName ñòàíîâèòñÿ èìåíåì ñáîðíèêà äëÿ îïåðàöèè îòêðûòèÿ ôàéëà òèïà
189 // "packfile:file.ext".
190 // åñëè êàêîé-íèáóäü ñáîðíèê ñ èìåíåì virtualName óæå îòêðûò, âåðí¸ò false.
191 // íèêîãäà íå êèäàåò èñêëþ÷åíèé.
192 // top: äîáàâèòü â íà÷àëî ñïèñêà ïîèñêà.
193 // âåðí¸ò ëîæü ïðè îøèáêå.
194 // îòêðûâàåò ñáîðíèê èç ïîòîêà. dataFileName -- ÂÈÐÒÓÀËÜÍÎÅ èìÿ.
195 // ò.å. íà ñàìîì äåëå òàêîãî ôàéëà ìîæåò è íå áûòü íà äèñêå.
196 function SFSAddSubDataFile (const virtualName: AnsiString; ds: TStream; top: Boolean=false): Boolean;
198 // øâûðÿåòñÿ èñêëþ÷åíèÿìè.
199 // åñëè fName íå èìååò óêàçàíèÿ íà ôàéë äàííûõ (ýòî òî, ÷òî îòäåëåíî îò
200 // îñòàëüíîãî èìåíè äâîåòî÷èåì), òî èùåì ñíà÷àëà ïî âñåì çàðåãèñòðèðîâàííûì
201 // ôàéëàì äàííûõ, ïîòîì â òåêóùåì êàòàëîãå, ïîòîì â êàòàëîãå, îòêóäà ñòàðòîâàëè.
202 // åñëè íè÷åãî íå íàøëè, êèäàåì èñêëþ÷åíèå.
203 function SFSFileOpenEx (const fName: AnsiString): TStream;
205 // ïðè îøèáêå -- NIL, è íèêàêèõ èñêëþ÷åíèé.
206 function SFSFileOpen (const fName: AnsiString): TStream;
208 // âîçâðàùàåò NIL ïðè îøèáêå.
209 // ïîñëå èñïîëüçîâàíèÿ, íàòóðàëüíî, èòåðàòîð íàäî ãðîõíóòü %-)
210 function SFSFileList (const dataFileName: AnsiString): TSFSFileList;
212 // çàïðåòèòü îñâîáîæäåíèå âðåìåííûõ òîìîâ (ìîæíî âûçûâàòü ðåêóðñèâíî)
213 procedure sfsGCDisable ();
215 // ðàçðåøèòü îñâîáîæäåíèå âðåìåííûõ òîìîâ (ìîæíî âûçûâàòü ðåêóðñèâíî)
216 procedure sfsGCEnable ();
218 // for completeness sake
219 procedure sfsGCCollect ();
221 function SFSReplacePathDelims (const s: AnsiString; newDelim: Char): AnsiString;
223 // ðàçîáðàòü òîëñòîå èìÿ ôàéëà, âåðíóòü âèðòóàëüíîå èìÿ ïîñëåäíåãî ñïèñêà
224 // èëè ïóñòóþ ñòîðîêó, åñëè ñïèñêîâ íå áûëî.
225 function SFSGetLastVirtualName (const fn: AnsiString): AnsiString;
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: AnsiString): Boolean;
244 function WildListMatch (wildList, text: AnsiString; delimChar: AnsiChar=':'): Integer;
245 function HasWildcards (const pattern: AnsiString): Boolean;
248 var
249 // ïðàâäà: ðàçðåøåíî èñêàòü ôàéëî íå òîëüêî â ôàéëàõ äàííûõ, íî è íà äèñêå.
250 sfsDiskEnabled: Boolean = true;
251 // ïðàâäà: åñëè ôàéë íå ïðåôèêñîâàí, òî ñíà÷àëà èùåì ôàéëî íà äèñêå,
252 // ïîòîì â ôàéëàõ äàííûõ.
253 sfsDiskFirst: Boolean = true;
254 // ïðàâäà: äàæå äëÿ ïðåôèêñîâàíûõ ôàéëîâ ñíà÷àëà ïðîñìîòðèì äèñê
255 // (åñëè óñòàíîâëåí ôëàæîê sfsDiskFirst è sfsDiskEnabled).
256 sfsForceDiskForPrefixed: Boolean = false;
257 // ñïèñîê äèñêîâûõ êàòàëîãîâ äëÿ ïîèñêà ôàéëà. åñëè ïóñò -- èùåì òîëüêî â
258 // òåêóùåì. êàòàëîãè ðàçäåëÿþòñÿ òðóáîé ("|").
259 // <currentdir> çàìåíÿåòñÿ íà òåêóùèé êàòàëîã (ñ çàâåðøàþùèì "/"),
260 // <exedir> çàìåíÿåòñÿ íà êàòàëîã, ãäå ñèäèò .EXE (ñ çàâåðøàþùèì "/").
261 sfsDiskDirs: AnsiString = '<currentdir>|<exedir>';
264 implementation
266 uses
267 xstreams, utils;
270 const
271 // character defines
272 WILD_CHAR_ESCAPE = '\';
273 WILD_CHAR_SINGLE = '?';
274 WILD_CHAR_SINGLE_OR_NONE = '+';
275 WILD_CHAR_MULTI = '*';
276 WILD_CHAR_RANGE_OPEN = '[';
277 WILD_CHAR_RANGE = '-';
278 WILD_CHAR_RANGE_CLOSE = ']';
279 WILD_CHAR_RANGE_NOT = '!';
282 function HasWildcards (const pattern: AnsiString): Boolean;
283 begin
284 result :=
285 (Pos(WILD_CHAR_ESCAPE, pattern) <> 0) or
286 (Pos(WILD_CHAR_SINGLE, pattern) <> 0) or
287 (Pos(WILD_CHAR_SINGLE_OR_NONE, pattern) <> 0) or
288 (Pos(WILD_CHAR_MULTI, pattern) <> 0) or
289 (Pos(WILD_CHAR_RANGE_OPEN, pattern) <> 0);
290 end;
292 function MatchMask (const pattern: AnsiString; p, pend: Integer; const text: AnsiString; t, tend: Integer): Boolean;
293 var
294 rangeStart, rangeEnd: AnsiChar;
295 rangeNot, rangeMatched: Boolean;
296 ch: AnsiChar;
297 begin
298 // sanity checks
299 if (pend < 0) or (pend > Length(pattern)) then pend := Length(pattern);
300 if (tend < 0) or (tend > Length(text)) then tend := Length(text);
301 if t < 1 then t := 1;
302 if p < 1 then p := 1;
303 while p <= pend do
304 begin
305 if t > tend then
306 begin
307 // no more text. check if there's no more chars in pattern (except "*" & "+")
308 while (p <= pend) and
309 ((pattern[p] = WILD_CHAR_MULTI) or
310 (pattern[p] = WILD_CHAR_SINGLE_OR_NONE)) do Inc(p);
311 result := (p > pend);
312 exit;
313 end;
314 case pattern[p] of
315 WILD_CHAR_SINGLE: ;
316 WILD_CHAR_ESCAPE:
317 begin
318 Inc(p);
319 if p > pend then result := false else result := (pattern[p] = text[t]);
320 if not result then exit;
321 end;
322 WILD_CHAR_RANGE_OPEN:
323 begin
324 result := false;
325 Inc(p); if p > pend then exit; // sanity check
326 rangeNot := (pattern[p] = WILD_CHAR_RANGE_NOT);
327 if rangeNot then begin Inc(p); if p > pend then exit; {sanity check} end;
328 if pattern[p] = WILD_CHAR_RANGE_CLOSE then exit; // sanity check
329 ch := text[t]; // speed reasons
330 rangeMatched := false;
331 repeat
332 if p > pend then exit; // sanity check
333 rangeStart := pattern[p];
334 if rangeStart = WILD_CHAR_RANGE_CLOSE then break;
335 Inc(p); if p > pend then exit; // sanity check
336 if pattern[p] = WILD_CHAR_RANGE then
337 begin
338 Inc(p); if p > pend then exit; // sanity check
339 rangeEnd := pattern[p]; Inc(p);
340 if rangeStart < rangeEnd then
341 begin
342 rangeMatched := (ch >= rangeStart) and (ch <= rangeEnd);
343 end
344 else rangeMatched := (ch >= rangeEnd) and (ch <= rangeStart);
345 end
346 else rangeMatched := (ch = rangeStart);
347 until rangeMatched;
348 if rangeNot = rangeMatched then exit;
350 // skip the rest or the range
351 while (p <= pend) and (pattern[p] <> WILD_CHAR_RANGE_CLOSE) do Inc(p);
352 if p > pend then exit; // sanity check
353 end;
354 WILD_CHAR_SINGLE_OR_NONE:
355 begin
356 Inc(p);
357 result := MatchMask(pattern, p, pend, text, t, tend);
358 if not result then result := MatchMask(pattern, p, pend, text, t+1, tend);
359 exit;
360 end;
361 WILD_CHAR_MULTI:
362 begin
363 while (p <= pend) and (pattern[p] = WILD_CHAR_MULTI) do Inc(p);
364 result := (p > pend); if result then exit;
365 while not result and (t <= tend) do
366 begin
367 result := MatchMask(pattern, p, pend, text, t, tend);
368 Inc(t);
369 end;
370 exit;
371 end;
372 else result := (pattern[p] = text[t]); if not result then exit;
373 end;
374 Inc(p); Inc(t);
375 end;
376 result := (t > tend);
377 end;
380 function WildMatch (pattern, text: AnsiString): Boolean;
381 begin
382 if pattern <> '' then pattern := AnsiLowerCase(pattern);
383 if text <> '' then text := AnsiLowerCase(text);
384 result := MatchMask(pattern, 1, -1, text, 1, -1);
385 end;
387 function WildListMatch (wildList, text: AnsiString; delimChar: AnsiChar=':'): Integer;
388 var
389 s, e: Integer;
390 begin
391 if wildList <> '' then wildList := AnsiLowerCase(wildList);
392 if text <> '' then text := AnsiLowerCase(text);
393 result := 0;
394 s := 1;
395 while s <= Length(wildList) do
396 begin
397 e := s; while e <= Length(wildList) do
398 begin
399 if wildList[e] = WILD_CHAR_RANGE_OPEN then
400 begin
401 while (e <= Length(wildList)) and (wildList[e] <> WILD_CHAR_RANGE_CLOSE) do Inc(e);
402 end;
403 if wildList[e] = delimChar then break;
404 Inc(e);
405 end;
406 if s < e then
407 begin
408 if MatchMask(wildList, s, e-1, text, 1, -1) then exit;
409 end;
410 Inc(result);
411 s := e+1;
412 end;
413 result := -1;
414 end;
417 type
418 TVolumeInfo = class
419 public
420 fFactory: TSFSVolumeFactory;
421 fVolume: TSFSVolume;
422 fPackName: AnsiString; // äëÿ îäíîãî è òîãî æå ôàéëà áóäåò òîëüêî îäèí òîì!
423 fStream: TStream; // ôàéëîâûé ïîòîê äëÿ ñáîðíèêà
424 fPermanent: Boolean; // èñòèíà -- íå áóäåò óãðîáëåíà, åñëè íå îñòàíåòñÿ íè îäíîãî îòêðûòîãî òîìà
425 // èñòèíà -- ýòîò òîì áûë ñîçäàí èç ïîòîêà è íå èìååò äèñêîâîãî ôàéëà, ïîòîìó ôàáðèêå áóäåò ïåðåäàíî íå èìÿ ñáîðíèêà, à ïóñòàÿ ñòðîêà
426 fNoDiskFile: Boolean;
427 fOpenedFilesCount: Integer;
429 destructor Destroy (); override;
430 end;
432 TOwnedPartialStream = class (TSFSPartialStream)
433 protected
434 fOwner: TVolumeInfo;
436 public
437 constructor Create (pOwner: TVolumeInfo; pSrc: TStream; pPos, pSize: Int64; pKillSrc: Boolean);
438 destructor Destroy (); override;
439 end;
442 var
443 factories: TObjectList; // TSFSVolumeFactory
444 volumes: TObjectList; // TVolumeInfo
445 gcdisabled: Integer = 0; // >0: disabled
448 procedure sfsGCCollect ();
449 var
450 f, c: Integer;
451 vi: TVolumeInfo;
452 used: Boolean;
453 begin
454 // collect garbage
455 f := 0;
456 while f < volumes.Count do
457 begin
458 vi := TVolumeInfo(volumes[f]);
459 if vi = nil then continue;
460 if (not vi.fPermanent) and (vi.fOpenedFilesCount = 0) then
461 begin
462 // this volume probably can be removed
463 used := false;
464 c := volumes.Count-1;
465 while not used and (c >= 0) do
466 begin
467 if (c <> f) and (volumes[c] <> nil) then
468 begin
469 used := (TVolumeInfo(volumes[c]).fStream = vi.fStream);
470 if not used then used := (TVolumeInfo(volumes[c]).fVolume.fFileStream = vi.fStream);
471 if used then break;
472 end;
473 Dec(c);
474 end;
475 if not used then
476 begin
477 {$IFDEF SFS_VOLDEBUG}writeln('000: destroying volume "', TVolumeInfo(volumes[f]).fPackName, '"');{$ENDIF}
478 volumes.extract(vi); // remove from list
479 vi.Free; // and kill
480 f := 0;
481 continue;
482 end;
483 end;
484 Inc(f); // next volume
485 end;
486 end;
488 procedure sfsGCDisable ();
489 begin
490 Inc(gcdisabled);
491 end;
493 procedure sfsGCEnable ();
494 begin
495 Dec(gcdisabled);
496 if gcdisabled <= 0 then
497 begin
498 gcdisabled := 0;
499 sfsGCCollect();
500 end;
501 end;
504 // ðàçáèòü èìÿ ôàéëà íà ÷àñòè: ïðåôèêñ ôàéëîâîé ñèñòåìû, èìÿ ôàéëà äàííûõ,
505 // ñîáñòâåííî èìÿ ôàéëà
506 // èìÿ âûãëÿäèò êàê:
507 // (("sfspfx:")?"datafile::")*"filename"
508 procedure SplitFName (const fn: AnsiString; out dataFile, fileName: AnsiString);
509 var
510 f: Integer;
511 begin
512 f := Length(fn)-1;
513 while f >= 1 do
514 begin
515 if (fn[f] = ':') and (fn[f+1] = ':') then break;
516 Dec(f);
517 end;
518 if f < 1 then begin dataFile := ''; fileName := fn; end
519 else
520 begin
521 dataFile := Copy(fn, 1, f-1);
522 fileName := Copy(fn, f+2, maxInt-10000);
523 end;
524 end;
526 // ñàéäýôôåêò: âûðåçàåò âèðòóàëüíîå èìÿ èç dataFile.
527 function ExtractVirtName (var dataFile: AnsiString): AnsiString;
528 var
529 f: Integer;
530 begin
531 f := Length(dataFile); result := dataFile;
532 while f > 1 do
533 begin
534 if dataFile[f] = ':' then break;
535 if dataFile[f] = '|' then
536 begin
537 if dataFile[f-1] = '|' then begin Dec(f); Delete(dataFile, f, 1); end
538 else
539 begin
540 result := Copy(dataFile, f+1, Length(dataFile));
541 Delete(dataFile, f, Length(dataFile));
542 break;
543 end;
544 end;
545 Dec(f);
546 end;
547 end;
549 // ðàçáèòü èìÿ ñáîðíèêà íà ÷àñòè: ïðåôèêñ ôàéëîâîé ñèñòåìû, èìÿ ôàéëà äàííûõ,
550 // âèðòóàëüíîå èìÿ. åñëè âèðòóàëüíîãî èìåíè íå äàíî, îíî áóäåò ðàâíî dataFile.
551 // èìÿ âûãëÿäèò êàê:
552 // [sfspfx:]datafile[|virtname]
553 // åñëè ïåðåä äâîåòî÷èåì ìåíüøå òð¸õ áóêâ, òî ýòî ñ÷èòàåòñÿ íå ïðåôèêñîì,
554 // à èìåíåì äèñêà.
555 procedure SplitDataName (const fn: AnsiString; out pfx, dataFile, virtName: AnsiString);
556 var
557 f: Integer;
558 begin
559 f := Pos(':', fn);
560 if f <= 3 then begin pfx := ''; dataFile := fn; end
561 else
562 begin
563 pfx := Copy(fn, 1, f-1);
564 dataFile := Copy(fn, f+1, maxInt-10000);
565 end;
566 virtName := ExtractVirtName(dataFile);
567 end;
569 // íàéòè ïðîèçâîäèòåëÿ äëÿ ýòîãî ôàéëà (åñëè ôàéë óæå îòêðûò).
570 // onlyPerm: òîëüêî "ïîñòîÿííûå" ïðîèçâîäèòåëè.
571 function FindVolumeInfo (const dataFileName: AnsiString; onlyPerm: Boolean=false): Integer;
572 var
573 f: Integer;
574 vi: TVolumeInfo;
575 begin
576 f := 0;
577 while f < volumes.Count do
578 begin
579 if volumes[f] <> nil then
580 begin
581 vi := TVolumeInfo(volumes[f]);
582 if not onlyPerm or vi.fPermanent then
583 begin
584 if StrEquCI1251(vi.fPackName, dataFileName) then
585 begin
586 result := f;
587 exit;
588 end;
589 end;
590 end;
591 Inc(f);
592 end;
593 result := -1;
594 end;
596 // íàéòè èíôó äëÿ ýòîãî òîìà.
597 // õîðîøåå èìÿ, ïðàâäà? %-)
598 function FindVolumeInfoByVolumeInstance (vol: TSFSVolume): Integer;
599 begin
600 result := volumes.Count-1;
601 while result >= 0 do
602 begin
603 if volumes[result] <> nil then
604 begin
605 if TVolumeInfo(volumes[result]).fVolume = vol then exit;
606 end;
607 Dec(result);
608 end;
609 end;
612 // adds '/' too
613 function normalizePath (fn: AnsiString): AnsiString;
614 var
615 i: Integer;
616 begin
617 result := '';
618 i := 1;
619 while i <= length(fn) do
620 begin
621 if (fn[i] = '.') and ((length(fn)-i = 0) or (fn[i+1] = '/') or (fn[i+1] = '\')) then
622 begin
623 i := i+2;
624 continue;
625 end;
626 if (fn[i] = '/') or (fn[i] = '\') then
627 begin
628 if (length(result) > 0) and (result[length(result)] <> '/') then result := result+'/';
629 end
630 else
631 begin
632 result := result+fn[i];
633 end;
634 Inc(i);
635 end;
636 if (length(result) > 0) and (result[length(result)] <> '/') then result := result+'/';
637 end;
639 function SFSReplacePathDelims (const s: AnsiString; newDelim: Char): AnsiString;
640 var
641 f: Integer;
642 begin
643 result := s;
644 for f := 1 to Length(result) do
645 begin
646 if (result[f] = '/') or (result[f] = '\') then
647 begin
648 // avoid unnecessary string changes
649 if result[f] <> newDelim then result[f] := newDelim;
650 end;
651 end;
652 end;
654 function SFSGetLastVirtualName (const fn: AnsiString): AnsiString;
655 var
656 rest, tmp: AnsiString;
657 f: Integer;
658 begin
659 rest := fn;
660 repeat
661 f := Pos('::', rest); if f = 0 then f := Length(rest)+1;
662 tmp := Copy(rest, 1, f-1); Delete(rest, 1, f+1);
663 result := ExtractVirtName(tmp);
664 until rest = '';
665 end;
668 { TVolumeInfo }
669 destructor TVolumeInfo.Destroy ();
670 var
671 f, me: Integer;
672 used: Boolean; // ôëàæîê çàþçàíîñòè ïîòîêà êåì-òî åù¸
673 begin
674 if fFactory <> nil then fFactory.Recycle(fVolume);
675 used := false;
676 fVolume := nil;
677 fFactory := nil;
678 fPackName := '';
680 // òèïà ìóñîðîñáîðíèê: åñëè íàø ïîòîê áîëåå íèêåì íå þçàåòñÿ, òî óãðîáèòü åãî íàôèã
681 if not used then
682 begin
683 me := volumes.IndexOf(self);
684 f := volumes.Count-1;
685 while not used and (f >= 0) do
686 begin
687 if (f <> me) and (volumes[f] <> nil) then
688 begin
689 used := (TVolumeInfo(volumes[f]).fStream = fStream);
690 if not used then
691 begin
692 used := (TVolumeInfo(volumes[f]).fVolume.fFileStream = fStream);
693 end;
694 if used then break;
695 end;
696 Dec(f);
697 end;
698 end;
699 if not used then FreeAndNil(fStream); // åñëè áîëüøå íèêåì íå þçàíî, ïðèøèá¸ì
700 inherited Destroy();
701 end;
704 { TOwnedPartialStream }
705 constructor TOwnedPartialStream.Create (pOwner: TVolumeInfo; pSrc: TStream;
706 pPos, pSize: Int64; pKillSrc: Boolean);
707 begin
708 inherited Create(pSrc, pPos, pSize, pKillSrc);
709 fOwner := pOwner;
710 if pOwner <> nil then Inc(pOwner.fOpenedFilesCount);
711 end;
713 destructor TOwnedPartialStream.Destroy ();
714 var
715 f: Integer;
716 begin
717 inherited Destroy();
718 if fOwner <> nil then
719 begin
720 Dec(fOwner.fOpenedFilesCount);
721 if (gcdisabled = 0) and not fOwner.fPermanent and (fOwner.fOpenedFilesCount < 1) then
722 begin
723 f := volumes.IndexOf(fOwner);
724 if f <> -1 then
725 begin
726 {$IFDEF SFS_VOLDEBUG}writeln('001: destroying volume "', TVolumeInfo(volumes[f]).fPackName, '"');{$ENDIF}
727 volumes[f] := nil; // this will destroy the volume
728 end;
729 end;
730 end;
731 end;
734 { TSFSFileInfo }
735 constructor TSFSFileInfo.Create (pOwner: TSFSVolume);
736 begin
737 inherited Create();
738 fOwner := pOwner;
739 fPath := '';
740 fName := '';
741 fSize := 0;
742 fOfs := 0;
743 if pOwner <> nil then pOwner.fFiles.Add(self);
744 end;
746 destructor TSFSFileInfo.Destroy ();
747 begin
748 if fOwner <> nil then fOwner.fFiles.Extract(self);
749 inherited Destroy();
750 end;
753 { TSFSVolume }
754 constructor TSFSVolume.Create (const pFileName: AnsiString; pSt: TStream);
755 begin
756 inherited Create();
757 fFileStream := pSt;
758 fFileName := pFileName;
759 fFiles := TObjectList.Create(true);
760 end;
762 procedure TSFSVolume.DoDirectoryRead ();
763 var
764 f, c: Integer;
765 sfi: TSFSFileInfo;
766 tmp: AnsiString;
767 begin
768 fFileName := ExpandFileName(SFSReplacePathDelims(fFileName, '/'));
769 ReadDirectory();
770 fFiles.Pack();
772 f := 0;
773 while f < fFiles.Count do
774 begin
775 sfi := TSFSFileInfo(fFiles[f]);
776 // normalize name & path
777 sfi.fPath := SFSReplacePathDelims(sfi.fPath, '/');
778 if (sfi.fPath <> '') and (sfi.fPath[1] = '/') then Delete(sfi.fPath, 1, 1);
779 if (sfi.fPath <> '') and (sfi.fPath[Length(sfi.fPath)] <> '/') then sfi.fPath := sfi.fPath+'/';
780 tmp := SFSReplacePathDelims(sfi.fName, '/');
781 c := Length(tmp); while (c > 0) and (tmp[c] <> '/') do Dec(c);
782 if c > 0 then
783 begin
784 // split path and name
785 Delete(sfi.fName, 1, c); // cut name
786 tmp := Copy(tmp, 1, c); // get path
787 if tmp = '/' then tmp := ''; // just delimiter; ignore it
788 sfi.fPath := sfi.fPath+tmp;
789 end;
790 sfi.fPath := normalizePath(sfi.fPath);
791 if (length(sfi.fPath) = 0) and (length(sfi.fName) = 0) then sfi.Free else Inc(f);
792 end;
793 end;
795 destructor TSFSVolume.Destroy ();
796 begin
797 Clear();
798 FreeAndNil(fFiles);
799 inherited Destroy();
800 end;
802 procedure TSFSVolume.Clear ();
803 begin
804 fFiles.Clear();
805 end;
807 function TSFSVolume.FindFile (const fPath, fName: AnsiString): Integer;
808 begin
809 if fFiles = nil then result := -1
810 else
811 begin
812 result := fFiles.Count;
813 while result > 0 do
814 begin
815 Dec(result);
816 if fFiles[result] <> nil then
817 begin
818 if StrEquCI1251(fPath, TSFSFileInfo(fFiles[result]).fPath) and
819 StrEquCI1251(fName, TSFSFileInfo(fFiles[result]).fName) then exit;
820 end;
821 end;
822 result := -1;
823 end;
824 end;
826 function TSFSVolume.GetFileCount (): Integer;
827 begin
828 if fFiles = nil then result := 0 else result := fFiles.Count;
829 end;
831 function TSFSVolume.GetFiles (index: Integer): TSFSFileInfo;
832 begin
833 if fFiles = nil then result := nil
834 else
835 begin
836 if (index < 0) or (index >= fFiles.Count) then result := nil
837 else result := TSFSFileInfo(fFiles[index]);
838 end;
839 end;
841 function TSFSVolume.OpenFileEx (const fName: AnsiString): TStream;
842 var
843 fp, fn: AnsiString;
844 f, ls: Integer;
845 begin
846 fp := fName;
847 // normalize name, find split position
848 if (fp <> '') and ((fp[1] = '/') or (fp[1] = '\')) then Delete(fp, 1, 1);
849 ls := 0;
850 for f := 1 to Length(fp) do
851 begin
852 if fp[f] = '\' then fp[f] := '/';
853 if fp[f] = '/' then ls := f;
854 end;
855 fn := Copy(fp, ls+1, Length(fp));
856 fp := Copy(fp, 1, ls);
857 f := FindFile(fp, fn);
858 if f = -1 then raise ESFSError.Create('file not found: "'+fName+'"');
859 result := OpenFileByIndex(f);
860 if result = nil then raise ESFSError.Create('file not found: "'+fName+'"');
861 end;
864 { TSFSFileList }
865 constructor TSFSFileList.Create (const pVolume: TSFSVolume);
866 var
867 f: Integer;
868 begin
869 inherited Create();
870 ASSERT(pVolume <> nil);
871 f := FindVolumeInfoByVolumeInstance(pVolume);
872 ASSERT(f <> -1);
873 fVolume := pVolume;
874 Inc(TVolumeInfo(volumes[f]).fOpenedFilesCount); // íå ïîçâîëèì óáèòü çàïèñü!
875 end;
877 destructor TSFSFileList.Destroy ();
878 var
879 f: Integer;
880 begin
881 f := FindVolumeInfoByVolumeInstance(fVolume);
882 ASSERT(f <> -1);
883 Dec(TVolumeInfo(volumes[f]).fOpenedFilesCount);
884 // óáü¸ì çàïèñü, åñëè îíà âðåìåííàÿ, è â íåé íåò áîëüøå íè÷åãî îòêðûòîãî
885 if (gcdisabled = 0) and not TVolumeInfo(volumes[f]).fPermanent and (TVolumeInfo(volumes[f]).fOpenedFilesCount < 1) then
886 begin
887 {$IFDEF SFS_VOLDEBUG}writeln('002: destroying volume "', TVolumeInfo(volumes[f]).fPackName, '"');{$ENDIF}
888 volumes[f] := nil;
889 end;
890 inherited Destroy();
891 end;
893 function TSFSFileList.GetCount (): Integer;
894 begin
895 result := fVolume.fFiles.Count;
896 end;
898 function TSFSFileList.GetFiles (index: Integer): TSFSFileInfo;
899 begin
900 if (index < 0) or (index >= fVolume.fFiles.Count) then result := nil
901 else result := TSFSFileInfo(fVolume.fFiles[index]);
902 end;
905 procedure SFSRegisterVolumeFactory (factory: TSFSVolumeFactory);
906 var
907 f: Integer;
908 begin
909 if factory = nil then exit;
910 if factories.IndexOf(factory) <> -1 then
911 raise ESFSError.Create('duplicate factories are not allowed');
912 f := factories.IndexOf(nil);
913 if f = -1 then factories.Add(factory) else factories[f] := factory;
914 end;
916 procedure SFSUnregisterVolumeFactory (factory: TSFSVolumeFactory);
917 var
918 f: Integer;
919 c: Integer;
920 begin
921 if factory = nil then exit;
922 f := factories.IndexOf(factory);
923 if f = -1 then raise ESFSError.Create('can''t unregister nonexisting factory');
924 c := 0; while c < volumes.Count do
925 begin
926 if (volumes[c] <> nil) and (TVolumeInfo(volumes[c]).fFactory = factory) then volumes[c] := nil;
927 Inc(c);
928 end;
929 factories[f] := nil;
930 end;
933 function SFSAddDataFileEx (dataFileName: AnsiString; ds: TStream; top, permanent: Integer): Integer;
934 // dataFileName ìîæåò èìåòü ïðåôèêñ òèïà "zip:" (ñì. âûøå: IsMyPrefix).
935 // ìîæåò âûêèíóòü èñêëþ÷åíèå!
936 // top:
937 // <0: äîáàâèòü â íà÷àëî ñïèñêà ïîèñêà.
938 // =0: íå ìåíÿòü.
939 // >0: äîáàâèòü â êîíåö ñïèñêà ïîèñêà.
940 // permanent:
941 // <0: ñîçäàòü "âðåìåííûé" òîì.
942 // =0: íå ìåíÿòü ôëàæîê ïîñòîÿíñòâà.
943 // >0: ñîçäàòü "ïîñòîÿííûé" òîì.
944 // åñëè ds <> nil, òî ñîçäà¸ò ñáîðíèê èç ïîòîêà. åñëè ñáîðíèê ñ èìåíåì
945 // dataFileName óæå çàðåãèñòðèðîâàí, òî ïàäàåò íàôèã.
946 // âîçâðàùàåò èíäåêñ â volumes.
947 // óìååò äåëàòü ðåêóðñèþ.
948 var
949 fac: TSFSVolumeFactory;
950 vol: TSFSVolume;
951 vi: TVolumeInfo;
952 f: Integer;
953 st, st1: TStream;
954 pfx: AnsiString;
955 fn, vfn, tmp: AnsiString;
956 begin
957 f := Pos('::', dataFileName);
958 if f <> 0 then
959 begin
960 // ðåêóðñèâíîå îòêðûòèå.
961 // ðàçîáü¸ì dataFileName íà èìÿ ñáîðíèêà è îñòàòîê.
962 // pfx áóäåò èìåíåì ñáîðíèêà, dataFileName -- îñòàòêîì.
963 pfx := Copy(dataFileName, 1, f-1); Delete(dataFileName, 1, f+1);
964 // ñíà÷àëà îòêðîåì ïåðâûé ñïèñîê...
965 result := SFSAddDataFileEx(pfx, ds, 0, 0);
966 // ...òåïåðü ïðîäîëæèì ñ îñòàòêîì.
967 // óçíàåì, êàêîå ôàéëî îòêðûâàòü.
968 // âûêîâûðÿåì ïåðâûé "::" ïðåôèêñ (ýòî áóäåò èìÿ ôàéëà).
969 f := Pos('::', dataFileName); if f = 0 then f := Length(dataFileName)+1;
970 fn := Copy(dataFileName, 1, f-1); Delete(dataFileName, 1, f-1);
971 // dataFileName õðàíèò îñòàòîê.
972 // èçâëå÷¸ì èìÿ ôàéëà:
973 SplitDataName(fn, pfx, tmp, vfn);
974 // îòêðîåì ýòîò ôàéë
975 vi := TVolumeInfo(volumes[result]); st := nil;
976 try
977 st := vi.fVolume.OpenFileEx(tmp);
978 st1 := TOwnedPartialStream.Create(vi, st, 0, st.Size, true);
979 except
980 FreeAndNil(st);
981 // óäàëèì íåèñïîëüçóåìûé âðåìåííûé òîì.
982 if (gcdisabled = 0) and not vi.fPermanent and (vi.fOpenedFilesCount < 1) then volumes[result] := nil;
983 raise;
984 end;
985 // óðà. îòêðûëè ôàéë. êèäàåì â âîçäóõ ÷åï÷èêè, ïðîäîëæàåì ðàçâëå÷åíèå.
986 fn := fn+dataFileName;
987 try
988 st1.Position := 0;
989 result := SFSAddDataFileEx(fn, st1, top, permanent);
990 except
991 st1.Free(); // à âîò íå çàëàäèëîñü. çàêðûëè îòêðûòîå ôàéëî, âûëåòåëè.
992 raise;
993 end;
994 exit;
995 end;
997 // îáûêíîâåííîå íåðåêóðñèâíîå îòêðûòèå.
998 SplitDataName(dataFileName, pfx, fn, vfn);
1000 f := FindVolumeInfo(vfn);
1001 if f <> -1 then
1002 begin
1003 if ds <> nil then raise ESFSError.Create('subdata name conflict');
1004 if permanent <> 0 then TVolumeInfo(volumes[f]).fPermanent := (permanent > 0);
1005 if top = 0 then result := f
1006 else if top < 0 then result := 0
1007 else result := volumes.Count-1;
1008 if result <> f then volumes.Move(f, result);
1009 exit;
1010 end;
1012 if ds <> nil then st := ds
1013 else st := TFileStream.Create(fn, fmOpenRead or {fmShareDenyWrite}fmShareDenyNone);
1014 st.Position := 0;
1016 volumes.Pack();
1018 fac := nil; vol := nil;
1019 try
1020 for f := 0 to factories.Count-1 do
1021 begin
1022 fac := TSFSVolumeFactory(factories[f]);
1023 if fac = nil then continue;
1024 if (pfx <> '') and not fac.IsMyVolumePrefix(pfx) then continue;
1025 st.Position := 0;
1026 try
1027 if ds <> nil then vol := fac.Produce(pfx, '', st)
1028 else vol := fac.Produce(pfx, fn, st);
1029 except
1030 vol := nil;
1031 end;
1032 if vol <> nil then break;
1033 end;
1034 if vol = nil then raise ESFSError.Create('no factory for "'+dataFileName+'"');
1035 except
1036 if st <> ds then st.Free();
1037 raise;
1038 end;
1040 vi := TVolumeInfo.Create();
1041 try
1042 if top < 0 then
1043 begin
1044 result := 0;
1045 volumes.Insert(0, vi);
1046 end
1047 else result := volumes.Add(vi);
1048 except
1049 vol.Free();
1050 if st <> ds then st.Free();
1051 vi.Free();
1052 raise;
1053 end;
1055 vi.fFactory := fac;
1056 vi.fVolume := vol;
1057 vi.fPackName := vfn;
1058 vi.fStream := st;
1059 vi.fPermanent := (permanent > 0);
1060 vi.fNoDiskFile := (ds <> nil);
1061 vi.fOpenedFilesCount := 0;
1062 end;
1064 function SFSAddSubDataFile (const virtualName: AnsiString; ds: TStream; top: Boolean=false): Boolean;
1065 var
1066 tv: Integer;
1067 begin
1068 ASSERT(ds <> nil);
1069 try
1070 if top then tv := -1 else tv := 1;
1071 SFSAddDataFileEx(virtualName, ds, tv, 0);
1072 result := true;
1073 except
1074 result := false;
1075 end;
1076 end;
1078 function SFSAddDataFile (const dataFileName: AnsiString; top: Boolean=false): Boolean;
1079 var
1080 tv: Integer;
1081 begin
1082 try
1083 if top then tv := -1 else tv := 1;
1084 SFSAddDataFileEx(dataFileName, nil, tv, 1);
1085 result := true;
1086 except
1087 result := false;
1088 end;
1089 end;
1091 function SFSAddDataFileTemp (const dataFileName: AnsiString; top: Boolean=false): Boolean;
1092 var
1093 tv: Integer;
1094 begin
1095 try
1096 if top then tv := -1 else tv := 1;
1097 SFSAddDataFileEx(dataFileName, nil, tv, 0);
1098 result := true;
1099 except
1100 result := false;
1101 end;
1102 end;
1106 function SFSExpandDirName (const s: AnsiString): AnsiString;
1107 var
1108 f, e: Integer;
1109 es: AnsiString;
1110 begin
1111 f := 1; result := s;
1112 while f < Length(result) do
1113 begin
1114 while (f < Length(result)) and (result[f] <> '<') do Inc(f);
1115 if f >= Length(result) then exit;
1116 e := f; while (e < Length(result)) and (result[e] <> '>') do Inc(e);
1117 es := Copy(result, f, e+1-f);
1119 if es = '<currentdir>' then es := GetCurrentDir
1120 else if es = '<exedir>' then es := ExtractFilePath(ParamStr(0))
1121 else es := '';
1123 if es <> '' then
1124 begin
1125 if (es[Length(es)] <> '/') and (es[Length(es)] <> '\') then es := es+'/';
1126 Delete(result, f, e+1-f);
1127 Insert(es, result, f);
1128 Inc(f, Length(es));
1129 end
1130 else f := e+1;
1131 end;
1132 end;
1134 function SFSFileOpenEx (const fName: AnsiString): TStream;
1135 var
1136 dataFileName, fn: AnsiString;
1137 f: Integer;
1138 vi: TVolumeInfo;
1139 diskChecked: Boolean;
1140 ps: TStream;
1142 function CheckDisk (): TStream;
1143 // ïðîâåðèì, åñòü ëè ôàëî fn ãäå-òî íà äèñêàõ.
1144 var
1145 dfn, dirs, cdir: AnsiString;
1146 f: Integer;
1147 begin
1148 result := nil;
1149 if diskChecked or not sfsDiskEnabled then exit;
1150 diskChecked := true;
1151 dfn := SFSReplacePathDelims(fn, '/');
1152 dirs := sfsDiskDirs; if dirs = '' then dirs := '<currentdir>';
1153 while dirs <> '' do
1154 begin
1155 f := 1; while (f <= Length(dirs)) and (dirs[f] <> '|') do Inc(f);
1156 cdir := Copy(dirs, 1, f-1); Delete(dirs, 1, f);
1157 if cdir = '' then continue;
1158 cdir := SFSReplacePathDelims(SFSExpandDirName(cdir), '/');
1159 if cdir[Length(cdir)] <> '/' then cdir := cdir+'/';
1160 try
1161 result := TFileStream.Create(cdir+dfn, fmOpenRead or {fmShareDenyWrite}fmShareDenyNone);
1162 exit;
1163 except
1164 end;
1165 end;
1166 end;
1168 begin
1169 SplitFName(fName, dataFileName, fn);
1170 if fn = '' then raise ESFSError.Create('invalid file name: "'+fName+'"');
1172 diskChecked := false;
1174 if dataFileName <> '' then
1175 begin
1176 // ïðåôèêñîâàíûé ôàéë
1177 if sfsForceDiskForPrefixed then
1178 begin
1179 result := CheckDisk();
1180 if result <> nil then exit;
1181 end;
1183 f := SFSAddDataFileEx(dataFileName, nil, 0, 0);
1184 vi := TVolumeInfo(volumes[f]);
1186 try
1187 result := vi.fVolume.OpenFileEx(fn);
1188 ps := TOwnedPartialStream.Create(vi, result, 0, result.Size, true);
1189 except
1190 result.Free();
1191 if (gcdisabled = 0) and not vi.fPermanent and (vi.fOpenedFilesCount < 1) then volumes[f] := nil;
1192 result := CheckDisk(); // îáëîì ñ datafile, ïðîâåðèì äèñê
1193 if result = nil then raise ESFSError.Create('file not found: "'+fName+'"');
1194 exit;
1195 end;
1196 //Inc(vi.fOpenedFilesCount);
1197 result := ps;
1198 exit;
1199 end;
1201 // íåïðåôèêñîâàíûé ôàéë
1202 if sfsDiskFirst then
1203 begin
1204 result := CheckDisk();
1205 if result <> nil then exit;
1206 end;
1207 // èùåì ïî âñåì ïåðìàíåíòíûì ïðåôèêñàì
1208 f := 0;
1209 while f < volumes.Count do
1210 begin
1211 vi := TVolumeInfo(volumes[f]);
1212 if (vi <> nil) and vi.fPermanent then
1213 begin
1214 if vi.fVolume <> nil then
1215 begin
1216 result := vi.fVolume.OpenFileEx(fn);
1217 if result <> nil then
1218 begin
1219 try
1220 ps := TOwnedPartialStream.Create(vi, result, 0, result.Size, true);
1221 result := ps;
1222 //Inc(vi.fOpenedFilesCount);
1223 except
1224 FreeAndNil(result);
1225 end;
1226 end;
1227 if result <> nil then exit;
1228 end;
1229 end;
1230 Inc(f);
1231 end;
1232 result := CheckDisk();
1233 if result = nil then raise ESFSError.Create('file not found: "'+fName+'"');
1234 end;
1236 function SFSFileOpen (const fName: AnsiString): TStream;
1237 begin
1238 try
1239 result := SFSFileOpenEx(fName);
1240 except
1241 result := nil;
1242 end;
1243 end;
1245 function SFSFileList (const dataFileName: AnsiString): TSFSFileList;
1246 var
1247 f: Integer;
1248 vi: TVolumeInfo;
1249 begin
1250 result := nil;
1251 if dataFileName = '' then exit;
1253 try
1254 f := SFSAddDataFileEx(dataFileName, nil, 0, 0);
1255 except
1256 exit;
1257 end;
1258 vi := TVolumeInfo(volumes[f]);
1260 try
1261 result := TSFSFileList.Create(vi.fVolume);
1262 except
1263 if (gcdisabled = 0) and not vi.fPermanent and (vi.fOpenedFilesCount < 1) then volumes[f] := nil;
1264 end;
1265 end;
1268 initialization
1269 factories := TObjectList.Create(true);
1270 volumes := TObjectList.Create(true);
1271 //finalization
1272 //volumes.Free(); // it fails for some reason... Runtime 217 (^C hit). wtf?!
1273 //factories.Free(); // not need to be done actually...
1274 end.