OpenShot Audio Library | OpenShotAudio 0.4.0
juce_PropertiesFile.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24*/
25
26namespace juce
27{
28
29namespace PropertyFileConstants
30{
31 constexpr static const int magicNumber = (int) ByteOrder::makeInt ('P', 'R', 'O', 'P');
32 constexpr static const int magicNumberCompressed = (int) ByteOrder::makeInt ('C', 'P', 'R', 'P');
33
34 constexpr static const char* const fileTag = "PROPERTIES";
35 constexpr static const char* const valueTag = "VALUE";
36 constexpr static const char* const nameAttribute = "name";
37 constexpr static const char* const valueAttribute = "val";
38}
39
40//==============================================================================
42 : commonToAllUsers (false),
43 ignoreCaseOfKeyNames (false),
44 doNotSave (false),
45 millisecondsBeforeSaving (3000),
46 storageFormat (PropertiesFile::storeAsXML),
47 processLock (nullptr)
48{
49}
50
52{
53 // mustn't have illegal characters in this name..
54 jassert (applicationName == File::createLegalFileName (applicationName));
55
56 #if JUCE_MAC || JUCE_IOS
57 File dir (commonToAllUsers ? "/Library/"
58 : "~/Library/");
59
60 if (osxLibrarySubFolder != "Preferences"
61 && ! osxLibrarySubFolder.startsWith ("Application Support")
62 && ! osxLibrarySubFolder.startsWith ("Containers"))
63 {
64 /* The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple
65 have changed their advice, and now stipulate that settings should go in "Library/Application Support",
66 or Library/Containers/[app_bundle_id] for a sandboxed app.
67
68 Because older apps would be broken by a silent change in this class's behaviour, you must now
69 explicitly set the osxLibrarySubFolder value to indicate which path you want to use.
70
71 In newer apps, you should always set this to "Application Support"
72 or "Application Support/YourSubFolderName".
73
74 If your app needs to load settings files that were created by older versions of juce and
75 you want to maintain backwards-compatibility, then you can set this to "Preferences".
76 But.. for better Apple-compliance, the recommended approach would be to write some code that
77 finds your old settings files in ~/Library/Preferences, moves them to ~/Library/Application Support,
78 and then uses the new path.
79 */
80 jassertfalse;
81
82 dir = dir.getChildFile ("Application Support");
83 }
84 else
85 {
86 dir = dir.getChildFile (osxLibrarySubFolder);
87 }
88
89 if (folderName.isNotEmpty())
90 dir = dir.getChildFile (folderName);
91
92 #elif JUCE_LINUX || JUCE_BSD || JUCE_ANDROID
93 auto dir = File (commonToAllUsers ? "/var" : "~")
94 .getChildFile (folderName.isNotEmpty() ? folderName
95 : ("." + applicationName));
96
97 #elif JUCE_WINDOWS
100
101 if (dir == File())
102 return {};
103
104 dir = dir.getChildFile (folderName.isNotEmpty() ? folderName
105 : applicationName);
106 #endif
107
108 return (filenameSuffix.startsWithChar (L'.')
109 ? dir.getChildFile (applicationName).withFileExtension (filenameSuffix)
110 : dir.getChildFile (applicationName + "." + filenameSuffix));
111}
112
113
114//==============================================================================
116 : PropertySet (o.ignoreCaseOfKeyNames),
117 file (f), options (o)
118{
119 reload();
120}
121
123 : PropertySet (o.ignoreCaseOfKeyNames),
124 file (o.getDefaultFile()), options (o)
125{
126 reload();
127}
128
130{
131 ProcessScopedLock pl (createProcessLock());
132
133 if (pl != nullptr && ! pl->isLocked())
134 return false; // locking failure..
135
136 loadedOk = (! file.exists()) || loadAsBinary() || loadAsXml();
137 return loadedOk;
138}
139
141{
142 saveIfNeeded();
143}
144
145InterProcessLock::ScopedLockType* PropertiesFile::createProcessLock() const
146{
147 return options.processLock != nullptr ? new InterProcessLock::ScopedLockType (*options.processLock) : nullptr;
148}
149
151{
152 const ScopedLock sl (getLock());
153 return (! needsWriting) || save();
154}
155
157{
158 const ScopedLock sl (getLock());
159 return needsWriting;
160}
161
162void PropertiesFile::setNeedsToBeSaved (const bool needsToBeSaved_)
163{
164 const ScopedLock sl (getLock());
165 needsWriting = needsToBeSaved_;
166}
167
169{
170 const ScopedLock sl (getLock());
171
172 stopTimer();
173
174 if (options.doNotSave
175 || file == File()
176 || file.isDirectory()
178 return false;
179
180 if (options.storageFormat == storeAsXML)
181 return saveAsXml();
182
183 return saveAsBinary();
184}
185
186bool PropertiesFile::loadAsXml()
187{
188 if (auto doc = parseXMLIfTagMatches (file, PropertyFileConstants::fileTag))
189 {
190 for (auto* e : doc->getChildWithTagNameIterator (PropertyFileConstants::valueTag))
191 {
192 auto name = e->getStringAttribute (PropertyFileConstants::nameAttribute);
193
194 if (name.isNotEmpty())
195 getAllProperties().set (name,
196 e->getFirstChildElement() != nullptr
197 ? e->getFirstChildElement()->toString (XmlElement::TextFormat().singleLine().withoutHeader())
198 : e->getStringAttribute (PropertyFileConstants::valueAttribute));
199 }
200
201 return true;
202 }
203
204 return false;
205}
206
207bool PropertiesFile::saveAsXml()
208{
209 XmlElement doc (PropertyFileConstants::fileTag);
210 auto& props = getAllProperties();
211
212 for (int i = 0; i < props.size(); ++i)
213 {
214 auto* e = doc.createNewChildElement (PropertyFileConstants::valueTag);
215 e->setAttribute (PropertyFileConstants::nameAttribute, props.getAllKeys() [i]);
216
217 // if the value seems to contain xml, store it as such..
218 if (auto childElement = parseXML (props.getAllValues() [i]))
219 e->addChildElement (childElement.release());
220 else
221 e->setAttribute (PropertyFileConstants::valueAttribute, props.getAllValues() [i]);
222 }
223
224 ProcessScopedLock pl (createProcessLock());
225
226 if (pl != nullptr && ! pl->isLocked())
227 return false; // locking failure..
228
229 if (doc.writeTo (file, {}))
230 {
231 needsWriting = false;
232 return true;
233 }
234
235 return false;
236}
237
238bool PropertiesFile::loadAsBinary()
239{
240 FileInputStream fileStream (file);
241
242 if (fileStream.openedOk())
243 {
244 auto magicNumber = fileStream.readInt();
245
246 if (magicNumber == PropertyFileConstants::magicNumberCompressed)
247 {
248 SubregionStream subStream (&fileStream, 4, -1, false);
249 GZIPDecompressorInputStream gzip (subStream);
250 return loadAsBinary (gzip);
251 }
252
253 if (magicNumber == PropertyFileConstants::magicNumber)
254 return loadAsBinary (fileStream);
255 }
256
257 return false;
258}
259
260bool PropertiesFile::loadAsBinary (InputStream& input)
261{
262 BufferedInputStream in (input, 2048);
263
264 int numValues = in.readInt();
265
266 while (--numValues >= 0 && ! in.isExhausted())
267 {
268 auto key = in.readString();
269 auto value = in.readString();
270 jassert (key.isNotEmpty());
271
272 if (key.isNotEmpty())
273 getAllProperties().set (key, value);
274 }
275
276 return true;
277}
278
279bool PropertiesFile::saveAsBinary()
280{
281 ProcessScopedLock pl (createProcessLock());
282
283 if (pl != nullptr && ! pl->isLocked())
284 return false; // locking failure..
285
286 TemporaryFile tempFile (file);
287
288 {
289 FileOutputStream out (tempFile.getFile());
290
291 if (! out.openedOk())
292 return false;
293
294 if (options.storageFormat == storeAsCompressedBinary)
295 {
296 out.writeInt (PropertyFileConstants::magicNumberCompressed);
297 out.flush();
298
299 GZIPCompressorOutputStream zipped (out, 9);
300
301 if (! writeToStream (zipped))
302 return false;
303 }
304 else
305 {
306 // have you set up the storage option flags correctly?
307 jassert (options.storageFormat == storeAsBinary);
308
309 out.writeInt (PropertyFileConstants::magicNumber);
310
311 if (! writeToStream (out))
312 return false;
313 }
314 }
315
316 if (! tempFile.overwriteTargetFileWithTemporary())
317 return false;
318
319 needsWriting = false;
320 return true;
321}
322
323bool PropertiesFile::writeToStream (OutputStream& out)
324{
325 auto& props = getAllProperties();
326 auto& keys = props.getAllKeys();
327 auto& values = props.getAllValues();
328 auto numProperties = props.size();
329
330 if (! out.writeInt (numProperties))
331 return false;
332
333 for (int i = 0; i < numProperties; ++i)
334 {
335 if (! out.writeString (keys[i])) return false;
336 if (! out.writeString (values[i])) return false;
337 }
338
339 return true;
340}
341
342void PropertiesFile::timerCallback()
343{
344 saveIfNeeded();
345}
346
348{
350 needsWriting = true;
351
352 if (options.millisecondsBeforeSaving > 0)
354 else if (options.millisecondsBeforeSaving == 0)
355 saveIfNeeded();
356}
357
358} // namespace juce
static constexpr uint16 makeInt(uint8 leastSig, uint8 mostSig) noexcept
bool isDirectory() const
static File JUCE_CALLTYPE getSpecialLocation(const SpecialLocationType type)
File getChildFile(StringRef relativeOrAbsolutePath) const
Definition: juce_File.cpp:420
@ userApplicationDataDirectory
Definition: juce_File.h:895
@ commonApplicationDataDirectory
Definition: juce_File.h:907
File getParentDirectory() const
Definition: juce_File.cpp:358
File withFileExtension(StringRef newExtension) const
Definition: juce_File.cpp:707
static String createLegalFileName(const String &fileNameToFix)
Definition: juce_File.cpp:843
bool exists() const
Result createDirectory() const
Definition: juce_File.cpp:519
PropertiesFile(const Options &options)
void setNeedsToBeSaved(bool needsToBeSaved)
void propertyChanged() override
StringPairArray & getAllProperties() noexcept
const CriticalSection & getLock() const noexcept
void set(const String &key, const String &value)
void stopTimer() noexcept
Definition: juce_Timer.cpp:332
void startTimer(int intervalInMilliseconds) noexcept
Definition: juce_Timer.cpp:309
TextFormat withoutHeader() const