1 // streaming file system (virtual)
4 {.$DEFINE SFS_VOLDEBUG}
10 SysUtils
, Classes
, Contnrs
;
14 ESFSError
= class(Exception
);
17 TSFSString
= AnsiString;
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
;
37 // âèðòóàëüíàÿ ôàéëîâàÿ ñèñòåìà. ÒÎËÜÊÎ ÄËß ×ÒÅÍÈß!
38 // òîì ÍÅ ÄÎËÆÅÍ óáèâàòüñÿ íèêàê èíà÷å, ÷åì ïðè ïîìîùè ôàáðèêè!
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;
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
;
103 // ôàáðèêà òîìîâ. âñå SFS ïðè ñòàðòå äîáàâëÿþò ñâîè ôàáðèêè.
104 // áëàãîäàðÿ ýòîìó ìîæíî ñîçäàâàòü ðàçíûå âñÿêèå SFS ñòàíäàðòíûì
105 // âûçîâîì ñòàíäàðòíîé ïðîöåäóðû.
106 // ôàáðèêà ÍÅ ÄÎËÆÍÀ óáèâàòüñÿ íèêàê èíà÷å, ÷åì ïðè ïîìîùè âûçîâà
107 // SFSUnregisterVolumeFactory()! ýòî ãàðàíòèðóåò, ÷òî äâèæîê
108 // ïåðåä ðàññòðåëîì îòäàñò åé âñå å¸ òîìà.
109 TSFSVolumeFactory
= class
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;
129 // "èòåðàòîð", âîçâðàùàåìûé SFSFileList()
134 function GetCount (): Integer;
135 function GetFiles (index
: Integer): TSFSFileInfo
;
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
;
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;
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:
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
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'
243 function WildMatch (pattern
, text: TSFSString
): Boolean;
244 function WildListMatch (wildList
, text: TSFSString
; delimChar
: AnsiChar=':'): Integer;
245 function HasWildcards (const pattern
: TSFSString
): Boolean;
247 // this will compare only last path element from sfspath
248 function SFSDFPathEqu (sfspath
: string; path
: string): Boolean;
250 function SFSUpCase (ch
: Char): Char;
252 function utf8to1251 (s
: TSFSString
): TSFSString
;
256 // ïðàâäà: ðàçðåøåíî èñêàòü ôàéëî íå òîëüêî â ôàéëàõ äàííûõ, íî è íà äèñêå.
257 sfsDiskEnabled
: Boolean = true;
258 // ïðàâäà: åñëè ôàéë íå ïðåôèêñîâàí, òî ñíà÷àëà èùåì ôàéëî íà äèñêå,
259 // ïîòîì â ôàéëàõ äàííûõ.
260 sfsDiskFirst
: Boolean = true;
261 // ïðàâäà: äàæå äëÿ ïðåôèêñîâàíûõ ôàéëîâ ñíà÷àëà ïðîñìîòðèì äèñê
262 // (åñëè óñòàíîâëåí ôëàæîê sfsDiskFirst è sfsDiskEnabled).
263 sfsForceDiskForPrefixed
: Boolean = false;
264 // ñïèñîê äèñêîâûõ êàòàëîãîâ äëÿ ïîèñêà ôàéëà. åñëè ïóñò -- èùåì òîëüêî â
265 // òåêóùåì. êàòàëîãè ðàçäåëÿþòñÿ òðóáîé ("|").
266 // <currentdir> çàìåíÿåòñÿ íà òåêóùèé êàòàëîã (ñ çàâåðøàþùèì "/"),
267 // <exedir> çàìåíÿåòñÿ íà êàòàëîã, ãäå ñèäèò .EXE (ñ çàâåðøàþùèì "/").
268 sfsDiskDirs
: TSFSString
= '<currentdir>|<exedir>';
277 function Int64ToStrComma (i
: Int64): string;
282 f
:= Length(result
)+1;
285 Dec(f
, 3); Insert(',', result
, f
);
290 // `name` will be modified
291 function sfsFindFileCI (path
: string; var name
: string): Boolean;
294 bestname
: string = '';
296 if length(path
) = 0 then path
:= '.';
297 while (length(path
) > 0) and (path
[length(path
)] = '/') do Delete(path
, length(path
), 1);
298 if (length(path
) = 0) or (path
[length(path
)] <> '/') then path
:= path
+'/';
299 if FileExists(path
+name
) then begin result
:= true; exit
; end;
300 if FindFirst(path
+'*', faAnyFile
, sr
) = 0 then
302 if (sr
.name
= '.') or (sr
.name
= '..') then continue
;
303 if (sr
.attr
and faDirectory
) <> 0 then continue
;
304 if sr
.name
= name
then
310 if (length(bestname
) = 0) and SFSStrEqu(sr
.name
, name
) then bestname
:= sr
.name
;
311 until FindNext(sr
) <> 0;
313 if length(bestname
) > 0 then begin result
:= true; name
:= bestname
; end else result
:= false;
319 WILD_CHAR_ESCAPE
= '\';
320 WILD_CHAR_SINGLE
= '?';
321 WILD_CHAR_SINGLE_OR_NONE
= '+';
322 WILD_CHAR_MULTI
= '*';
323 WILD_CHAR_RANGE_OPEN
= '[';
324 WILD_CHAR_RANGE
= '-';
325 WILD_CHAR_RANGE_CLOSE
= ']';
326 WILD_CHAR_RANGE_NOT
= '!';
329 function HasWildcards (const pattern
: TSFSString
): Boolean;
332 (Pos(WILD_CHAR_ESCAPE
, pattern
) <> 0) or
333 (Pos(WILD_CHAR_SINGLE
, pattern
) <> 0) or
334 (Pos(WILD_CHAR_SINGLE_OR_NONE
, pattern
) <> 0) or
335 (Pos(WILD_CHAR_MULTI
, pattern
) <> 0) or
336 (Pos(WILD_CHAR_RANGE_OPEN
, pattern
) <> 0);
339 function MatchMask (const pattern
: TSFSString
; p
, pend
: Integer; const text: TSFSString
; t
, tend
: Integer): Boolean;
341 rangeStart
, rangeEnd
: AnsiChar;
342 rangeNot
, rangeMatched
: Boolean;
346 if (pend
< 0) or (pend
> Length(pattern
)) then pend
:= Length(pattern
);
347 if (tend
< 0) or (tend
> Length(text)) then tend
:= Length(text);
348 if t
< 1 then t
:= 1;
349 if p
< 1 then p
:= 1;
354 // no more text. check if there's no more chars in pattern (except "*" & "+")
355 while (p
<= pend
) and
356 ((pattern
[p
] = WILD_CHAR_MULTI
) or
357 (pattern
[p
] = WILD_CHAR_SINGLE_OR_NONE
)) do Inc(p
);
358 result
:= (p
> pend
);
366 if p
> pend
then result
:= false else result
:= (pattern
[p
] = text[t
]);
367 if not result
then exit
;
369 WILD_CHAR_RANGE_OPEN
:
372 Inc(p
); if p
> pend
then exit
; // sanity check
373 rangeNot
:= (pattern
[p
] = WILD_CHAR_RANGE_NOT
);
374 if rangeNot
then begin Inc(p
); if p
> pend
then exit
; {sanity check} end;
375 if pattern
[p
] = WILD_CHAR_RANGE_CLOSE
then exit
; // sanity check
376 ch
:= text[t
]; // speed reasons
377 rangeMatched
:= false;
379 if p
> pend
then exit
; // sanity check
380 rangeStart
:= pattern
[p
];
381 if rangeStart
= WILD_CHAR_RANGE_CLOSE
then break
;
382 Inc(p
); if p
> pend
then exit
; // sanity check
383 if pattern
[p
] = WILD_CHAR_RANGE
then
385 Inc(p
); if p
> pend
then exit
; // sanity check
386 rangeEnd
:= pattern
[p
]; Inc(p
);
387 if rangeStart
< rangeEnd
then
389 rangeMatched
:= (ch
>= rangeStart
) and (ch
<= rangeEnd
);
391 else rangeMatched
:= (ch
>= rangeEnd
) and (ch
<= rangeStart
);
393 else rangeMatched
:= (ch
= rangeStart
);
395 if rangeNot
= rangeMatched
then exit
;
397 // skip the rest or the range
398 while (p
<= pend
) and (pattern
[p
] <> WILD_CHAR_RANGE_CLOSE
) do Inc(p
);
399 if p
> pend
then exit
; // sanity check
401 WILD_CHAR_SINGLE_OR_NONE
:
404 result
:= MatchMask(pattern
, p
, pend
, text, t
, tend
);
405 if not result
then result
:= MatchMask(pattern
, p
, pend
, text, t
+1, tend
);
410 while (p
<= pend
) and (pattern
[p
] = WILD_CHAR_MULTI
) do Inc(p
);
411 result
:= (p
> pend
); if result
then exit
;
412 while not result
and (t
<= tend
) do
414 result
:= MatchMask(pattern
, p
, pend
, text, t
, tend
);
419 else result
:= (pattern
[p
] = text[t
]); if not result
then exit
;
423 result
:= (t
> tend
);
427 function WildMatch (pattern
, text: TSFSString
): Boolean;
429 if pattern
<> '' then pattern
:= AnsiLowerCase(pattern
);
430 if text <> '' then text := AnsiLowerCase(text);
431 result
:= MatchMask(pattern
, 1, -1, text, 1, -1);
434 function WildListMatch (wildList
, text: TSFSString
; delimChar
: AnsiChar=':'): Integer;
438 if wildList
<> '' then wildList
:= AnsiLowerCase(wildList
);
439 if text <> '' then text := AnsiLowerCase(text);
442 while s
<= Length(wildList
) do
444 e
:= s
; while e
<= Length(wildList
) do
446 if wildList
[e
] = WILD_CHAR_RANGE_OPEN
then
448 while (e
<= Length(wildList
)) and (wildList
[e
] <> WILD_CHAR_RANGE_CLOSE
) do Inc(e
);
450 if wildList
[e
] = delimChar
then break
;
455 if MatchMask(wildList
, s
, e
-1, text, 1, -1) then exit
;
466 fFactory
: TSFSVolumeFactory
;
468 fPackName
: TSFSString
; // äëÿ îäíîãî è òîãî æå ôàéëà áóäåò òîëüêî îäèí òîì!
469 fStream
: TStream
; // ôàéëîâûé ïîòîê äëÿ ñáîðíèêà
470 fPermanent
: Boolean; // èñòèíà -- íå áóäåò óãðîáëåíà, åñëè íå îñòàíåòñÿ íè îäíîãî îòêðûòîãî òîìà
471 // èñòèíà -- ýòîò òîì áûë ñîçäàí èç ïîòîêà è íå èìååò äèñêîâîãî ôàéëà, ïîòîìó ôàáðèêå áóäåò ïåðåäàíî íå èìÿ ñáîðíèêà, à ïóñòàÿ ñòðîêà
472 fNoDiskFile
: Boolean;
473 fOpenedFilesCount
: Integer;
475 destructor Destroy (); override;
478 TOwnedPartialStream
= class (TSFSPartialStream
)
483 constructor Create (pOwner
: TVolumeInfo
; pSrc
: TStream
; pPos
, pSize
: Int64; pKillSrc
: Boolean);
484 destructor Destroy (); override;
489 factories
: TObjectList
; // TSFSVolumeFactory
490 volumes
: TObjectList
; // TVolumeInfo
491 gcdisabled
: Integer = 0; // >0: disabled
494 procedure sfsGCCollect ();
502 while f
< volumes
.Count
do
504 vi
:= TVolumeInfo(volumes
[f
]);
505 if vi
= nil then continue
;
506 if (not vi
.fPermanent
) and (vi
.fVolume
.fRC
= 0) and (vi
.fOpenedFilesCount
= 0) then
508 // this volume probably can be removed
510 c
:= volumes
.Count
-1;
511 while not used
and (c
>= 0) do
513 if (c
<> f
) and (volumes
[c
] <> nil) then
515 used
:= (TVolumeInfo(volumes
[c
]).fStream
= vi
.fStream
);
516 if not used
then used
:= (TVolumeInfo(volumes
[c
]).fVolume
.fFileStream
= vi
.fStream
);
523 {$IFDEF SFS_VOLDEBUG}writeln('000: destroying volume "', TVolumeInfo(volumes
[f
]).fPackName
, '"');{$ENDIF}
524 volumes
.extract(vi
); // remove from list
530 Inc(f
); // next volume
534 procedure sfsGCDisable ();
539 procedure sfsGCEnable ();
542 if gcdisabled
<= 0 then
550 // ðàçáèòü èìÿ ôàéëà íà ÷àñòè: ïðåôèêñ ôàéëîâîé ñèñòåìû, èìÿ ôàéëà äàííûõ,
551 // ñîáñòâåííî èìÿ ôàéëà
553 // (("sfspfx:")?"datafile::")*"filename"
554 procedure SplitFName (const fn
: string; out dataFile
, fileName
: string);
561 if (fn
[f
] = ':') and (fn
[f
+1] = ':') then break
;
564 if f
< 1 then begin dataFile
:= ''; fileName
:= fn
; end
567 dataFile
:= Copy(fn
, 1, f
-1);
568 fileName
:= Copy(fn
, f
+2, maxInt
-10000);
572 // ñàéäýôôåêò: âûðåçàåò âèðòóàëüíîå èìÿ èç dataFile.
573 function ExtractVirtName (var dataFile
: string): string;
577 f
:= Length(dataFile
); result
:= dataFile
;
580 if dataFile
[f
] = ':' then break
;
581 if dataFile
[f
] = '|' then
583 if dataFile
[f
-1] = '|' then begin Dec(f
); Delete(dataFile
, f
, 1); end
586 result
:= Copy(dataFile
, f
+1, Length(dataFile
));
587 Delete(dataFile
, f
, Length(dataFile
));
595 // ðàçáèòü èìÿ ñáîðíèêà íà ÷àñòè: ïðåôèêñ ôàéëîâîé ñèñòåìû, èìÿ ôàéëà äàííûõ,
596 // âèðòóàëüíîå èìÿ. åñëè âèðòóàëüíîãî èìåíè íå äàíî, îíî áóäåò ðàâíî dataFile.
598 // [sfspfx:]datafile[|virtname]
599 // åñëè ïåðåä äâîåòî÷èåì ìåíüøå òð¸õ áóêâ, òî ýòî ñ÷èòàåòñÿ íå ïðåôèêñîì,
601 procedure SplitDataName (const fn
: string; out pfx
, dataFile
, virtName
: string);
606 if f
<= 3 then begin pfx
:= ''; dataFile
:= fn
; end
609 pfx
:= Copy(fn
, 1, f
-1);
610 dataFile
:= Copy(fn
, f
+1, maxInt
-10000);
612 virtName
:= ExtractVirtName(dataFile
);
615 // íàéòè ïðîèçâîäèòåëÿ äëÿ ýòîãî ôàéëà (åñëè ôàéë óæå îòêðûò).
616 // onlyPerm: òîëüêî "ïîñòîÿííûå" ïðîèçâîäèòåëè.
617 function FindVolumeInfo (const dataFileName
: TSFSString
; onlyPerm
: Boolean=false): Integer;
623 while f
< volumes
.Count
do
625 if volumes
[f
] <> nil then
627 vi
:= TVolumeInfo(volumes
[f
]);
628 if not onlyPerm
or vi
.fPermanent
then
630 if SFSStrEqu(vi
.fPackName
, dataFileName
) then
642 // íàéòè èíôó äëÿ ýòîãî òîìà.
643 // õîðîøåå èìÿ, ïðàâäà? %-)
644 function FindVolumeInfoByVolumeInstance (vol
: TSFSVolume
): Integer;
646 result
:= volumes
.Count
-1;
649 if volumes
[result
] <> nil then
651 if TVolumeInfo(volumes
[result
]).fVolume
= vol
then exit
;
657 function SFSUpCase (ch
: Char): Char;
661 if (ch
>= 'a') and (ch
<= 'z') then Dec(ch
, 32);
665 if (ch
>= #224) and (ch
<= #255) then
672 #184, #186, #191: Dec(ch
, 16);
680 function SFSStrEqu (const s0
, s1
: TSFSString
): Boolean;
684 //result := (AnsiCompareText(s0, s1) == 0);
686 if length(s0
) <> length(s1
) then exit
;
687 for i
:= 1 to length(s0
) do
689 if SFSUpCase(s0
[i
]) <> SFSUpCase(s1
[i
]) then exit
;
694 // this will compare only last path element from sfspath
695 function SFSDFPathEqu (sfspath
: string; path
: string): Boolean;
699 result
:= SFSStrEqu(sfspath
, path
);
701 if not result and (length(sfspath) > 1) then
703 i := length(sfspath);
706 while (i > 1) and (sfspath[i-1] <> '/') do Dec(i);
708 writeln('{', sfspath, '} [', Copy(sfspath, i, length(sfspath)), '] : [', path, ']');
709 result := SFSStrEqu(Copy(sfspath, i, length(sfspath)), path);
716 function normalizePath (fn
: string): string;
722 while i
<= length(fn
) do
724 if (fn
[i
] = '.') and ((length(fn
)-i
= 0) or (fn
[i
+1] = '/') or (fn
[i
+1] = '\')) then
729 if (fn
[i
] = '/') or (fn
[i
] = '\') then
731 if (length(result
) > 0) and (result
[length(result
)] <> '/') then result
:= result
+'/';
735 result
:= result
+fn
[i
];
739 if (length(result
) > 0) and (result
[length(result
)] <> '/') then result
:= result
+'/';
742 function SFSReplacePathDelims (const s
: TSFSString
; newDelim
: TSFSChar
): TSFSString
;
747 for f
:= 1 to Length(result
) do
749 if (result
[f
] = '/') or (result
[f
] = '\') then
751 // avoid unnecessary string changes
752 if result
[f
] <> newDelim
then result
[f
] := newDelim
;
757 function SFSGetLastVirtualName (const fn
: TSFSString
): string;
764 f
:= Pos('::', rest
); if f
= 0 then f
:= Length(rest
)+1;
765 tmp
:= Copy(rest
, 1, f
-1); Delete(rest
, 1, f
+1);
766 result
:= ExtractVirtName(tmp
);
772 destructor TVolumeInfo
.Destroy ();
775 used
: Boolean; // ôëàæîê çàþçàíîñòè ïîòîêà êåì-òî åù¸
777 if fFactory
<> nil then fFactory
.Recycle(fVolume
);
778 if fVolume
<> nil then used
:= (fVolume
.fRC
<> 0) else used
:= false;
783 // òèïà ìóñîðîñáîðíèê: åñëè íàø ïîòîê áîëåå íèêåì íå þçàåòñÿ, òî óãðîáèòü åãî íàôèã
786 me
:= volumes
.IndexOf(self
);
787 f
:= volumes
.Count
-1;
788 while not used
and (f
>= 0) do
790 if (f
<> me
) and (volumes
[f
] <> nil) then
792 used
:= (TVolumeInfo(volumes
[f
]).fStream
= fStream
);
795 used
:= (TVolumeInfo(volumes
[f
]).fVolume
.fFileStream
= fStream
);
802 if not used
then FreeAndNil(fStream
); // åñëè áîëüøå íèêåì íå þçàíî, ïðèøèá¸ì
807 { TOwnedPartialStream }
808 constructor TOwnedPartialStream
.Create (pOwner
: TVolumeInfo
; pSrc
: TStream
;
809 pPos
, pSize
: Int64; pKillSrc
: Boolean);
811 inherited Create(pSrc
, pPos
, pSize
, pKillSrc
);
813 if pOwner
<> nil then Inc(pOwner
.fOpenedFilesCount
);
816 destructor TOwnedPartialStream
.Destroy ();
821 if fOwner
<> nil then
823 Dec(fOwner
.fOpenedFilesCount
);
824 if (gcdisabled
= 0) and not fOwner
.fPermanent
and (fOwner
.fOpenedFilesCount
< 1) then
826 f
:= volumes
.IndexOf(fOwner
);
829 {$IFDEF SFS_VOLDEBUG}writeln('001: destroying volume "', TVolumeInfo(volumes
[f
]).fPackName
, '"');{$ENDIF}
830 volumes
[f
] := nil; // this will destroy the volume
838 constructor TSFSFileInfo
.Create (pOwner
: TSFSVolume
);
846 if pOwner
<> nil then pOwner
.fFiles
.Add(self
);
849 destructor TSFSFileInfo
.Destroy ();
851 if fOwner
<> nil then fOwner
.fFiles
.Extract(self
);
857 constructor TSFSVolume
.Create (const pFileName
: TSFSString
; pSt
: TStream
);
862 fFileName
:= pFileName
;
863 fFiles
:= TObjectList
.Create(true);
866 procedure TSFSVolume
.removeCommonPath ();
870 procedure TSFSVolume
.DoDirectoryRead ();
876 fFileName
:= ExpandFileName(SFSReplacePathDelims(fFileName
, '/'));
881 while f
< fFiles
.Count
do
883 sfi
:= TSFSFileInfo(fFiles
[f
]);
884 // normalize name & path
885 sfi
.fPath
:= SFSReplacePathDelims(sfi
.fPath
, '/');
886 if (sfi
.fPath
<> '') and (sfi
.fPath
[1] = '/') then Delete(sfi
.fPath
, 1, 1);
887 if (sfi
.fPath
<> '') and (sfi
.fPath
[Length(sfi
.fPath
)] <> '/') then sfi
.fPath
:= sfi
.fPath
+'/';
888 tmp
:= SFSReplacePathDelims(sfi
.fName
, '/');
889 c
:= Length(tmp
); while (c
> 0) and (tmp
[c
] <> '/') do Dec(c
);
892 // split path and name
893 Delete(sfi
.fName
, 1, c
); // cut name
894 tmp
:= Copy(tmp
, 1, c
); // get path
895 if tmp
= '/' then tmp
:= ''; // just delimiter; ignore it
896 sfi
.fPath
:= sfi
.fPath
+tmp
;
898 sfi
.fPath
:= normalizePath(sfi
.fPath
);
899 if (length(sfi
.fPath
) = 0) and (length(sfi
.fName
) = 0) then sfi
.Free
else Inc(f
);
904 destructor TSFSVolume
.Destroy ();
911 procedure TSFSVolume
.Clear ();
917 function TSFSVolume
.FindFile (const fPath
, fName
: TSFSString
): Integer;
919 if fFiles
= nil then result
:= -1
922 result
:= fFiles
.Count
;
926 if fFiles
[result
] <> nil then
928 if SFSStrEqu(fPath
, TSFSFileInfo(fFiles
[result
]).fPath
) and
929 SFSStrEqu(fName
, TSFSFileInfo(fFiles
[result
]).fName
) then exit
;
936 function TSFSVolume
.GetFileCount (): Integer;
938 if fFiles
= nil then result
:= 0 else result
:= fFiles
.Count
;
941 function TSFSVolume
.GetFiles (index
: Integer): TSFSFileInfo
;
943 if fFiles
= nil then result
:= nil
946 if (index
< 0) or (index
>= fFiles
.Count
) then result
:= nil
947 else result
:= TSFSFileInfo(fFiles
[index
]);
951 function TSFSVolume
.OpenFileEx (const fName
: TSFSString
): TStream
;
957 // normalize name, find split position
958 if (fp
<> '') and ((fp
[1] = '/') or (fp
[1] = '\')) then Delete(fp
, 1, 1);
960 for f
:= 1 to Length(fp
) do
962 if fp
[f
] = '\' then fp
[f
] := '/';
963 if fp
[f
] = '/' then ls
:= f
;
965 fn
:= Copy(fp
, ls
+1, Length(fp
));
966 fp
:= Copy(fp
, 1, ls
);
967 f
:= FindFile(fp
, fn
);
968 if f
= -1 then raise ESFSError
.Create('file not found: "'+fName
+'"');
969 result
:= OpenFileByIndex(f
);
970 if result
= nil then raise ESFSError
.Create('file not found: "'+fName
+'"');
975 constructor TSFSFileList
.Create (const pVolume
: TSFSVolume
);
980 ASSERT(pVolume
<> nil);
981 f
:= FindVolumeInfoByVolumeInstance(pVolume
);
984 Inc(TVolumeInfo(volumes
[f
]).fOpenedFilesCount
); // íå ïîçâîëèì óáèòü çàïèñü!
987 destructor TSFSFileList
.Destroy ();
991 f
:= FindVolumeInfoByVolumeInstance(fVolume
);
993 if fVolume
<> nil then Dec(fVolume
.fRC
);
994 Dec(TVolumeInfo(volumes
[f
]).fOpenedFilesCount
);
995 // óáü¸ì çàïèñü, åñëè îíà âðåìåííàÿ, è â íåé íåò áîëüøå íè÷åãî îòêðûòîãî
996 if (gcdisabled
= 0) and not TVolumeInfo(volumes
[f
]).fPermanent
and (TVolumeInfo(volumes
[f
]).fOpenedFilesCount
< 1) then
998 {$IFDEF SFS_VOLDEBUG}writeln('002: destroying volume "', TVolumeInfo(volumes
[f
]).fPackName
, '"');{$ENDIF}
1001 inherited Destroy();
1004 function TSFSFileList
.GetCount (): Integer;
1006 result
:= fVolume
.fFiles
.Count
;
1009 function TSFSFileList
.GetFiles (index
: Integer): TSFSFileInfo
;
1011 if (index
< 0) or (index
>= fVolume
.fFiles
.Count
) then result
:= nil
1012 else result
:= TSFSFileInfo(fVolume
.fFiles
[index
]);
1016 procedure SFSRegisterVolumeFactory (factory
: TSFSVolumeFactory
);
1020 if factory
= nil then exit
;
1021 if factories
.IndexOf(factory
) <> -1 then
1022 raise ESFSError
.Create('duplicate factories are not allowed');
1023 f
:= factories
.IndexOf(nil);
1024 if f
= -1 then factories
.Add(factory
) else factories
[f
] := factory
;
1027 procedure SFSUnregisterVolumeFactory (factory
: TSFSVolumeFactory
);
1032 if factory
= nil then exit
;
1033 f
:= factories
.IndexOf(factory
);
1034 if f
= -1 then raise ESFSError
.Create('can''t unregister nonexisting factory');
1035 c
:= 0; while c
< volumes
.Count
do
1037 if (volumes
[c
] <> nil) and (TVolumeInfo(volumes
[c
]).fFactory
= factory
) then volumes
[c
] := nil;
1040 factories
[f
] := nil;
1044 function SFSAddDataFileEx (dataFileName
: TSFSString
; ds
: TStream
; top
, permanent
: Integer): Integer;
1045 // dataFileName ìîæåò èìåòü ïðåôèêñ òèïà "zip:" (ñì. âûøå: IsMyPrefix).
1046 // ìîæåò âûêèíóòü èñêëþ÷åíèå!
1048 // <0: äîáàâèòü â íà÷àëî ñïèñêà ïîèñêà.
1050 // >0: äîáàâèòü â êîíåö ñïèñêà ïîèñêà.
1052 // <0: ñîçäàòü "âðåìåííûé" òîì.
1053 // =0: íå ìåíÿòü ôëàæîê ïîñòîÿíñòâà.
1054 // >0: ñîçäàòü "ïîñòîÿííûé" òîì.
1055 // åñëè ds <> nil, òî ñîçäà¸ò ñáîðíèê èç ïîòîêà. åñëè ñáîðíèê ñ èìåíåì
1056 // dataFileName óæå çàðåãèñòðèðîâàí, òî ïàäàåò íàôèã.
1057 // âîçâðàùàåò èíäåêñ â volumes.
1058 // óìååò äåëàòü ðåêóðñèþ.
1060 fac
: TSFSVolumeFactory
;
1066 fn
, vfn
, tmp
: TSFSString
;
1068 f
:= Pos('::', dataFileName
);
1071 // ðåêóðñèâíîå îòêðûòèå.
1072 // ðàçîáü¸ì dataFileName íà èìÿ ñáîðíèêà è îñòàòîê.
1073 // pfx áóäåò èìåíåì ñáîðíèêà, dataFileName -- îñòàòêîì.
1074 pfx
:= Copy(dataFileName
, 1, f
-1); Delete(dataFileName
, 1, f
+1);
1075 // ñíà÷àëà îòêðîåì ïåðâûé ñïèñîê...
1076 result
:= SFSAddDataFileEx(pfx
, ds
, 0, 0);
1077 // ...òåïåðü ïðîäîëæèì ñ îñòàòêîì.
1078 // óçíàåì, êàêîå ôàéëî îòêðûâàòü.
1079 // âûêîâûðÿåì ïåðâûé "::" ïðåôèêñ (ýòî áóäåò èìÿ ôàéëà).
1080 f
:= Pos('::', dataFileName
); if f
= 0 then f
:= Length(dataFileName
)+1;
1081 fn
:= Copy(dataFileName
, 1, f
-1); Delete(dataFileName
, 1, f
-1);
1082 // dataFileName õðàíèò îñòàòîê.
1083 // èçâëå÷¸ì èìÿ ôàéëà:
1084 SplitDataName(fn
, pfx
, tmp
, vfn
);
1085 // îòêðîåì ýòîò ôàéë
1086 vi
:= TVolumeInfo(volumes
[result
]); st
:= nil;
1088 st
:= vi
.fVolume
.OpenFileEx(tmp
);
1089 st1
:= TOwnedPartialStream
.Create(vi
, st
, 0, st
.Size
, true);
1092 // óäàëèì íåèñïîëüçóåìûé âðåìåííûé òîì.
1093 if (gcdisabled
= 0) and not vi
.fPermanent
and (vi
.fOpenedFilesCount
< 1) then volumes
[result
] := nil;
1096 // óðà. îòêðûëè ôàéë. êèäàåì â âîçäóõ ÷åï÷èêè, ïðîäîëæàåì ðàçâëå÷åíèå.
1097 fn
:= fn
+dataFileName
;
1100 result
:= SFSAddDataFileEx(fn
, st1
, top
, permanent
);
1102 st1
.Free(); // à âîò íå çàëàäèëîñü. çàêðûëè îòêðûòîå ôàéëî, âûëåòåëè.
1108 // îáûêíîâåííîå íåðåêóðñèâíîå îòêðûòèå.
1109 SplitDataName(dataFileName
, pfx
, fn
, vfn
);
1111 f
:= FindVolumeInfo(vfn
);
1114 if ds
<> nil then raise ESFSError
.Create('subdata name conflict');
1115 if permanent
<> 0 then TVolumeInfo(volumes
[f
]).fPermanent
:= (permanent
> 0);
1116 if top
= 0 then result
:= f
1117 else if top
< 0 then result
:= 0
1118 else result
:= volumes
.Count
-1;
1119 if result
<> f
then volumes
.Move(f
, result
);
1123 if ds
<> nil then st
:= ds
1124 else st
:= TFileStream
.Create(fn
, fmOpenRead
or fmShareDenyWrite
);
1129 fac
:= nil; vol
:= nil;
1131 for f
:= 0 to factories
.Count
-1 do
1133 fac
:= TSFSVolumeFactory(factories
[f
]);
1134 if fac
= nil then continue
;
1135 if (pfx
<> '') and not fac
.IsMyVolumePrefix(pfx
) then continue
;
1138 if ds
<> nil then vol
:= fac
.Produce(pfx
, '', st
)
1139 else vol
:= fac
.Produce(pfx
, fn
, st
);
1143 if vol
<> nil then break
;
1145 if vol
= nil then raise ESFSError
.Create('no factory for "'+dataFileName
+'"');
1147 if st
<> ds
then st
.Free();
1151 vi
:= TVolumeInfo
.Create();
1156 volumes
.Insert(0, vi
);
1158 else result
:= volumes
.Add(vi
);
1161 if st
<> ds
then st
.Free();
1168 vi
.fPackName
:= vfn
;
1170 vi
.fPermanent
:= (permanent
> 0);
1171 vi
.fNoDiskFile
:= (ds
<> nil);
1172 vi
.fOpenedFilesCount
:= 0;
1175 function SFSAddSubDataFile (const virtualName
: TSFSString
; ds
: TStream
; top
: Boolean=false): Boolean;
1181 if top
then tv
:= -1 else tv
:= 1;
1182 SFSAddDataFileEx(virtualName
, ds
, tv
, 0);
1189 function SFSAddDataFile (const dataFileName
: TSFSString
; top
: Boolean=false): Boolean;
1194 if top
then tv
:= -1 else tv
:= 1;
1195 SFSAddDataFileEx(dataFileName
, nil, tv
, 1);
1202 function SFSAddDataFileTemp (const dataFileName
: TSFSString
; top
: Boolean=false): Boolean;
1207 if top
then tv
:= -1 else tv
:= 1;
1208 SFSAddDataFileEx(dataFileName
, nil, tv
, 0);
1217 function SFSExpandDirName (const s
: TSFSString
): TSFSString
;
1222 f
:= 1; result
:= s
;
1223 while f
< Length(result
) do
1225 while (f
< Length(result
)) and (result
[f
] <> '<') do Inc(f
);
1226 if f
>= Length(result
) then exit
;
1227 e
:= f
; while (e
< Length(result
)) and (result
[e
] <> '>') do Inc(e
);
1228 es
:= Copy(result
, f
, e
+1-f
);
1230 if es
= '<currentdir>' then es
:= GetCurrentDir
1231 else if es
= '<exedir>' then es
:= ExtractFilePath(ParamStr(0))
1236 if (es
[Length(es
)] <> '/') and (es
[Length(es
)] <> '\') then es
:= es
+'/';
1237 Delete(result
, f
, e
+1-f
);
1238 Insert(es
, result
, f
);
1245 function SFSFileOpenEx (const fName
: TSFSString
): TStream
;
1247 dataFileName
, fn
: TSFSString
;
1250 diskChecked
: Boolean;
1253 function CheckDisk (): TStream
;
1254 // ïðîâåðèì, åñòü ëè ôàëî fn ãäå-òî íà äèñêàõ.
1256 dfn
, dirs
, cdir
: TSFSString
;
1260 if diskChecked
or not sfsDiskEnabled
then exit
;
1261 diskChecked
:= true;
1262 dfn
:= SFSReplacePathDelims(fn
, '/');
1263 dirs
:= sfsDiskDirs
; if dirs
= '' then dirs
:= '<currentdir>';
1266 f
:= 1; while (f
<= Length(dirs
)) and (dirs
[f
] <> '|') do Inc(f
);
1267 cdir
:= Copy(dirs
, 1, f
-1); Delete(dirs
, 1, f
);
1268 if cdir
= '' then continue
;
1269 cdir
:= SFSReplacePathDelims(SFSExpandDirName(cdir
), '/');
1270 if cdir
[Length(cdir
)] <> '/' then cdir
:= cdir
+'/';
1272 result
:= TFileStream
.Create(cdir
+dfn
, fmOpenRead
or fmShareDenyWrite
);
1280 SplitFName(fName
, dataFileName
, fn
);
1281 if fn
= '' then raise ESFSError
.Create('invalid file name: "'+fName
+'"');
1283 diskChecked
:= false;
1285 if dataFileName
<> '' then
1287 // ïðåôèêñîâàíûé ôàéë
1288 if sfsForceDiskForPrefixed
then
1290 result
:= CheckDisk();
1291 if result
<> nil then exit
;
1294 f
:= SFSAddDataFileEx(dataFileName
, nil, 0, 0);
1295 vi
:= TVolumeInfo(volumes
[f
]);
1298 result
:= vi
.fVolume
.OpenFileEx(fn
);
1299 ps
:= TOwnedPartialStream
.Create(vi
, result
, 0, result
.Size
, true);
1302 if (gcdisabled
= 0) and not vi
.fPermanent
and (vi
.fOpenedFilesCount
< 1) then volumes
[f
] := nil;
1303 result
:= CheckDisk(); // îáëîì ñ datafile, ïðîâåðèì äèñê
1304 if result
= nil then raise ESFSError
.Create('file not found: "'+fName
+'"');
1307 //Inc(vi.fOpenedFilesCount);
1312 // íåïðåôèêñîâàíûé ôàéë
1313 if sfsDiskFirst
then
1315 result
:= CheckDisk();
1316 if result
<> nil then exit
;
1318 // èùåì ïî âñåì ïåðìàíåíòíûì ïðåôèêñàì
1320 while f
< volumes
.Count
do
1322 vi
:= TVolumeInfo(volumes
[f
]);
1323 if (vi
<> nil) and vi
.fPermanent
then
1325 if vi
.fVolume
<> nil then
1327 result
:= vi
.fVolume
.OpenFileEx(fn
);
1328 if result
<> nil then
1331 ps
:= TOwnedPartialStream
.Create(vi
, result
, 0, result
.Size
, true);
1333 //Inc(vi.fOpenedFilesCount);
1338 if result
<> nil then exit
;
1343 result
:= CheckDisk();
1344 if result
= nil then raise ESFSError
.Create('file not found: "'+fName
+'"');
1347 function SFSFileOpen (const fName
: TSFSString
): TStream
;
1350 result
:= SFSFileOpenEx(fName
);
1356 function SFSFileList (const dataFileName
: TSFSString
): TSFSFileList
;
1362 if dataFileName
= '' then exit
;
1365 f
:= SFSAddDataFileEx(dataFileName
, nil, 0, 0);
1369 vi
:= TVolumeInfo(volumes
[f
]);
1372 result
:= TSFSFileList
.Create(vi
.fVolume
);
1373 Inc(vi
.fVolume
.fRC
);
1375 if (gcdisabled
= 0) and not vi
.fPermanent
and (vi
.fOpenedFilesCount
< 1) then volumes
[f
] := nil;
1380 // ////////////////////////////////////////////////////////////////////////// //
1384 function utf8CodeLen (ch
: Word): Integer;
1386 if ch
< $80 then begin result
:= 1; exit
; end;
1387 if (ch
and $FE) = $FC then begin result
:= 6; exit
; end;
1388 if (ch
and $FC) = $F8 then begin result
:= 5; exit
; end;
1389 if (ch
and $F8) = $F0 then begin result
:= 4; exit
; end;
1390 if (ch
and $F0) = $E0 then begin result
:= 3; exit
; end;
1391 if (ch
and $E0) = $C0 then begin result
:= 2; exit
; end;
1392 result
:= -1; // invalid
1396 function utf8Valid (s
: string): Boolean;
1402 while pos
<= length(s
) do
1404 len
:= utf8CodeLen(Byte(s
[pos
]));
1405 if len
< 1 then exit
; // invalid sequence start
1406 if pos
+len
-1 > length(s
) then exit
; // out of chars in string
1409 // check other sequence bytes
1412 if (Byte(s
[pos
]) and $C0) <> $80 then exit
;
1421 // ////////////////////////////////////////////////////////////////////////// //
1423 // TODO: move this to a separate file
1424 uni2wint
: array [128..255] of Word = (
1425 $0402,$0403,$201A,$0453,$201E,$2026,$2020,$2021,$20AC,$2030,$0409,$2039,$040A,$040C,$040B,$040F,
1426 $0452,$2018,$2019,$201C,$201D,$2022,$2013,$2014,$003F,$2122,$0459,$203A,$045A,$045C,$045B,$045F,
1427 $00A0,$040E,$045E,$0408,$00A4,$0490,$00A6,$00A7,$0401,$00A9,$0404,$00AB,$00AC,$00AD,$00AE,$0407,
1428 $00B0,$00B1,$0406,$0456,$0491,$00B5,$00B6,$00B7,$0451,$2116,$0454,$00BB,$0458,$0405,$0455,$0457,
1429 $0410,$0411,$0412,$0413,$0414,$0415,$0416,$0417,$0418,$0419,$041A,$041B,$041C,$041D,$041E,$041F,
1430 $0420,$0421,$0422,$0423,$0424,$0425,$0426,$0427,$0428,$0429,$042A,$042B,$042C,$042D,$042E,$042F,
1431 $0430,$0431,$0432,$0433,$0434,$0435,$0436,$0437,$0438,$0439,$043A,$043B,$043C,$043D,$043E,$043F,
1432 $0440,$0441,$0442,$0443,$0444,$0445,$0446,$0447,$0448,$0449,$044A,$044B,$044C,$044D,$044E,$044F
1436 function decodeUtf8Char (s
: TSFSString
; var pos
: Integer): char;
1440 (* The following encodings are valid, except for the 5 and 6 byte
1444 * 1110xxxx 10xxxxxx 10xxxxxx
1445 * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
1446 * 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
1447 * 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
1450 if pos
> length(s
) then exit
;
1454 if b
< $80 then begin result
:= char(b
); exit
; end;
1456 // mask out unused bits
1457 if (b
and $FE) = $FC then b
:= b
and $01
1458 else if (b
and $FC) = $F8 then b
:= b
and $03
1459 else if (b
and $F8) = $F0 then b
:= b
and $07
1460 else if (b
and $F0) = $E0 then b
:= b
and $0F
1461 else if (b
and $E0) = $C0 then b
:= b
and $1F
1462 else exit
; // invalid utf8
1465 while pos
<= length(s
) do
1468 if (c
and $C0) <> $80 then break
; // no more
1470 b
:= b
or (c
and $3F);
1475 for c
:= 128 to 255 do if uni2wint
[c
] = b
then begin result
:= char(c
and $FF); exit
; end;
1480 function utf8to1251 (s
: TSFSString
): TSFSString
;
1484 if not utf8Valid(s
) then begin result
:= s
; exit
; end;
1486 while pos
<= length(s
) do
1488 if Byte(s
[pos
]) >= $80 then break
;
1491 if pos
> length(s
) then begin result
:= s
; exit
; end; // nothing to do here
1494 while pos
<= length(s
) do result
:= result
+decodeUtf8Char(s
, pos
);
1499 factories
:= TObjectList
.Create(true);
1500 volumes
:= TObjectList
.Create(true);
1502 //volumes.Free(); // it fails for some reason... Runtime 217 (^C hit). wtf?!
1503 //factories.Free(); // not need to be done actually...