1 # ======================================================================
2 # Perl SOE TreeFile (loose file only) support
3 # Copyright 2003, Sony Online Entertainment
5 # ======================================================================
11 use Cwd
qw(:DEFAULT abs_path);
15 # ======================================================================
16 # TreeFile potentially-public variables.
17 # ======================================================================
19 # File::Find-like variables.
20 our $relativePathName;
23 # ======================================================================
24 # Setup variables that can be imported by Exporter into user modules.
25 # ======================================================================
27 use vars
qw(@ISA @EXPORT_OK $VERSION);
32 # These symbols are okay to export if specifically requested.
33 # @EXPORT_OK = qw(&buildFileLookupTable &saveFileLookupTable &loadFileLookupTable &getFullPathName $relativePathName $fullPathName);
34 @EXPORT_OK = qw(&buildFileLookupTable &saveFileLookupTable &loadFileLookupTable &getFullPathName &findRelative &findRelativeRegexMatch);
36 # ======================================================================
37 # TreeFile private variables.
38 # ======================================================================
42 my @directoryRootedPath = ();
43 my @directoryPriority = ();
44 my %directoryIndexByRelativeName = ();
47 my $findBasePathIndex;
48 my $printDuplicateFiles;
50 # ======================================================================
51 # TreeFile public functions.
52 # ======================================================================
54 # ----------------------------------------------------------------------
55 # TreeFile lookup table file format
57 # [1 or more of the following. These will always be listed from highest
58 # priority to least priority. These will always end with a /, see e
61 # p <rootedSearchPath>:<priority>
63 # [0 or more of the following. All p lines exist prior to the first e line
66 # e <treefileRelativePath>:<rootedPathIndex>
68 # The rootedPathIndex is the 0-based index of the p entry that contains
69 # the treefile specified. The full path should be the p path concatenated
70 # with the e pathname.
71 # ----------------------------------------------------------------------
73 sub sortTreefileHighestFirst
76 $testForA =~ s/searchPath//;
79 $testForB =~ s/searchPath//;
81 $testForB <=> $testForA;
84 # ----------------------------------------------------------------------
85 # syntax: buildRootedDirectories(baseDirectory)
87 # Setup rooted directories from the ConfigFile treefile declarations.
88 # ----------------------------------------------------------------------
90 sub buildRootedDirectories
92 # Clear out any existing entries.
93 @directoryRootedPath = ();
94 @directoryPriority = ();
97 my $baseDirectory = shift;
99 # Grab the searchPath* entries from ConfigFile.
100 my %values = ConfigFile
::getVariablesMatchingRegex
("SharedFile", "^searchPath");
102 foreach my $variableName (sort sortTreefileHighestFirst
keys %values)
104 print STDERR
"processing TreeFile config file declarations with tag $variableName.\n" if $debug;
106 if ($variableName =~ m/(\d+)/)
109 print STDERR
"directory priority = $priority.\n" if $debug;
111 my $pathArrayRef = $values{$variableName};
112 foreach my $path (@
$pathArrayRef)
114 print STDERR
"\tpath=[$path]\n" if $debug;
116 # Convert path from relative to absolute via $baseDirectory arg.
117 my $isRelative = ! File
::Spec
::Unix
->file_name_is_absolute($path);
120 $path = $baseDirectory . '/' . $path;
123 # Check if the directory exists.
126 # Canonicalize the path (remove directory self references, back references and symbolic paths).
127 $path = abs_path
($path);
128 $path .= '/' if !($path =~ m
!/$!);
129 print STDERR
"\tcanonicalized path=[$path]\n" if $debug;
131 push @directoryRootedPath, $path;
132 push @directoryPriority, $priority;
138 # Ensure directory root path and directory priority arrays don't end up out of sync.
139 die "arrays out of sync" if (scalar @directoryRootedPath) != (scalar @directoryPriority);
142 # ----------------------------------------------------------------------
144 sub findFileProcessor
146 # Ensure we're talking about a regular file that is readable by the
150 # Build treefile-relative name by stripping off base directory.
151 my $relativePathName = $File::Find
::name
;
152 $relativePathName =~ s/$findBasePathName//;
155 my $isDupe = exists $directoryIndexByRelativeName{$relativePathName};
158 print "dupe found: relative=[$relativePathName], full=[$File::Find::name]\n" if $printDuplicateFiles;
162 $directoryIndexByRelativeName{$relativePathName} = $findBasePathIndex;
167 print STDERR
"Processing File\n";
168 print STDERR
"\t[$File::Find::name]\n";
169 print STDERR
"\trelative name: [$relativePathName]\n";
170 print STDERR
"\tDUPLICATE\n" if $isDupe;
175 # ----------------------------------------------------------------------
176 # @syntax buildFileLookupTable [reportDuplicates [relativeToPath]]
178 # @param reportDuplicates if non-zero, report duplicate files (i.e. files
179 # with identical treefile-relative pathnames that
180 # exist in multiple locations on disk.) Defaults
183 # @param relativeToPath if specified, specifies the base directory to
184 # use for treefile searchpath specifications that
185 # are not rooted (i.e. relative paths). Defaults
186 # to the current directory.
188 # Prior to calling any of the filename lookup functions like getFullPath(),
189 # either buildFileLookupTable() or loadFileLookupTable() must be called.
190 # To generate the lookup table file, call buildFileLookupTable() and save
191 # it with saveFileLookupTable(). This lookup information then can be
192 # loaded directly from the file for subsequent runs on the same machine
193 # with the same data. If new files are added, the lookup data must be
194 # regenerated or it will become stale.
196 # ConfigFile must have been setup and must have processed any applicable
197 # TreeFile-related searchPath declarations prior to calling this function.
198 # ----------------------------------------------------------------------
200 sub buildFileLookupTable
203 $printDuplicateFiles = shift;
204 $printDuplicateFiles = 0 if !defined($printDuplicateFiles);
206 my $relativeToPath = shift;
207 $relativeToPath = getcwd
() if !defined($relativeToPath);
209 # Build TreeFile rooted search paths.
210 buildRootedDirectories
($relativeToPath);
212 # Do a find-all-files on each rooted search path, populating %directoryIndexByRelativeName.
213 %directoryIndexByRelativeName = ();
215 my $pathCount = scalar @directoryRootedPath;
216 for ($findBasePathIndex = 0; $findBasePathIndex < $pathCount; ++$findBasePathIndex)
218 $findBasePathName = $directoryRootedPath[$findBasePathIndex];
219 File
::Find
::find
(\
&findFileProcessor
, ($findBasePathName));
223 # ----------------------------------------------------------------------
224 # @syntax saveFileLookupTable(fileHandleRef)
226 # Saves the lookup table to the specified filehandle reference.
227 # If unspecified, writes to STDOUT.
228 # ----------------------------------------------------------------------
230 sub saveFileLookupTable
232 my $outputFileRef = shift;
233 $outputFileRef = \
*STDOUT
if !defined($outputFileRef);
235 my $searchPathCount = scalar @directoryRootedPath;
236 die "Directory data out of sync" if ($searchPathCount != scalar @directoryPriority);
238 for (my $i = 0; $i < $searchPathCount; ++$i)
240 my $rootedSearchPath = $directoryRootedPath[$i];
241 my $priority = $directoryPriority[$i];
243 print $outputFileRef "p $rootedSearchPath:$priority\n";
246 foreach my $relativePathName (sort keys %directoryIndexByRelativeName)
248 my $directoryIndex = $directoryIndexByRelativeName{$relativePathName};
249 print $outputFileRef "e $relativePathName:$directoryIndex\n";
253 # ----------------------------------------------------------------------
254 # @syntax loadFileLookupTable(fileHandleRef)
256 # Loads the lookup table from the specified filehandle reference.
257 # If unspecified, reads from STDIN.
258 # ----------------------------------------------------------------------
260 sub loadFileLookupTable
262 # Clear the treefile data structures.
263 @directoryRootedPath = ();
264 @directoryPriority = ();
265 %directoryIndexByRelativeName = ();
268 my $inputFileRef = shift;
269 $inputFileRef = \
*STDIN
if !defined($inputFileRef);
271 # Process the file contents.
272 while (<$inputFileRef>)
275 if (m/^p\s+(.+):(\d+)$/)
277 # Handle the rooted base path directive.
278 my $rootedBasePathName = $1;
281 push @directoryRootedPath, $rootedBasePathName;
282 push @directoryPriority, $priority;
284 elsif (m/^e\s+(.+):(\d+)$/)
286 # Handle the treefile entry directive.
287 my $relativePathName = $1;
288 my $directoryIndex = $2;
290 $directoryIndexByRelativeName{$relativePathName} = $directoryIndex;
294 die "Unexpected load file input, line=[$_].\n";
298 # Validate directory structures.
299 die "arrays out of sync" if (scalar @directoryRootedPath) != (scalar @directoryPriority);
302 # ----------------------------------------------------------------------
303 # @syntax getFullPathName(treefileRelativePathName)
305 # @param treefileRelativePathName this is a pathname as used within
306 # the game, relative to the TreeFile system.
307 # These filenames are never rooted, always relative.
309 # @return the rooted full loose-file pathname for the given
310 # treefile-relative pathname; returns undef if not found.
311 # ----------------------------------------------------------------------
316 my $relativePathName = shift;
317 return undef if !defined($relativePathName);
319 # Get rooted directory index where specified relative pathname lives.
320 my $directoryIndex = $directoryIndexByRelativeName{$relativePathName};
321 return undef if !defined($directoryIndex);
323 # Build full pathname, return to caller.
324 my $fullPathName = $directoryRootedPath[$directoryIndex] . $relativePathName;
325 return $fullPathName;
328 # ----------------------------------------------------------------------
329 # Operates similar to File::Find::find, operating over the TreeFile-relative
330 # filename namespace instead of the normal OS filesystem namespace.
332 # @syntax findRelative(callbackReference, treefileRelativeDirectoryList)
334 # @param callbackReference the function referenced by this arg will be
335 # called for each file in the treefile relative
336 # filename domain that starts with one of the
337 # specified treefile-relative directory names.
338 # @param treefileRelativeDirectoryList
339 # the TreeFile-relative directories (e.g.
340 # appearance/mesh) that should be searched.
341 # ----------------------------------------------------------------------
346 my $callbackRef = shift;
347 die "Caller must specify callback function reference" if !defined($callbackRef);
349 # Process each directory.
350 @_ = ("") if !scalar(@_);
351 foreach my $treefileRelativeDir (@_)
353 # Add trailing '/' if not already present.
354 $treefileRelativeDir .= '/' if !$treefileRelativeDir =~ m
!/$!;
355 my $matchRegex = '^' . $treefileRelativeDir;
357 # Test all treefile-relative names against this regex.
358 # @todo optimize this by building a TreeFile-relative directory structure
359 # so that we don't need to test against everything.
360 foreach $relativePathName (keys %directoryIndexByRelativeName)
362 if ($relativePathName =~ m/$matchRegex/)
364 # Allow $TreeFile::name to access the full on-disk pathname.
365 $fullPathName = getFullPathName
($relativePathName);
367 # Setup $_ for callback.
368 $_ = $relativePathName;
378 # ----------------------------------------------------------------------
379 # Operates similar to findRelative but is more efficient in that it makes
380 # a single pass over all tree file entries instead of a pass per specified
383 # @syntax findRelativeRegexMatch(callbackReference, regexList)
385 # @param callbackReference the function referenced by this arg will be
386 # called once for each file in the treefile relative
387 # filename domain that matches at least one of
388 # regex entries in regexList.
389 # @param regexList a list of regex entries that will be applied to
390 # the TreeFile-relative pathname (the whole thing).
391 # If any one of these matches a specified file, the
392 # callback will be made for the file.
393 # ----------------------------------------------------------------------
395 sub findRelativeRegexMatch
398 my $callbackRef = shift;
399 die "Caller must specify callback function reference" if !defined($callbackRef);
402 die "Caller must specify at least one regex to match against treefile-relative pathnames" if !$regexCount;
404 # Process each TreeFile-relative pathname.
405 foreach $relativePathName (keys %directoryIndexByRelativeName)
409 # Check if any of the regex entries match the treefile-relative pathname.
410 for (my $i = 0; ($i < $regexCount) && ($matchCount < 1); ++$i)
412 ++$matchCount if $relativePathName =~ m/$_[$i]/;
417 # Allow $TreeFile::name to access the full on-disk pathname.
418 $fullPathName = getFullPathName
($relativePathName);
420 # Setup $_ for callback.
421 $_ = $relativePathName;
430 # ======================================================================