1 /** @file package.h Package containing metadata, data, and/or files. 2 * 3 * @authors Copyright (c) 2014-2017 Jaakko Keränen <jaakko.keranen@iki.fi> 4 * 5 * @par License 6 * LGPL: http://www.gnu.org/licenses/lgpl.html 7 * 8 * <small>This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU Lesser General Public License as published by 10 * the Free Software Foundation; either version 3 of the License, or (at your 11 * option) any later version. This program is distributed in the hope that it 12 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 13 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 14 * General Public License for more details. You should have received a copy of 15 * the GNU Lesser General Public License along with this program; if not, see: 16 * http://www.gnu.org/licenses</small> 17 */ 18 19 #ifndef LIBDENG2_PACKAGE_H 20 #define LIBDENG2_PACKAGE_H 21 22 #include "../String" 23 #include "../File" 24 #include "../FileIndex" 25 #include "../Version" 26 #include "../IObject" 27 28 #include <QSet> 29 30 namespace de { 31 32 class Package; 33 34 /** 35 * Container package with metadata, data, and/or files. 36 * @ingroup fs 37 * 38 * A @em package is a collection of files packaged into a single unit (possibly using an 39 * Archive). Examples of packages are add-on packages (in various formats, e.g., PK3/ZIP 40 * archive or the Snowberry add-on bundle), savegames, custom maps, and demos. 41 * 42 * An instance of Package represents a package that is currently loaded. Note that 43 * the package's metadata namespace is owned by the file that contains the package; 44 * Package only consists of state that is relevant while the package is loaded (i.e., 45 * in active use). 46 */ 47 class DENG2_PUBLIC Package : public IObject 48 { 49 public: 50 /// Package's source is missing or inaccessible. @ingroup errors 51 DENG2_ERROR(SourceError); 52 53 /// Package fails validation. @ingroup errors 54 DENG2_ERROR(ValidationError); 55 56 /// Checked metadata does not describe a package. @ingroup errors 57 DENG2_SUB_ERROR(ValidationError, NotPackageError); 58 59 /// Package is missing some required metadata. @ingroup errors 60 DENG2_SUB_ERROR(ValidationError, IncompleteMetadataError); 61 62 typedef QSet<String> Assets; 63 64 /** 65 * Utility for accessing asset metadata. @ingroup fs 66 */ 67 class DENG2_PUBLIC Asset : public RecordAccessor 68 { 69 public: 70 Asset(Record const &rec); 71 Asset(Record const *rec); 72 73 /** 74 * Retrieves the value of a variable and resolves it to an absolute path in 75 * relation to the asset. 76 * 77 * @param varName Variable name in the package asset metadata. 78 * 79 * @return Absolute path. 80 * 81 * @see ScriptedInfo::absolutePathInContext() 82 */ 83 String absolutePath(String const &varName) const; 84 }; 85 86 public: 87 /** 88 * Creates a package whose data comes from a file. The file's metadata is used 89 * as the package's metadata namespace. 90 * 91 * @param file File or folder containing the package's contents. 92 */ 93 Package(File const &file); 94 95 virtual ~Package(); 96 97 /** 98 * Returns the ".pack" file of the Package. In practice, this may be a ZIP folder, an 99 * regular folder, or a link to a DataBundle. You should use sourceFile() to access 100 * the file in which the package's contents are actually stored. 101 * 102 * @return Package file. 103 */ 104 File const &file() const; 105 106 /** 107 * Returns the original source file of the package, where the package's contents are 108 * being sourced from. This is usually the file referenced by the "path" member in 109 * the package metadata. 110 */ 111 File const &sourceFile() const; 112 113 bool sourceFileExists() const; 114 115 /** 116 * Returns the package's root folder. 117 */ 118 Folder const &root() const; 119 120 /** 121 * Returns the unique package identifier. This is the file name of the package 122 * without any file extension. 123 */ 124 String identifier() const; 125 126 /** 127 * Version of the loaded package. The version can be specified either in the 128 * file name (following an underscore) or in the metadata. 129 */ 130 Version version() const; 131 132 /** 133 * Composes a list of assets contained in the package. 134 * 135 * @return Assets declared in the package metadata. 136 */ 137 Assets assets() const; 138 139 /** 140 * Executes a script function in the metadata of the package. 141 * 142 * @param name Name of the function to call. 143 * 144 * @return @c true, if the function exists and was called. @c false, if the 145 * function was not found. 146 */ 147 bool executeFunction(String const &name); 148 149 void setOrder(int ordinal); 150 151 int order() const; 152 153 void findPartialPath(String const &path, FileIndex::FoundFiles &found) const; 154 155 /** 156 * Called by PackageLoader after the package has been marked as loaded. 157 */ 158 virtual void didLoad(); 159 160 /** 161 * Called by PackageLoader immediately before the package is marked as unloaded. 162 */ 163 virtual void aboutToUnload(); 164 165 // Implements IObject. 166 Record &objectNamespace(); 167 Record const &objectNamespace() const; 168 169 public: 170 /** 171 * Parse the embedded metadata found in a package file. 172 * 173 * @param packageFile File containing a package. 174 */ 175 static void parseMetadata(File &packageFile); 176 177 /** 178 * Checks that all the metadata seems legit. An IncompleteMetadataError or 179 * another exception is thrown if the package is not deemed valid. 180 * 181 * @param packageInfo Metadata to validate. 182 */ 183 static void validateMetadata(Record const &packageInfo); 184 185 static Record &initializeMetadata(File &packageFile, String const &id = String()); 186 187 static Record const &metadata(File const &packageFile); 188 189 static QStringList tags(File const &packageFile); 190 191 static bool matchTags(File const &packageFile, String const &tagRegExp); 192 193 static QStringList tags(String const& tagsString); 194 195 static StringList requires(File const &packageFile); 196 197 static void addRequiredPackage(File &packageFile, String const &id); 198 199 static bool hasOptionalContent(String const &packageId); 200 201 static bool hasOptionalContent(File const &packageFile); 202 203 /** 204 * Splits a string containing a package identifier and version. The 205 * expected format of the string is `{packageId}_{version}`. 206 * 207 * @param identifier_version Identifier and version. 208 * 209 * @return The split components. 210 */ 211 static std::pair<String, Version> split(String const &identifier_version); 212 213 static String splitToHumanReadable(String const &identifier_version); 214 215 static bool equals(String const &id1, String const &id2); 216 217 static String identifierForFile(File const &file); 218 219 static String versionedIdentifierForFile(File const &file); 220 221 static Version versionForFile(File const &file); 222 223 /** 224 * Locates the file that represents the package where @a file is in. 225 * 226 * @param file File. 227 * 228 * @return Containing package, or nullptr if the file is not inside a package. 229 */ 230 static File const *containerOfFile(File const &file); 231 232 static String identifierForContainerOfFile(File const &file); 233 234 /** 235 * Finds the package that contains @a file and returns its modification time. 236 * If the file doesn't appear to be inside a package, returns the file's 237 * modification time. 238 * 239 * @param file File. 240 * 241 * @return Modification time of file or package. 242 */ 243 static Time containerOfFileModifiedAt(File const &file); 244 245 static String const VAR_PACKAGE; 246 static String const VAR_PACKAGE_ID; 247 static String const VAR_PACKAGE_ALIAS; 248 static String const VAR_PACKAGE_TITLE; 249 static String const VAR_ID; 250 static String const VAR_TITLE; 251 static String const VAR_VERSION; 252 253 private: 254 DENG2_PRIVATE(d) 255 }; 256 257 } // namespace de 258 259 #endif // LIBDENG2_PACKAGE_H 260