1 (* Copyright (C) 2016 - The Doom2D.org team & involved community members <http://www.doom2d.org>.
2 * This file is part of Doom2D Forever.
4 * This program is free software: you can redistribute it and/or modify it under the terms of
5 * the GNU General Public License as published by the Free Software Foundation, version 3 of
8 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10 * See the GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License along with this program.
13 * If not, see <http://www.gnu.org/licenses/>.
16 // streaming file system (virtual)
17 {$INCLUDE ../shared/a_modes.inc}
20 {.$DEFINE SFS_VOLDEBUG}
26 SysUtils
, Classes
, Contnrs
;
30 ESFSError
= class(Exception
);
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
50 // âèðòóàëüíàÿ ôàéëîâàÿ ñèñòåìà. ÒÎËÜÊÎ ÄËß ×ÒÅÍÈß!
51 // òîì ÍÅ ÄÎËÆÅÍ óáèâàòüñÿ íèêàê èíà÷å, ÷åì ïðè ïîìîùè ôàáðèêè!
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;
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
;
113 // ôàáðèêà òîìîâ. âñå SFS ïðè ñòàðòå äîáàâëÿþò ñâîè ôàáðèêè.
114 // áëàãîäàðÿ ýòîìó ìîæíî ñîçäàâàòü ðàçíûå âñÿêèå SFS ñòàíäàðòíûì
115 // âûçîâîì ñòàíäàðòíîé ïðîöåäóðû.
116 // ôàáðèêà ÍÅ ÄÎËÆÍÀ óáèâàòüñÿ íèêàê èíà÷å, ÷åì ïðè ïîìîùè âûçîâà
117 // SFSUnregisterVolumeFactory()! ýòî ãàðàíòèðóåò, ÷òî äâèæîê
118 // ïåðåä ðàññòðåëîì îòäàñò åé âñå å¸ òîìà.
119 // -- upd 2024-02-14 by ×Ä: à çà÷åì íàì, ñîáñòâåííî, èõ âîîáùå ïðèáèâàòü? âîò è ÿ òàê ïîäóìàë!
120 // ïîýòîìó âìåñòî ôàáðèê ïðèáèë ñàìó ôóíêöèþ SFSUnregisterVolumeFactory(), ãûã. îíà êðèâàÿ áûëà.
121 // à âñå ôàáðèêè âîîáùå ïåðåäåëàë â ìåòàêëàññû, òî åñòü òåïåðü èõ è ñîçäàâàòü íå íàäî. %-)
122 TSFSVolumeFactoryMethods
= class abstract
124 // åñëè äîáàâëÿåì ôàéë äàííûõ ôàéë ñ èìåíåì òèïà "zip:....", òî
125 // SFS èçâëå÷¸ò ýòî "zip" è ïåðåäàñò â ñèþ ôóíêöèþ.
126 // åæåëè ôóíêöèÿ âåðí¸ò ïðàâäó, òî SFS âûçîâåò Produce äëÿ äàííîãî
127 // ôàéëà. åñëè íè îäíà ôàáðèêà ïðåôèêñ íå ïðèçíàåò, òî ôàéë íå îòêðîþò.
128 // èñïîëüçóåòñÿ äëÿ ñêèïàíèÿ àâòîäåòåêòà.
129 // SFS ÍÅ Ñ×ÈÒÀÅÒ ÏÐÅÔÈÊÑÎÌ ÑÒÐÎÊÓ ÊÎÐÎ×Å ÒÐ¨Õ ÑÈÌÂÎËÎÂ!
130 class function IsMyVolumePrefix (const prefix
: AnsiString
): Boolean; virtual; abstract;
131 // ïðîâåðÿåò, ìîæåò ëè ôàáðèêà ñäåëàòü òîì äëÿ äàííîãî ôàéëà.
132 // st -- îòêðûòûé äëÿ ÷òåíèÿ ôàéëîâé ïîòîê. óêàçàòåëü ÷òåíèÿ ñòîèò â íà÷àëå.
133 // ýòîò ïîòîê íåëüçÿ çàêðûâàòü!
134 // prefix: òî, ÷òî áûëî ïåðåäàíî â IsMyVolumePrefix() èëè ''.
135 // èñêëþ÷åíèå ñ÷èòàåòñÿ îøèáêîé, âîçâðàò NIL ñ÷èòàåòñÿ îøèáêîé.
136 class function Produce (const prefix
, fileName
: AnsiString
; st
: TStream
): TSFSVolume
; virtual; abstract;
137 // êîãäà òîì áîëüøå íå íóæåí, îí áóäåò îòäàí ôàáðèêå íà ïåðåðàáîòêó.
138 // äàëåå äâèæîê íå áóäåò þçàòü ñåé òîì.
139 class procedure Recycle (vol
: TSFSVolume
); virtual; abstract;
141 TSFSVolumeFactory
= class of TSFSVolumeFactoryMethods
;
143 // "èòåðàòîð", âîçâðàùàåìûé SFSFileList()
148 function GetCount (): Integer;
149 function GetFiles (index
: Integer): TSFSFileInfo
;
152 constructor Create (const pVolume
: TSFSVolume
);
153 destructor Destroy (); override;
155 property Volume
: TSFSVolume read fVolume
;
156 property Count
: Integer read GetCount
;
157 // ïðè íåïðàâèëüíîì èíäåêñå ìîë÷à âåðí¸ò NIL.
158 // ïðè ïðàâèëüíîì òîæå ìîæåò âåðíóòü NIL!
159 // î÷åíü íå ñîâåòóþ ìåíÿòü ñîäåðæèìîå ïîëó÷åííîãî êëàññà.
160 // êîíå÷íî, ÿ ìîã áû âîçâðàùàòü íîâóþ ñòðóêòóðó èëè íå÷òî ïîõîæåå,
161 // íî áëèí, åñëè òû èäèîò è íå óìååøü äàæå êîììåíòû ÷èòàòü, òî
162 // êàêîãî òû âîîáùå â ïðîãðàììèíã ïîëåç?
163 property Files
[index
: Integer]: TSFSFileInfo read GetFiles
; default
;
167 procedure SFSRegisterVolumeFactory (factory
: TSFSVolumeFactory
);
169 // äîáàâèòü ñáîðíèê â ïîñòîÿííûé ñïèñîê.
170 // åñëè ñáîðíèê ñ òàêèì èìåíåì óæå îòêðûò, òî íå îòêðûâàåò åãî ïîâòîðíî.
171 // íèêîãäà íå êèäàåò èñêëþ÷åíèé.
172 // top: äîáàâèòü â íà÷àëî ñïèñêà ïîèñêà.
173 // âåðí¸ò ëîæü ïðè îøèáêå.
174 // ñïîñîáíî îòêðûâàòü ñáîðíèêè â ñáîðíèêàõ ïðè ïîìîùè êðóòûõ èì¸í a-la:
175 // "zip:pack0::pack:pack1::wad2:pack2".
176 // â äàëüíåéøåì ñëåäóåò îáðàùàòüñÿ ê ñáîðíèêó êàê "pack2::xxx".
177 // èëè ìîæíî íàïèñàòü:
178 // "zip:pack0::pack:pack1::wad2:pack2|datafile".
179 // è îáðàùàòüñÿ êàê "datafile::xxx".
180 // "||" ïðåîáðàçóþòñÿ â ïðîñòîé "|" è ðàçäåëèòåëåì íå ñ÷èòàþòñÿ.
181 // ïðèíèìàåòñÿ âî âíèìàíèå òîëüêî ïîñëåäíÿÿ òðóáà.
182 function SFSAddDataFile (const dataFileName
: AnsiString
; top
: Boolean=false): Boolean;
184 // äîáàâèòü ñáîðíèê âðåìåííî
185 function SFSAddDataFileTemp (const dataFileName
: AnsiString
; top
: Boolean=false): Boolean;
187 // äîáàâèòü â ïîñòîÿííûé ñïèñîê ñáîðíèê èç ïîòîêà ds.
188 // åñëè âîçâðàùàåò èñòèíó, òî SFS ñòàíîâèòñÿ âëÿäåëüöåì ïîòîêà ds è ñàìà
189 // óãðîáèò ñåé ïîòîê ïî íåîáõîäèìîñòè.
190 // virtualName ñòàíîâèòñÿ èìåíåì ñáîðíèêà äëÿ îïåðàöèè îòêðûòèÿ ôàéëà òèïà
191 // "packfile:file.ext".
192 // åñëè êàêîé-íèáóäü ñáîðíèê ñ èìåíåì virtualName óæå îòêðûò, âåðí¸ò false.
193 // íèêîãäà íå êèäàåò èñêëþ÷åíèé.
194 // top: äîáàâèòü â íà÷àëî ñïèñêà ïîèñêà.
195 // âåðí¸ò ëîæü ïðè îøèáêå.
196 // îòêðûâàåò ñáîðíèê èç ïîòîêà. dataFileName -- ÂÈÐÒÓÀËÜÍÎÅ èìÿ.
197 // ò.å. íà ñàìîì äåëå òàêîãî ôàéëà ìîæåò è íå áûòü íà äèñêå.
198 function SFSAddSubDataFile (const virtualName
: AnsiString
; ds
: TStream
; top
: Boolean=false): Boolean;
200 // øâûðÿåòñÿ èñêëþ÷åíèÿìè.
201 // åñëè fName íå èìååò óêàçàíèÿ íà ôàéë äàííûõ (ýòî òî, ÷òî îòäåëåíî îò
202 // îñòàëüíîãî èìåíè äâîåòî÷èåì), òî èùåì ñíà÷àëà ïî âñåì çàðåãèñòðèðîâàííûì
203 // ôàéëàì äàííûõ, ïîòîì â òåêóùåì êàòàëîãå, ïîòîì â êàòàëîãå, îòêóäà ñòàðòîâàëè.
204 // åñëè íè÷åãî íå íàøëè, êèäàåì èñêëþ÷åíèå.
205 function SFSFileOpenEx (const fName
: AnsiString
): TStream
;
207 // ïðè îøèáêå -- NIL, è íèêàêèõ èñêëþ÷åíèé.
208 function SFSFileOpen (const fName
: AnsiString
): TStream
;
210 // âîçâðàùàåò NIL ïðè îøèáêå.
211 // ïîñëå èñïîëüçîâàíèÿ, íàòóðàëüíî, èòåðàòîð íàäî ãðîõíóòü %-)
212 function SFSFileList (const dataFileName
: AnsiString
): TSFSFileList
;
214 // çàïðåòèòü îñâîáîæäåíèå âðåìåííûõ òîìîâ (ìîæíî âûçûâàòü ðåêóðñèâíî)
215 procedure sfsGCDisable ();
217 // ðàçðåøèòü îñâîáîæäåíèå âðåìåííûõ òîìîâ (ìîæíî âûçûâàòü ðåêóðñèâíî)
218 procedure sfsGCEnable ();
220 // for completeness sake
221 procedure sfsGCCollect ();
223 function SFSReplacePathDelims (const s
: AnsiString
; newDelim
: Char): AnsiString
;
225 // ðàçîáðàòü òîëñòîå èìÿ ôàéëà, âåðíóòü âèðòóàëüíîå èìÿ ïîñëåäíåãî ñïèñêà
226 // èëè ïóñòóþ ñòîðîêó, åñëè ñïèñêîâ íå áûëî.
227 function SFSGetLastVirtualName (const fn
: AnsiString
): AnsiString
;
230 // this code is meant to allow wildcard pattern matches. tt is VERY useful
231 // for matching filename wildcard patterns. tt allows unix grep-like pattern
232 // comparisons, for instance:
234 // ? Matches any single characer
235 // + Matches any single characer or nothing
236 // * Matches any number of contiguous characters
237 // [abc] Matches a or b or c at that position
238 // [!abc] Matches anything but a or b or c at that position
239 // [a-e] Matches a through e at that position
241 // 'ma?ch.*' -Would match match.exe, mavch.dat, march.on, etc
242 // 'this [e-n]s a [!zy]est' -Would match 'this is a test', but would
243 // not match 'this as a yest'
245 function WildMatch (pattern
, text: AnsiString
): Boolean;
246 function WildListMatch (wildList
, text: AnsiString
; delimChar
: AnsiChar
=':'): Integer;
247 function HasWildcards (const pattern
: AnsiString
): Boolean;
251 // ïðàâäà: ðàçðåøåíî èñêàòü ôàéëî íå òîëüêî â ôàéëàõ äàííûõ, íî è íà äèñêå.
252 sfsDiskEnabled
: Boolean = true;
253 // ïðàâäà: åñëè ôàéë íå ïðåôèêñîâàí, òî ñíà÷àëà èùåì ôàéëî íà äèñêå,
254 // ïîòîì â ôàéëàõ äàííûõ.
255 sfsDiskFirst
: Boolean = true;
256 // ïðàâäà: äàæå äëÿ ïðåôèêñîâàíûõ ôàéëîâ ñíà÷àëà ïðîñìîòðèì äèñê
257 // (åñëè óñòàíîâëåí ôëàæîê sfsDiskFirst è sfsDiskEnabled).
258 sfsForceDiskForPrefixed
: Boolean = false;
259 // ñïèñîê äèñêîâûõ êàòàëîãîâ äëÿ ïîèñêà ôàéëà. åñëè ïóñò -- èùåì òîëüêî â
260 // òåêóùåì. êàòàëîãè ðàçäåëÿþòñÿ òðóáîé ("|").
261 // <currentdir> çàìåíÿåòñÿ íà òåêóùèé êàòàëîã (ñ çàâåðøàþùèì "/"),
262 // <exedir> çàìåíÿåòñÿ íà êàòàëîã, ãäå ñèäèò .EXE (ñ çàâåðøàþùèì "/").
263 sfsDiskDirs
: AnsiString
= '<currentdir>|<exedir>';
274 WILD_CHAR_ESCAPE
= '\';
275 WILD_CHAR_SINGLE
= '?';
276 WILD_CHAR_SINGLE_OR_NONE
= '+';
277 WILD_CHAR_MULTI
= '*';
278 WILD_CHAR_RANGE_OPEN
= '[';
279 WILD_CHAR_RANGE
= '-';
280 WILD_CHAR_RANGE_CLOSE
= ']';
281 WILD_CHAR_RANGE_NOT
= '!';
284 function HasWildcards (const pattern
: AnsiString
): Boolean;
287 (Pos(WILD_CHAR_ESCAPE
, pattern
) <> 0) or
288 (Pos(WILD_CHAR_SINGLE
, pattern
) <> 0) or
289 (Pos(WILD_CHAR_SINGLE_OR_NONE
, pattern
) <> 0) or
290 (Pos(WILD_CHAR_MULTI
, pattern
) <> 0) or
291 (Pos(WILD_CHAR_RANGE_OPEN
, pattern
) <> 0);
294 function MatchMask (const pattern
: AnsiString
; p
, pend
: Integer; const text: AnsiString
; t
, tend
: Integer): Boolean;
296 rangeStart
, rangeEnd
: AnsiChar
;
297 rangeNot
, rangeMatched
: Boolean;
301 if (pend
< 0) or (pend
> Length(pattern
)) then pend
:= Length(pattern
);
302 if (tend
< 0) or (tend
> Length(text)) then tend
:= Length(text);
303 if t
< 1 then t
:= 1;
304 if p
< 1 then p
:= 1;
309 // no more text. check if there's no more chars in pattern (except "*" & "+")
310 while (p
<= pend
) and
311 ((pattern
[p
] = WILD_CHAR_MULTI
) or
312 (pattern
[p
] = WILD_CHAR_SINGLE_OR_NONE
)) do Inc(p
);
313 result
:= (p
> pend
);
321 if p
> pend
then result
:= false else result
:= (pattern
[p
] = text[t
]);
322 if not result
then exit
;
324 WILD_CHAR_RANGE_OPEN
:
327 Inc(p
); if p
> pend
then exit
; // sanity check
328 rangeNot
:= (pattern
[p
] = WILD_CHAR_RANGE_NOT
);
329 if rangeNot
then begin Inc(p
); if p
> pend
then exit
; {sanity check} end;
330 if pattern
[p
] = WILD_CHAR_RANGE_CLOSE
then exit
; // sanity check
331 ch
:= text[t
]; // speed reasons
332 rangeMatched
:= false;
334 if p
> pend
then exit
; // sanity check
335 rangeStart
:= pattern
[p
];
336 if rangeStart
= WILD_CHAR_RANGE_CLOSE
then break
;
337 Inc(p
); if p
> pend
then exit
; // sanity check
338 if pattern
[p
] = WILD_CHAR_RANGE
then
340 Inc(p
); if p
> pend
then exit
; // sanity check
341 rangeEnd
:= pattern
[p
]; Inc(p
);
342 if rangeStart
< rangeEnd
then
344 rangeMatched
:= (ch
>= rangeStart
) and (ch
<= rangeEnd
);
346 else rangeMatched
:= (ch
>= rangeEnd
) and (ch
<= rangeStart
);
348 else rangeMatched
:= (ch
= rangeStart
);
350 if rangeNot
= rangeMatched
then exit
;
352 // skip the rest or the range
353 while (p
<= pend
) and (pattern
[p
] <> WILD_CHAR_RANGE_CLOSE
) do Inc(p
);
354 if p
> pend
then exit
; // sanity check
356 WILD_CHAR_SINGLE_OR_NONE
:
359 result
:= MatchMask(pattern
, p
, pend
, text, t
, tend
);
360 if not result
then result
:= MatchMask(pattern
, p
, pend
, text, t
+1, tend
);
365 while (p
<= pend
) and (pattern
[p
] = WILD_CHAR_MULTI
) do Inc(p
);
366 result
:= (p
> pend
); if result
then exit
;
367 while not result
and (t
<= tend
) do
369 result
:= MatchMask(pattern
, p
, pend
, text, t
, tend
);
374 else result
:= (pattern
[p
] = text[t
]); if not result
then exit
;
378 result
:= (t
> tend
);
382 function WildMatch (pattern
, text: AnsiString
): Boolean;
384 if pattern
<> '' then pattern
:= AnsiLowerCase(pattern
);
385 if text <> '' then text := AnsiLowerCase(text);
386 result
:= MatchMask(pattern
, 1, -1, text, 1, -1);
389 function WildListMatch (wildList
, text: AnsiString
; delimChar
: AnsiChar
=':'): Integer;
393 if wildList
<> '' then wildList
:= AnsiLowerCase(wildList
);
394 if text <> '' then text := AnsiLowerCase(text);
397 while s
<= Length(wildList
) do
399 e
:= s
; while e
<= Length(wildList
) do
401 if wildList
[e
] = WILD_CHAR_RANGE_OPEN
then
403 while (e
<= Length(wildList
)) and (wildList
[e
] <> WILD_CHAR_RANGE_CLOSE
) do Inc(e
);
405 if wildList
[e
] = delimChar
then break
;
410 if MatchMask(wildList
, s
, e
-1, text, 1, -1) then exit
;
422 fFactory
: TSFSVolumeFactory
;
424 fPackName
: AnsiString
; // äëÿ îäíîãî è òîãî æå ôàéëà áóäåò òîëüêî îäèí òîì!
425 fStream
: TStream
; // ôàéëîâûé ïîòîê äëÿ ñáîðíèêà
426 fPermanent
: Boolean; // èñòèíà -- íå áóäåò óãðîáëåíà, åñëè íå îñòàíåòñÿ íè îäíîãî îòêðûòîãî òîìà
427 // èñòèíà -- ýòîò òîì áûë ñîçäàí èç ïîòîêà è íå èìååò äèñêîâîãî ôàéëà, ïîòîìó ôàáðèêå áóäåò ïåðåäàíî íå èìÿ ñáîðíèêà, à ïóñòàÿ ñòðîêà
428 fNoDiskFile
: Boolean;
429 fOpenedFilesCount
: Integer;
431 destructor Destroy (); override;
434 TOwnedPartialStream
= class (TSFSPartialStream
)
439 constructor Create (pOwner
: TVolumeInfo
; pSrc
: TStream
; pPos
, pSize
: Int64; pKillSrc
: Boolean);
440 destructor Destroy (); override;
445 factories
: TFPList
; // TSFSVolumeFactory
446 volumes
: TFPObjectList
; // TVolumeInfo
447 gcdisabled
: Integer; // >0: disabled
450 procedure sfsGCCollect ();
458 while f
< volumes
.Count
do
460 vi
:= TVolumeInfo(volumes
[f
]);
461 if (vi
<> nil) and (not vi
.fPermanent
) and (vi
.fOpenedFilesCount
= 0) then
463 // this volume probably can be removed
465 c
:= volumes
.Count
-1;
466 while not used
and (c
>= 0) do
468 if (c
<> f
) and (volumes
[c
] <> nil) then
470 used
:= vi
.fStream
= TVolumeInfo(volumes
[c
]).fStream
;
471 if not used
then used
:= vi
.fStream
= TVolumeInfo(volumes
[c
]).fVolume
.fFileStream
;
478 {$IFDEF SFS_VOLDEBUG}writeln('000: destroying volume "', TVolumeInfo(volumes
[f
]).fPackName
, '"');{$ENDIF}
479 volumes
.Delete(f
); // remove from list and also kill
484 f
+= 1; // next volume
488 procedure sfsGCDisable ();
493 procedure sfsGCEnable ();
496 if gcdisabled
<= 0 then
504 // ðàçáèòü èìÿ ôàéëà íà ÷àñòè: ïðåôèêñ ôàéëîâîé ñèñòåìû, èìÿ ôàéëà äàííûõ,
505 // ñîáñòâåííî èìÿ ôàéëà
507 // (("sfspfx:")?"datafile::")*"filename"
508 procedure SplitFName (const fn
: AnsiString
; out dataFile
, fileName
: AnsiString
);
515 if (fn
[f
] = ':') and (fn
[f
+1] = ':') then break
;
518 if f
< 1 then begin dataFile
:= ''; fileName
:= fn
; end
521 dataFile
:= Copy(fn
, 1, f
-1);
522 fileName
:= Copy(fn
, f
+2, maxInt
-10000);
526 // ñàéäýôôåêò: âûðåçàåò âèðòóàëüíîå èìÿ èç dataFile.
527 function ExtractVirtName (var dataFile
: AnsiString
): AnsiString
;
531 f
:= Length(dataFile
); result
:= dataFile
;
534 if dataFile
[f
] = ':' then break
;
535 if dataFile
[f
] = '|' then
537 if dataFile
[f
-1] = '|' then begin Dec(f
); Delete(dataFile
, f
, 1); end
540 result
:= Copy(dataFile
, f
+1, Length(dataFile
));
541 Delete(dataFile
, f
, Length(dataFile
));
549 // ðàçáèòü èìÿ ñáîðíèêà íà ÷àñòè: ïðåôèêñ ôàéëîâîé ñèñòåìû, èìÿ ôàéëà äàííûõ,
550 // âèðòóàëüíîå èìÿ. åñëè âèðòóàëüíîãî èìåíè íå äàíî, îíî áóäåò ðàâíî dataFile.
552 // [sfspfx:]datafile[|virtname]
553 // åñëè ïåðåä äâîåòî÷èåì ìåíüøå òð¸õ áóêâ, òî ýòî ñ÷èòàåòñÿ íå ïðåôèêñîì,
555 procedure SplitDataName (const fn
: AnsiString
; out pfx
, dataFile
, virtName
: AnsiString
);
560 if f
<= 3 then begin pfx
:= ''; dataFile
:= fn
; end
563 pfx
:= Copy(fn
, 1, f
-1);
564 dataFile
:= Copy(fn
, f
+1, maxInt
-10000);
566 virtName
:= ExtractVirtName(dataFile
);
569 // íàéòè ïðîèçâîäèòåëÿ äëÿ ýòîãî ôàéëà (åñëè ôàéë óæå îòêðûò).
570 // onlyPerm: òîëüêî "ïîñòîÿííûå" ïðîèçâîäèòåëè.
571 function FindVolumeInfo (const dataFileName
: AnsiString
; onlyPerm
: Boolean=false): Integer;
577 while f
< volumes
.Count
do
579 if volumes
[f
] <> nil then
581 vi
:= TVolumeInfo(volumes
[f
]);
582 if not onlyPerm
or vi
.fPermanent
then
584 if StrEquCI1251(vi
.fPackName
, dataFileName
) then
596 // íàéòè èíôó äëÿ ýòîãî òîìà.
597 // õîðîøåå èìÿ, ïðàâäà? %-)
598 function FindVolumeInfoByVolumeInstance (vol
: TSFSVolume
): Integer;
600 result
:= volumes
.Count
-1;
603 if volumes
[result
] <> nil then
605 if TVolumeInfo(volumes
[result
]).fVolume
= vol
then exit
;
613 function normalizePath (fn
: AnsiString
): AnsiString
;
619 while i
<= length(fn
) do
621 if (fn
[i
] = '.') and ((length(fn
)-i
= 0) or (fn
[i
+1] = '/') or (fn
[i
+1] = '\')) then
626 if (fn
[i
] = '/') or (fn
[i
] = '\') then
628 if (length(result
) > 0) and (result
[length(result
)] <> '/') then result
:= result
+'/';
632 result
:= result
+fn
[i
];
636 if (length(result
) > 0) and (result
[length(result
)] <> '/') then result
:= result
+'/';
639 function SFSReplacePathDelims (const s
: AnsiString
; newDelim
: Char): AnsiString
;
644 for f
:= 1 to Length(result
) do
646 if (result
[f
] = '/') or (result
[f
] = '\') then
648 // avoid unnecessary string changes
649 if result
[f
] <> newDelim
then result
[f
] := newDelim
;
654 function SFSGetLastVirtualName (const fn
: AnsiString
): AnsiString
;
656 rest
, tmp
: AnsiString
;
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
);
669 destructor TVolumeInfo
.Destroy ();
672 used
: Boolean = False; // ôëàæîê çàþçàíîñòè ïîòîêà êåì-òî åù¸
674 // òèïà ìóñîðîñáîðíèê: åñëè íàø ïîòîê áîëåå íèêåì íå þçàåòñÿ, òî óãðîáèòü åãî íàôèã
675 me
:= volumes
.IndexOf(Self
);
676 f
:= volumes
.Count
-1;
677 while not used
and (f
>= 0) do
679 if (f
<> me
) and (volumes
[f
] <> nil) then
681 used
:= fStream
= TVolumeInfo(volumes
[f
]).fStream
;
683 used
:= fStream
= TVolumeInfo(volumes
[f
]).fVolume
.fFileStream
;
689 if fFactory
<> nil then fFactory
.Recycle(fVolume
);
690 if not used
then fStream
.Free(); // åñëè áîëüøå íèêåì íå þçàíî, ïðèøèá¸ì
692 if me
>= 0 then volumes
.List
[me
] := nil; // prevent double-free on unit finalization
697 { TOwnedPartialStream }
698 constructor TOwnedPartialStream
.Create (pOwner
: TVolumeInfo
; pSrc
: TStream
;
699 pPos
, pSize
: Int64; pKillSrc
: Boolean);
701 inherited Create(pSrc
, pPos
, pSize
, pKillSrc
);
703 if pOwner
<> nil then Inc(pOwner
.fOpenedFilesCount
);
706 destructor TOwnedPartialStream
.Destroy ();
711 if fOwner
<> nil then
713 Dec(fOwner
.fOpenedFilesCount
);
714 if (gcdisabled
= 0) and not fOwner
.fPermanent
and (fOwner
.fOpenedFilesCount
< 1) then
716 f
:= volumes
.IndexOf(fOwner
);
719 {$IFDEF SFS_VOLDEBUG}writeln('001: destroying volume "', TVolumeInfo(volumes
[f
]).fPackName
, '"');{$ENDIF}
720 volumes
[f
] := nil; // NB: TObjectList destroys the owned object here (see SetItem() method)!!
728 constructor TSFSFileInfo
.Create (pOwner
: TSFSVolume
);
736 if pOwner
<> nil then pOwner
.fFiles
.Add(self
);
739 destructor TSFSFileInfo
.Destroy ();
741 if fOwner
<> nil then fOwner
.fFiles
.Extract(self
);
747 constructor TSFSVolume
.Create (const pFileName
: AnsiString
; pSt
: TStream
);
751 fFileName
:= pFileName
;
752 fFiles
:= TObjectList
.Create(true);
755 procedure TSFSVolume
.DoDirectoryRead ();
761 fFileName
:= ExpandFileName(SFSReplacePathDelims(fFileName
, '/'));
766 while f
< fFiles
.Count
do
768 sfi
:= TSFSFileInfo(fFiles
[f
]);
769 // normalize name & path
770 sfi
.fPath
:= SFSReplacePathDelims(sfi
.fPath
, '/');
771 if (sfi
.fPath
<> '') and (sfi
.fPath
[1] = '/') then Delete(sfi
.fPath
, 1, 1);
772 if (sfi
.fPath
<> '') and (sfi
.fPath
[Length(sfi
.fPath
)] <> '/') then sfi
.fPath
:= sfi
.fPath
+'/';
773 tmp
:= SFSReplacePathDelims(sfi
.fName
, '/');
774 c
:= Length(tmp
); while (c
> 0) and (tmp
[c
] <> '/') do Dec(c
);
777 // split path and name
778 Delete(sfi
.fName
, 1, c
); // cut name
779 tmp
:= Copy(tmp
, 1, c
); // get path
780 if tmp
= '/' then tmp
:= ''; // just delimiter; ignore it
781 sfi
.fPath
:= sfi
.fPath
+tmp
;
783 sfi
.fPath
:= normalizePath(sfi
.fPath
);
784 if (length(sfi
.fPath
) = 0) and (length(sfi
.fName
) = 0) then sfi
.Free
else Inc(f
);
788 destructor TSFSVolume
.Destroy ();
795 procedure TSFSVolume
.Clear ();
800 function TSFSVolume
.FindFile (const fPath
, fName
: AnsiString
): Integer;
802 if fFiles
= nil then result
:= -1
805 result
:= fFiles
.Count
;
809 if fFiles
[result
] <> nil then
811 if StrEquCI1251(fPath
, TSFSFileInfo(fFiles
[result
]).fPath
) and
812 StrEquCI1251(fName
, TSFSFileInfo(fFiles
[result
]).fName
) then exit
;
819 function TSFSVolume
.GetFileCount (): Integer;
821 if fFiles
= nil then result
:= 0 else result
:= fFiles
.Count
;
824 function TSFSVolume
.GetFiles (index
: Integer): TSFSFileInfo
;
826 if fFiles
= nil then result
:= nil
829 if (index
< 0) or (index
>= fFiles
.Count
) then result
:= nil
830 else result
:= TSFSFileInfo(fFiles
[index
]);
834 function TSFSVolume
.OpenFileEx (const fName
: AnsiString
): TStream
;
840 // normalize name, find split position
841 if (fp
<> '') and ((fp
[1] = '/') or (fp
[1] = '\')) then Delete(fp
, 1, 1);
843 for f
:= 1 to Length(fp
) do
845 if fp
[f
] = '\' then fp
[f
] := '/';
846 if fp
[f
] = '/' then ls
:= f
;
848 fn
:= Copy(fp
, ls
+1, Length(fp
));
849 fp
:= Copy(fp
, 1, ls
);
850 f
:= FindFile(fp
, fn
);
851 if f
= -1 then raise ESFSError
.Create('file not found: "'+fName
+'"');
852 result
:= OpenFileByIndex(f
);
853 if result
= nil then raise ESFSError
.Create('file not found: "'+fName
+'"');
858 constructor TSFSFileList
.Create (const pVolume
: TSFSVolume
);
863 ASSERT(pVolume
<> nil);
864 f
:= FindVolumeInfoByVolumeInstance(pVolume
);
867 Inc(TVolumeInfo(volumes
[f
]).fOpenedFilesCount
); // íå ïîçâîëèì óáèòü çàïèñü!
870 destructor TSFSFileList
.Destroy ();
874 f
:= FindVolumeInfoByVolumeInstance(fVolume
);
876 Dec(TVolumeInfo(volumes
[f
]).fOpenedFilesCount
);
877 // óáü¸ì çàïèñü, åñëè îíà âðåìåííàÿ, è â íåé íåò áîëüøå íè÷åãî îòêðûòîãî
878 if (gcdisabled
= 0) and not TVolumeInfo(volumes
[f
]).fPermanent
and (TVolumeInfo(volumes
[f
]).fOpenedFilesCount
< 1) then
880 {$IFDEF SFS_VOLDEBUG}writeln('002: destroying volume "', TVolumeInfo(volumes
[f
]).fPackName
, '"');{$ENDIF}
881 volumes
[f
] := nil; // NB: TObjectList destroys the owned object here (see SetItem() method)!!
886 function TSFSFileList
.GetCount (): Integer;
888 result
:= fVolume
.fFiles
.Count
;
891 function TSFSFileList
.GetFiles (index
: Integer): TSFSFileInfo
;
893 if (index
< 0) or (index
>= fVolume
.fFiles
.Count
) then result
:= nil
894 else result
:= TSFSFileInfo(fVolume
.fFiles
[index
]);
898 procedure SFSRegisterVolumeFactory (factory
: TSFSVolumeFactory
);
902 if factory
= nil then exit
;
903 if factories
.IndexOf(factory
) <> -1 then
904 raise ESFSError
.Create('duplicate factories are not allowed');
905 f
:= factories
.IndexOf(nil);
907 then factories
.Add(factory
)
908 else factories
[f
] := factory
;
911 function SFSAddDataFileEx (dataFileName
: AnsiString
; ds
: TStream
; top
, permanent
: Integer): Integer;
912 // dataFileName ìîæåò èìåòü ïðåôèêñ òèïà "zip:" (ñì. âûøå: IsMyPrefix).
913 // ìîæåò âûêèíóòü èñêëþ÷åíèå!
915 // <0: äîáàâèòü â íà÷àëî ñïèñêà ïîèñêà.
917 // >0: äîáàâèòü â êîíåö ñïèñêà ïîèñêà.
919 // <0: ñîçäàòü "âðåìåííûé" òîì.
920 // =0: íå ìåíÿòü ôëàæîê ïîñòîÿíñòâà.
921 // >0: ñîçäàòü "ïîñòîÿííûé" òîì.
922 // åñëè ds <> nil, òî ñîçäà¸ò ñáîðíèê èç ïîòîêà. åñëè ñáîðíèê ñ èìåíåì
923 // dataFileName óæå çàðåãèñòðèðîâàí, òî ïàäàåò íàôèã.
924 // âîçâðàùàåò èíäåêñ â volumes.
925 // óìååò äåëàòü ðåêóðñèþ.
927 fac
: TSFSVolumeFactory
;
933 fn
, vfn
, tmp
: AnsiString
;
935 f
:= Pos('::', dataFileName
);
938 // ðåêóðñèâíîå îòêðûòèå.
939 // ðàçîáü¸ì dataFileName íà èìÿ ñáîðíèêà è îñòàòîê.
940 // pfx áóäåò èìåíåì ñáîðíèêà, dataFileName -- îñòàòêîì.
941 pfx
:= Copy(dataFileName
, 1, f
-1); Delete(dataFileName
, 1, f
+1);
942 // ñíà÷àëà îòêðîåì ïåðâûé ñïèñîê...
943 result
:= SFSAddDataFileEx(pfx
, ds
, 0, 0);
944 // ...òåïåðü ïðîäîëæèì ñ îñòàòêîì.
945 // óçíàåì, êàêîå ôàéëî îòêðûâàòü.
946 // âûêîâûðÿåì ïåðâûé "::" ïðåôèêñ (ýòî áóäåò èìÿ ôàéëà).
947 f
:= Pos('::', dataFileName
); if f
= 0 then f
:= Length(dataFileName
)+1;
948 fn
:= Copy(dataFileName
, 1, f
-1); Delete(dataFileName
, 1, f
-1);
949 // dataFileName õðàíèò îñòàòîê.
950 // èçâëå÷¸ì èìÿ ôàéëà:
951 SplitDataName(fn
, pfx
, tmp
, vfn
);
953 vi
:= TVolumeInfo(volumes
[result
]); st
:= nil;
955 st
:= vi
.fVolume
.OpenFileEx(tmp
);
956 st1
:= TOwnedPartialStream
.Create(vi
, st
, 0, st
.Size
, true);
959 // óäàëèì íåèñïîëüçóåìûé âðåìåííûé òîì.
960 if (gcdisabled
= 0) and not vi
.fPermanent
and (vi
.fOpenedFilesCount
< 1) then
961 volumes
[result
] := nil; // NB: TObjectList destroys the owned object here (see SetItem() method)!!
964 // óðà. îòêðûëè ôàéë. êèäàåì â âîçäóõ ÷åï÷èêè, ïðîäîëæàåì ðàçâëå÷åíèå.
965 fn
:= fn
+dataFileName
;
968 result
:= SFSAddDataFileEx(fn
, st1
, top
, permanent
);
970 st1
.Free(); // à âîò íå çàëàäèëîñü. çàêðûëè îòêðûòîå ôàéëî, âûëåòåëè.
976 // îáûêíîâåííîå íåðåêóðñèâíîå îòêðûòèå.
977 SplitDataName(dataFileName
, pfx
, fn
, vfn
);
979 f
:= FindVolumeInfo(vfn
);
982 if ds
<> nil then raise ESFSError
.Create('subdata name conflict');
983 if permanent
<> 0 then TVolumeInfo(volumes
[f
]).fPermanent
:= (permanent
> 0);
984 if top
= 0 then result
:= f
985 else if top
< 0 then result
:= 0
986 else result
:= volumes
.Count
-1;
987 if result
<> f
then volumes
.Move(f
, result
);
991 if ds
<> nil then st
:= ds
992 else st
:= TFileStream
.Create(fn
, fmOpenRead
or {fmShareDenyWrite}fmShareDenyNone
);
997 fac
:= nil; vol
:= nil;
999 for f
:= 0 to factories
.Count
-1 do
1001 fac
:= TSFSVolumeFactory(factories
[f
]);
1002 if fac
= nil then continue
;
1003 if (pfx
<> '') and not fac
.IsMyVolumePrefix(pfx
) then continue
;
1006 if ds
<> nil then vol
:= fac
.Produce(pfx
, '', st
)
1007 else vol
:= fac
.Produce(pfx
, fn
, st
);
1011 if vol
<> nil then break
;
1013 if vol
= nil then raise ESFSError
.Create('no factory for "'+dataFileName
+'"');
1015 if st
<> ds
then st
.Free();
1019 vi
:= TVolumeInfo
.Create();
1024 volumes
.Insert(0, vi
);
1026 else result
:= volumes
.Add(vi
);
1029 if st
<> ds
then st
.Free();
1036 vi
.fPackName
:= vfn
;
1038 vi
.fPermanent
:= (permanent
> 0);
1039 vi
.fNoDiskFile
:= (ds
<> nil);
1040 vi
.fOpenedFilesCount
:= 0;
1043 function SFSAddSubDataFile (const virtualName
: AnsiString
; ds
: TStream
; top
: Boolean=false): Boolean;
1049 if top
then tv
:= -1 else tv
:= 1;
1050 SFSAddDataFileEx(virtualName
, ds
, tv
, 0);
1057 function SFSAddDataFile (const dataFileName
: AnsiString
; top
: Boolean=false): Boolean;
1062 if top
then tv
:= -1 else tv
:= 1;
1063 SFSAddDataFileEx(dataFileName
, nil, tv
, 1);
1070 function SFSAddDataFileTemp (const dataFileName
: AnsiString
; top
: Boolean=false): Boolean;
1075 if top
then tv
:= -1 else tv
:= 1;
1076 SFSAddDataFileEx(dataFileName
, nil, tv
, 0);
1085 function SFSExpandDirName (const s
: AnsiString
): AnsiString
;
1090 f
:= 1; result
:= s
;
1091 while f
< Length(result
) do
1093 while (f
< Length(result
)) and (result
[f
] <> '<') do Inc(f
);
1094 if f
>= Length(result
) then exit
;
1095 e
:= f
; while (e
< Length(result
)) and (result
[e
] <> '>') do Inc(e
);
1096 es
:= Copy(result
, f
, e
+1-f
);
1098 if es
= '<currentdir>' then es
:= GetCurrentDir
1099 else if es
= '<exedir>' then es
:= ExtractFilePath(ParamStr(0))
1104 if (es
[Length(es
)] <> '/') and (es
[Length(es
)] <> '\') then es
:= es
+'/';
1105 Delete(result
, f
, e
+1-f
);
1106 Insert(es
, result
, f
);
1113 function SFSFileOpenEx (const fName
: AnsiString
): TStream
;
1115 dataFileName
, fn
: AnsiString
;
1118 diskChecked
: Boolean;
1121 function CheckDisk (): TStream
;
1122 // ïðîâåðèì, åñòü ëè ôàëî fn ãäå-òî íà äèñêàõ.
1124 dfn
, dirs
, cdir
: AnsiString
;
1128 if diskChecked
or not sfsDiskEnabled
then exit
;
1129 diskChecked
:= true;
1130 dfn
:= SFSReplacePathDelims(fn
, '/');
1131 dirs
:= sfsDiskDirs
; if dirs
= '' then dirs
:= '<currentdir>';
1134 f
:= 1; while (f
<= Length(dirs
)) and (dirs
[f
] <> '|') do Inc(f
);
1135 cdir
:= Copy(dirs
, 1, f
-1); Delete(dirs
, 1, f
);
1136 if cdir
= '' then continue
;
1137 cdir
:= SFSReplacePathDelims(SFSExpandDirName(cdir
), '/');
1138 if cdir
[Length(cdir
)] <> '/' then cdir
:= cdir
+'/';
1140 result
:= TFileStream
.Create(cdir
+dfn
, fmOpenRead
or {fmShareDenyWrite}fmShareDenyNone
);
1148 SplitFName(fName
, dataFileName
, fn
);
1149 if fn
= '' then raise ESFSError
.Create('invalid file name: "'+fName
+'"');
1151 diskChecked
:= false;
1153 if dataFileName
<> '' then
1155 // ïðåôèêñîâàíûé ôàéë
1156 if sfsForceDiskForPrefixed
then
1158 result
:= CheckDisk();
1159 if result
<> nil then exit
;
1162 f
:= SFSAddDataFileEx(dataFileName
, nil, 0, 0);
1163 vi
:= TVolumeInfo(volumes
[f
]);
1166 result
:= vi
.fVolume
.OpenFileEx(fn
);
1167 ps
:= TOwnedPartialStream
.Create(vi
, result
, 0, result
.Size
, true);
1170 if (gcdisabled
= 0) and not vi
.fPermanent
and (vi
.fOpenedFilesCount
< 1) then
1171 volumes
[f
] := nil; // NB: TObjectList destroys the owned object here (see SetItem() method)!!
1172 result
:= CheckDisk(); // îáëîì ñ datafile, ïðîâåðèì äèñê
1173 if result
= nil then raise ESFSError
.Create('file not found: "'+fName
+'"');
1176 //Inc(vi.fOpenedFilesCount);
1181 // íåïðåôèêñîâàíûé ôàéë
1182 if sfsDiskFirst
then
1184 result
:= CheckDisk();
1185 if result
<> nil then exit
;
1187 // èùåì ïî âñåì ïåðìàíåíòíûì ïðåôèêñàì
1189 while f
< volumes
.Count
do
1191 vi
:= TVolumeInfo(volumes
[f
]);
1192 if (vi
<> nil) and vi
.fPermanent
then
1194 if vi
.fVolume
<> nil then
1196 result
:= vi
.fVolume
.OpenFileEx(fn
);
1197 if result
<> nil then
1200 ps
:= TOwnedPartialStream
.Create(vi
, result
, 0, result
.Size
, true);
1202 //Inc(vi.fOpenedFilesCount);
1207 if result
<> nil then exit
;
1212 result
:= CheckDisk();
1213 if result
= nil then raise ESFSError
.Create('file not found: "'+fName
+'"');
1216 function SFSFileOpen (const fName
: AnsiString
): TStream
;
1219 result
:= SFSFileOpenEx(fName
);
1225 function SFSFileList (const dataFileName
: AnsiString
): TSFSFileList
;
1231 if dataFileName
= '' then exit
;
1234 f
:= SFSAddDataFileEx(dataFileName
, nil, 0, 0);
1238 vi
:= TVolumeInfo(volumes
[f
]);
1241 result
:= TSFSFileList
.Create(vi
.fVolume
);
1243 if (gcdisabled
= 0) and not vi
.fPermanent
and (vi
.fOpenedFilesCount
< 1) then
1244 volumes
[f
] := nil; // NB: TObjectList destroys the owned object here (see SetItem() method)!!
1249 factories
:= TFPList
.Create();
1250 volumes
:= TFPObjectList
.Create(True);
1254 factories
.Destroy();