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