2008-08-31 13:58:17 +00:00
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers , whose names
* are too numerous to list here . Please refer to the COPYRIGHT
* file distributed with this source distribution .
*
2021-12-26 18:47:58 +01:00
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
2014-02-18 02:34:18 +01:00
*
2008-08-31 13:58:17 +00:00
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
2014-02-18 02:34:18 +01:00
*
2008-08-31 13:58:17 +00:00
* You should have received a copy of the GNU General Public License
2021-12-26 18:47:58 +01:00
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
2008-08-31 13:58:17 +00:00
*
*/
# include "common/archive.h"
2023-02-10 22:40:09 +01:00
# include "common/file.h"
2008-08-31 13:58:17 +00:00
# include "common/fs.h"
2008-09-27 18:32:01 +00:00
# include "common/system.h"
2011-04-24 11:34:27 +03:00
# include "common/textconsole.h"
2022-11-30 01:16:17 +01:00
# include "common/memstream.h"
2023-02-10 22:40:09 +01:00
# include "common/punycode.h"
2023-07-19 21:40:51 +05:30
# include "common/debug.h"
2008-08-31 13:58:17 +00:00
namespace Common {
2023-07-06 00:08:36 -04:00
GenericArchiveMember : : GenericArchiveMember ( const String & pathStr , const Archive & parent )
: _parent ( parent ) , _path ( pathStr , parent . getPathSeparator ( ) ) {
}
GenericArchiveMember : : GenericArchiveMember ( const Path & path , const Archive & parent )
: _parent ( parent ) , _path ( path ) {
2008-10-03 16:57:40 +00:00
}
String GenericArchiveMember : : getName ( ) const {
2023-07-06 00:08:36 -04:00
return _path . toString ( _parent . getPathSeparator ( ) ) ;
}
Path GenericArchiveMember : : getPathInArchive ( ) const {
return _path ;
}
String GenericArchiveMember : : getFileName ( ) const {
return _path . getLastComponent ( ) . toString ( _parent . getPathSeparator ( ) ) ;
2008-10-03 16:57:40 +00:00
}
2009-01-23 04:36:18 +00:00
SeekableReadStream * GenericArchiveMember : : createReadStream ( ) const {
2023-07-06 00:08:36 -04:00
return _parent . createReadStreamForMember ( _path ) ;
2008-10-03 16:57:40 +00:00
}
2008-08-31 13:58:17 +00:00
2023-01-07 15:13:13 +01:00
int Archive : : listMatchingMembers ( ArchiveMemberList & list , const Path & pattern , bool matchPathComponents ) const {
2008-09-05 18:24:41 +00:00
// Get all "names" (TODO: "files" ?)
2008-10-03 16:57:40 +00:00
ArchiveMemberList allNames ;
listMembers ( allNames ) ;
2008-09-05 18:24:41 +00:00
2021-08-09 16:23:36 -04:00
String patternString = pattern . toString ( ) ;
2008-09-05 18:24:41 +00:00
int matches = 0 ;
2023-07-06 00:08:36 -04:00
char pathSepString [ 2 ] = { getPathSeparator ( ) , ' \0 ' } ;
const char * wildcardExclusions = matchPathComponents ? NULL : pathSepString ;
2008-09-05 18:24:41 +00:00
2011-12-13 17:20:25 +01:00
ArchiveMemberList : : const_iterator it = allNames . begin ( ) ;
2016-02-09 12:10:04 +01:00
for ( ; it ! = allNames . end ( ) ; + + it ) {
2009-11-21 20:19:15 +00:00
// TODO: We match case-insenstivie for now, our API does not define whether that's ok or not though...
// For our use case case-insensitive is probably what we want to have though.
2023-01-07 15:13:13 +01:00
if ( ( * it ) - > getName ( ) . matchString ( patternString , true , wildcardExclusions ) ) {
2008-09-05 18:24:41 +00:00
list . push_back ( * it ) ;
matches + + ;
}
}
return matches ;
}
2023-08-08 20:42:09 +05:30
Common : : Error Archive : : dumpArchive ( String destPath ) {
2023-02-10 22:40:09 +01:00
Common : : ArchiveMemberList files ;
listMembers ( files ) ;
byte * data = nullptr ;
uint dataSize = 0 ;
for ( auto & f : files ) {
2023-07-19 21:40:51 +05:30
Common : : Path filePath = f - > getPathInArchive ( ) . punycodeEncode ( ) ;
debug ( 1 , " File: %s " , filePath . toString ( ) . c_str ( ) ) ;
2023-08-08 20:42:09 +05:30
2023-08-09 16:15:43 +05:30
// skip if f represents a directory
if ( filePath . toString ( ) . lastChar ( ) = = ' / ' ) continue ;
2023-02-10 22:40:09 +01:00
Common : : SeekableReadStream * stream = f - > createReadStream ( ) ;
uint32 len = stream - > size ( ) ;
if ( dataSize < len ) {
free ( data ) ;
data = ( byte * ) malloc ( stream - > size ( ) ) ;
dataSize = stream - > size ( ) ;
}
stream - > read ( data , len ) ;
Common : : DumpFile out ;
2023-07-19 21:40:51 +05:30
Common : : Path outPath = Common : : Path ( destPath ) . join ( filePath ) ;
if ( ! out . open ( outPath . toString ( ) , true ) ) {
2023-08-09 16:15:43 +05:30
return Common : : Error ( Common : : kCreatingFileFailed , " Cannot open/create dump file " + outPath . toString ( ) ) ;
2023-02-10 22:40:09 +01:00
} else {
2023-08-08 20:42:09 +05:30
uint32 writtenBytes = out . write ( data , len ) ;
if ( writtenBytes < len ) {
// Not all data was written
out . close ( ) ;
delete stream ;
free ( data ) ;
return Common : : Error ( Common : : kWritingFailed , " Not enough storage space! Please free up some storage and try again " ) ;
}
2023-02-10 22:40:09 +01:00
out . flush ( ) ;
out . close ( ) ;
}
delete stream ;
}
free ( data ) ;
2023-08-08 20:42:09 +05:30
return Common : : kNoError ;
2023-02-10 22:40:09 +01:00
}
2023-05-29 16:15:54 -04:00
char Archive : : getPathSeparator ( ) const {
return ' / ' ;
}
2022-11-30 01:16:17 +01:00
SeekableReadStream * MemcachingCaseInsensitiveArchive : : createReadStreamForMember ( const Path & path ) const {
String translated = translatePath ( path ) ;
2022-12-05 04:46:33 +01:00
bool isNew = false ;
2022-11-30 01:16:17 +01:00
if ( ! _cache . contains ( translated ) ) {
2023-03-11 23:44:48 +01:00
SharedArchiveContents readResult = readContentsForPath ( translated ) ;
if ( readResult . _bypass )
return readResult . _bypass ;
_cache [ translated ] = readResult ;
2022-12-05 04:46:33 +01:00
isNew = true ;
2022-11-30 01:16:17 +01:00
}
2022-12-05 04:46:33 +01:00
SharedArchiveContents * entry = & _cache [ translated ] ;
2022-11-30 01:16:17 +01:00
2022-12-05 04:46:33 +01:00
// Errors and missing files. Just return nullptr,
// no need to create stream.
if ( entry - > isFileMissing ( ) )
2022-11-30 01:16:17 +01:00
return nullptr ;
2022-12-05 04:46:33 +01:00
// Check whether the entry is still valid as WeakPtr might have expired.
if ( ! entry - > makeStrong ( ) ) {
// If it's expired, recreate the entry.
2023-03-11 23:44:48 +01:00
SharedArchiveContents readResult = readContentsForPath ( translated ) ;
if ( readResult . _bypass )
return readResult . _bypass ;
_cache [ translated ] = readResult ;
2022-12-05 04:46:33 +01:00
entry = & _cache [ translated ] ;
isNew = true ;
}
// It's possible that recreation failed in case of e.g. network
// share going offline.
if ( entry - > isFileMissing ( ) )
return nullptr ;
// Now we have a valid contents reference. Make stream for it.
Common : : MemoryReadStream * memStream = new Common : : MemoryReadStream ( entry - > getContents ( ) , entry - > getSize ( ) ) ;
// If the entry was just created and it's too big for strong caching,
// mark the copy in cache as weak
if ( isNew & & entry - > getSize ( ) > _maxStronglyCachedSize ) {
entry - > makeWeak ( ) ;
}
return memStream ;
2022-11-30 01:16:17 +01:00
}
2008-08-31 13:58:17 +00:00
2009-03-09 22:26:02 +00:00
SearchSet : : ArchiveNodeList : : iterator SearchSet : : find ( const String & name ) {
2008-12-15 12:55:13 +00:00
ArchiveNodeList : : iterator it = _list . begin ( ) ;
2016-02-09 12:10:04 +01:00
for ( ; it ! = _list . end ( ) ; + + it ) {
2008-11-21 17:49:19 +00:00
if ( it - > _name = = name )
2008-08-31 13:58:17 +00:00
break ;
}
return it ;
}
2009-03-09 22:26:02 +00:00
SearchSet : : ArchiveNodeList : : const_iterator SearchSet : : find ( const String & name ) const {
ArchiveNodeList : : const_iterator it = _list . begin ( ) ;
2016-02-09 12:10:04 +01:00
for ( ; it ! = _list . end ( ) ; + + it ) {
2009-03-09 22:26:02 +00:00
if ( it - > _name = = name )
break ;
}
return it ;
}
2008-08-31 13:58:17 +00:00
/*
2021-04-15 21:20:04 +02:00
Keep the nodes sorted according to descending priorities .
In case two or node nodes have the same priority , insertion
order prevails .
2008-08-31 13:58:17 +00:00
*/
void SearchSet : : insert ( const Node & node ) {
2008-12-15 12:55:13 +00:00
ArchiveNodeList : : iterator it = _list . begin ( ) ;
2016-02-09 12:10:04 +01:00
for ( ; it ! = _list . end ( ) ; + + it ) {
2008-11-21 17:49:19 +00:00
if ( it - > _priority < node . _priority )
2008-08-31 13:58:17 +00:00
break ;
}
_list . insert ( it , node ) ;
}
2008-11-07 13:42:05 +00:00
void SearchSet : : add ( const String & name , Archive * archive , int priority , bool autoFree ) {
2008-08-31 13:58:17 +00:00
if ( find ( name ) = = _list . end ( ) ) {
2008-10-22 17:44:12 +00:00
Node node ( priority , name , archive , autoFree ) ;
2008-08-31 13:58:17 +00:00
insert ( node ) ;
} else {
2008-11-07 13:42:05 +00:00
if ( autoFree )
delete archive ;
2008-08-31 13:58:17 +00:00
warning ( " SearchSet::add: archive '%s' already present " , name . c_str ( ) ) ;
}
}
2009-06-01 00:01:32 +00:00
void SearchSet : : addDirectory ( const String & name , const String & directory , int priority , int depth , bool flat ) {
2008-12-27 17:16:48 +00:00
FSNode dir ( directory ) ;
2009-06-01 00:01:32 +00:00
addDirectory ( name , dir , priority , depth , flat ) ;
2008-12-27 17:16:48 +00:00
}
2009-06-01 00:01:32 +00:00
void SearchSet : : addDirectory ( const String & name , const FSNode & dir , int priority , int depth , bool flat ) {
2008-12-27 17:16:48 +00:00
if ( ! dir . exists ( ) | | ! dir . isDirectory ( ) )
return ;
2019-10-20 17:37:18 +02:00
add ( name , new FSDirectory ( dir , depth , flat , _ignoreClashes ) , priority ) ;
2008-12-27 17:16:48 +00:00
}
2013-06-06 21:41:14 +02:00
void SearchSet : : addSubDirectoriesMatching ( const FSNode & directory , String origPattern , bool ignoreCase , int priority , int depth , bool flat ) {
2009-09-23 00:15:00 +00:00
FSList subDirs ;
if ( ! directory . getChildren ( subDirs ) )
return ;
2009-10-03 20:12:44 +00:00
String nextPattern , pattern ;
String : : const_iterator sep = Common : : find ( origPattern . begin ( ) , origPattern . end ( ) , ' / ' ) ;
if ( sep ! = origPattern . end ( ) ) {
pattern = String ( origPattern . begin ( ) , sep ) ;
2009-09-23 00:15:00 +00:00
+ + sep ;
2009-10-03 20:12:44 +00:00
if ( sep ! = origPattern . end ( ) )
nextPattern = String ( sep , origPattern . end ( ) ) ;
2016-02-09 12:10:04 +01:00
} else {
2009-10-03 20:12:44 +00:00
pattern = origPattern ;
2009-09-23 00:15:00 +00:00
}
// TODO: The code we have for displaying all matches, which vary only in case, might
// be a bit overhead, but as long as we want to display all useful information to the
// user we will need to keep track of all directory names added so far. We might
// want to reconsider this though.
typedef HashMap < String , bool , IgnoreCase_Hash , IgnoreCase_EqualTo > MatchList ;
MatchList multipleMatches ;
MatchList : : iterator matchIter ;
for ( FSList : : const_iterator i = subDirs . begin ( ) ; i ! = subDirs . end ( ) ; + + i ) {
String name = i - > getName ( ) ;
2011-08-06 09:47:19 +02:00
if ( matchString ( name . c_str ( ) , pattern . c_str ( ) , ignoreCase ) ) {
2009-09-23 00:15:00 +00:00
matchIter = multipleMatches . find ( name ) ;
if ( matchIter = = multipleMatches . end ( ) ) {
multipleMatches [ name ] = true ;
} else {
if ( matchIter - > _value ) {
warning ( " Clash in case for match of pattern \" %s \" found in directory \" %s \" : \" %s \" " , pattern . c_str ( ) , directory . getPath ( ) . c_str ( ) , matchIter - > _key . c_str ( ) ) ;
matchIter - > _value = false ;
}
warning ( " Clash in case for match of pattern \" %s \" found in directory \" %s \" : \" %s \" " , pattern . c_str ( ) , directory . getPath ( ) . c_str ( ) , name . c_str ( ) ) ;
}
if ( nextPattern . empty ( ) )
2013-06-06 21:41:14 +02:00
addDirectory ( name , * i , priority , depth , flat ) ;
2009-09-23 00:15:00 +00:00
else
2013-06-06 21:41:14 +02:00
addSubDirectoriesMatching ( * i , nextPattern , ignoreCase , priority , depth , flat ) ;
2009-09-23 00:15:00 +00:00
}
}
}
2008-12-27 17:16:48 +00:00
2008-11-07 13:42:05 +00:00
void SearchSet : : remove ( const String & name ) {
2008-12-15 12:55:13 +00:00
ArchiveNodeList : : iterator it = find ( name ) ;
2008-08-31 13:58:17 +00:00
if ( it ! = _list . end ( ) ) {
2008-10-22 17:44:12 +00:00
if ( it - > _autoFree )
delete it - > _arc ;
2008-08-31 13:58:17 +00:00
_list . erase ( it ) ;
}
}
2008-09-06 22:09:34 +00:00
bool SearchSet : : hasArchive ( const String & name ) const {
return ( find ( name ) ! = _list . end ( ) ) ;
}
2023-02-10 01:19:34 +01:00
Archive * SearchSet : : getArchive ( const String & name ) const {
auto arch = find ( name ) ;
if ( arch = = _list . end ( ) )
return nullptr ;
return arch - > _arc ;
}
2008-08-31 13:58:17 +00:00
void SearchSet : : clear ( ) {
2008-12-15 12:55:13 +00:00
for ( ArchiveNodeList : : iterator i = _list . begin ( ) ; i ! = _list . end ( ) ; + + i ) {
2008-10-22 17:44:12 +00:00
if ( i - > _autoFree )
delete i - > _arc ;
}
2008-08-31 13:58:17 +00:00
_list . clear ( ) ;
}
2008-11-07 13:42:05 +00:00
void SearchSet : : setPriority ( const String & name , int priority ) {
2008-12-15 12:55:13 +00:00
ArchiveNodeList : : iterator it = find ( name ) ;
2008-08-31 13:58:17 +00:00
if ( it = = _list . end ( ) ) {
warning ( " SearchSet::setPriority: archive '%s' is not present " , name . c_str ( ) ) ;
return ;
}
2008-11-21 17:49:19 +00:00
if ( priority = = it - > _priority )
2008-08-31 13:58:17 +00:00
return ;
Node node ( * it ) ;
_list . erase ( it ) ;
node . _priority = priority ;
insert ( node ) ;
}
2021-08-01 19:33:22 -04:00
bool SearchSet : : hasFile ( const Path & path ) const {
if ( path . empty ( ) )
2008-08-31 13:58:17 +00:00
return false ;
2011-12-13 17:20:25 +01:00
ArchiveNodeList : : const_iterator it = _list . begin ( ) ;
2016-02-09 12:10:04 +01:00
for ( ; it ! = _list . end ( ) ; + + it ) {
2021-08-01 19:33:22 -04:00
if ( it - > _arc - > hasFile ( path ) )
2008-08-31 13:58:17 +00:00
return true ;
}
return false ;
}
2023-01-07 15:13:13 +01:00
int SearchSet : : listMatchingMembers ( ArchiveMemberList & list , const Path & pattern , bool matchPathComponents ) const {
2008-08-31 13:58:17 +00:00
int matches = 0 ;
2011-12-13 17:20:25 +01:00
ArchiveNodeList : : const_iterator it = _list . begin ( ) ;
2016-02-09 12:10:04 +01:00
for ( ; it ! = _list . end ( ) ; + + it )
2023-01-07 15:13:13 +01:00
matches + = it - > _arc - > listMatchingMembers ( list , pattern , matchPathComponents ) ;
2008-08-31 13:58:17 +00:00
return matches ;
}
2011-12-13 17:20:25 +01:00
int SearchSet : : listMembers ( ArchiveMemberList & list ) const {
2008-09-18 08:19:00 +00:00
int matches = 0 ;
2011-12-13 17:20:25 +01:00
ArchiveNodeList : : const_iterator it = _list . begin ( ) ;
2016-02-09 12:10:04 +01:00
for ( ; it ! = _list . end ( ) ; + + it )
2008-11-21 17:49:19 +00:00
matches + = it - > _arc - > listMembers ( list ) ;
2008-09-18 08:19:00 +00:00
return matches ;
}
2023-07-08 18:13:28 +02:00
const ArchiveMemberPtr SearchSet : : getMember ( const Path & path , Archive * * container ) const {
2021-08-01 19:33:22 -04:00
if ( path . empty ( ) )
2008-11-01 12:49:29 +00:00
return ArchiveMemberPtr ( ) ;
2011-12-13 17:20:25 +01:00
ArchiveNodeList : : const_iterator it = _list . begin ( ) ;
2016-02-09 12:10:04 +01:00
for ( ; it ! = _list . end ( ) ; + + it ) {
2023-07-08 18:13:28 +02:00
if ( it - > _arc - > hasFile ( path ) ) {
if ( container ) {
* container = it - > _arc ;
}
2021-08-01 19:33:22 -04:00
return it - > _arc - > getMember ( path ) ;
2023-07-08 18:13:28 +02:00
}
2008-11-01 12:49:29 +00:00
}
return ArchiveMemberPtr ( ) ;
}
2023-07-08 18:13:28 +02:00
const ArchiveMemberPtr SearchSet : : getMember ( const Path & path ) const {
return getMember ( path , nullptr ) ;
}
2021-08-01 19:33:22 -04:00
SeekableReadStream * SearchSet : : createReadStreamForMember ( const Path & path ) const {
if ( path . empty ( ) )
2018-04-05 20:25:28 +02:00
return nullptr ;
2008-08-31 13:58:17 +00:00
2009-03-09 22:26:02 +00:00
ArchiveNodeList : : const_iterator it = _list . begin ( ) ;
2016-02-09 12:10:04 +01:00
for ( ; it ! = _list . end ( ) ; + + it ) {
2021-08-01 19:33:22 -04:00
SeekableReadStream * stream = it - > _arc - > createReadStreamForMember ( path ) ;
2009-02-22 16:48:02 +00:00
if ( stream )
return stream ;
2008-08-31 13:58:17 +00:00
}
2018-04-05 20:25:28 +02:00
return nullptr ;
2008-08-31 13:58:17 +00:00
}
2023-03-11 15:05:16 +01:00
SeekableReadStream * SearchSet : : createReadStreamForMemberNext ( const Path & path , const Archive * starting ) const {
if ( path . empty ( ) )
return nullptr ;
ArchiveNodeList : : const_iterator it = _list . begin ( ) ;
for ( ; it ! = _list . end ( ) ; + + it )
if ( it - > _arc = = starting ) {
+ + it ;
break ;
}
for ( ; it ! = _list . end ( ) ; + + it ) {
SeekableReadStream * stream = it - > _arc - > createReadStreamForMember ( path ) ;
if ( stream )
return stream ;
}
return nullptr ;
}
2008-09-11 13:24:01 +00:00
2008-09-27 18:32:01 +00:00
SearchManager : : SearchManager ( ) {
2018-04-15 14:00:56 +02:00
clear ( ) ; // Force a reset
2008-09-27 18:32:01 +00:00
}
void SearchManager : : clear ( ) {
SearchSet : : clear ( ) ;
// Always keep system specific archives in the SearchManager.
// But we give them a lower priority than the default priority (which is 0),
// so that archives added by client code are searched first.
2009-01-29 22:09:06 +00:00
if ( g_system )
g_system - > addSysArchivesToSearchSet ( * this , - 1 ) ;
2008-10-02 17:52:29 +00:00
2019-12-16 22:58:26 +02:00
# ifndef __ANDROID__
2008-10-02 17:52:29 +00:00
// Add the current dir as a very last resort.
2021-02-28 01:28:23 -08:00
// See also bug #3984.
2019-12-16 22:58:26 +02:00
// But don't do this for Android platform, since it may lead to crashes
2008-10-22 17:08:17 +00:00
addDirectory ( " . " , " . " , - 2 ) ;
2019-12-16 22:58:26 +02:00
# endif
2008-09-27 18:32:01 +00:00
}
2008-09-11 13:24:01 +00:00
2017-07-10 21:17:41 +02:00
DECLARE_SINGLETON ( SearchManager ) ;
2011-06-28 02:06:23 +03:00
} // namespace Common