Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

juce_PropertiesFile.cpp 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. namespace PropertyFileConstants
  18. {
  19. static const int magicNumber = (int) ByteOrder::littleEndianInt ("PROP");
  20. static const int magicNumberCompressed = (int) ByteOrder::littleEndianInt ("CPRP");
  21. static const char* const fileTag = "PROPERTIES";
  22. static const char* const valueTag = "VALUE";
  23. static const char* const nameAttribute = "name";
  24. static const char* const valueAttribute = "val";
  25. }
  26. //==============================================================================
  27. PropertiesFile::Options::Options()
  28. : commonToAllUsers (false),
  29. ignoreCaseOfKeyNames (false),
  30. doNotSave (false),
  31. millisecondsBeforeSaving (3000),
  32. storageFormat (PropertiesFile::storeAsXML),
  33. processLock (nullptr)
  34. {
  35. }
  36. File PropertiesFile::Options::getDefaultFile() const
  37. {
  38. // mustn't have illegal characters in this name..
  39. jassert (applicationName == File::createLegalFileName (applicationName));
  40. #if JUCE_MAC || JUCE_IOS
  41. File dir (commonToAllUsers ? "/Library/"
  42. : "~/Library/");
  43. if (osxLibrarySubFolder != "Preferences" && osxLibrarySubFolder != "Application Support")
  44. {
  45. /* The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple
  46. have changed their advice, and now stipulate that settings should go in "Library/Application Support".
  47. Because older apps would be broken by a silent change in this class's behaviour, you must now
  48. explicitly set the osxLibrarySubFolder value to indicate which path you want to use.
  49. In newer apps, you should always set this to "Application Support".
  50. If your app needs to load settings files that were created by older versions of juce and
  51. you want to maintain backwards-compatibility, then you can set this to "Preferences".
  52. But.. for better Apple-compliance, the recommended approach would be to write some code that
  53. finds your old settings files in ~/Library/Preferences, moves them to ~/Library/Application Support,
  54. and then uses the new path.
  55. */
  56. jassertfalse;
  57. dir = dir.getChildFile ("Application Support");
  58. }
  59. else
  60. {
  61. dir = dir.getChildFile (osxLibrarySubFolder);
  62. }
  63. if (folderName.isNotEmpty())
  64. dir = dir.getChildFile (folderName);
  65. #elif JUCE_LINUX || JUCE_ANDROID
  66. const File dir (File (commonToAllUsers ? "/var" : "~")
  67. .getChildFile (folderName.isNotEmpty() ? folderName
  68. : ("." + applicationName)));
  69. #elif JUCE_WINDOWS
  70. File dir (File::getSpecialLocation (commonToAllUsers ? File::commonApplicationDataDirectory
  71. : File::userApplicationDataDirectory));
  72. if (dir == File::nonexistent)
  73. return File::nonexistent;
  74. dir = dir.getChildFile (folderName.isNotEmpty() ? folderName
  75. : applicationName);
  76. #endif
  77. return dir.getChildFile (applicationName)
  78. .withFileExtension (filenameSuffix);
  79. }
  80. //==============================================================================
  81. PropertiesFile::PropertiesFile (const File& f, const Options& o)
  82. : PropertySet (o.ignoreCaseOfKeyNames),
  83. file (f), options (o),
  84. loadedOk (false), needsWriting (false)
  85. {
  86. reload();
  87. }
  88. PropertiesFile::PropertiesFile (const Options& o)
  89. : PropertySet (o.ignoreCaseOfKeyNames),
  90. file (o.getDefaultFile()), options (o),
  91. loadedOk (false), needsWriting (false)
  92. {
  93. reload();
  94. }
  95. bool PropertiesFile::reload()
  96. {
  97. ProcessScopedLock pl (createProcessLock());
  98. if (pl != nullptr && ! pl->isLocked())
  99. return false; // locking failure..
  100. loadedOk = (! file.exists()) || loadAsBinary() || loadAsXml();
  101. return loadedOk;
  102. }
  103. PropertiesFile::~PropertiesFile()
  104. {
  105. saveIfNeeded();
  106. }
  107. InterProcessLock::ScopedLockType* PropertiesFile::createProcessLock() const
  108. {
  109. return options.processLock != nullptr ? new InterProcessLock::ScopedLockType (*options.processLock) : nullptr;
  110. }
  111. bool PropertiesFile::saveIfNeeded()
  112. {
  113. const ScopedLock sl (getLock());
  114. return (! needsWriting) || save();
  115. }
  116. bool PropertiesFile::needsToBeSaved() const
  117. {
  118. const ScopedLock sl (getLock());
  119. return needsWriting;
  120. }
  121. void PropertiesFile::setNeedsToBeSaved (const bool needsToBeSaved_)
  122. {
  123. const ScopedLock sl (getLock());
  124. needsWriting = needsToBeSaved_;
  125. }
  126. bool PropertiesFile::save()
  127. {
  128. const ScopedLock sl (getLock());
  129. stopTimer();
  130. if (options.doNotSave
  131. || file == File::nonexistent
  132. || file.isDirectory()
  133. || ! file.getParentDirectory().createDirectory())
  134. return false;
  135. if (options.storageFormat == storeAsXML)
  136. return saveAsXml();
  137. return saveAsBinary();
  138. }
  139. bool PropertiesFile::loadAsXml()
  140. {
  141. XmlDocument parser (file);
  142. ScopedPointer<XmlElement> doc (parser.getDocumentElement (true));
  143. if (doc != nullptr && doc->hasTagName (PropertyFileConstants::fileTag))
  144. {
  145. doc = parser.getDocumentElement();
  146. if (doc != nullptr)
  147. {
  148. forEachXmlChildElementWithTagName (*doc, e, PropertyFileConstants::valueTag)
  149. {
  150. const String name (e->getStringAttribute (PropertyFileConstants::nameAttribute));
  151. if (name.isNotEmpty())
  152. {
  153. getAllProperties().set (name,
  154. e->getFirstChildElement() != nullptr
  155. ? e->getFirstChildElement()->createDocument (String::empty, true)
  156. : e->getStringAttribute (PropertyFileConstants::valueAttribute));
  157. }
  158. }
  159. return true;
  160. }
  161. // must be a pretty broken XML file we're trying to parse here,
  162. // or a sign that this object needs an InterProcessLock,
  163. // or just a failure reading the file. This last reason is why
  164. // we don't jassertfalse here.
  165. }
  166. return false;
  167. }
  168. bool PropertiesFile::saveAsXml()
  169. {
  170. XmlElement doc (PropertyFileConstants::fileTag);
  171. for (int i = 0; i < getAllProperties().size(); ++i)
  172. {
  173. XmlElement* const e = doc.createNewChildElement (PropertyFileConstants::valueTag);
  174. e->setAttribute (PropertyFileConstants::nameAttribute, getAllProperties().getAllKeys() [i]);
  175. // if the value seems to contain xml, store it as such..
  176. if (XmlElement* const childElement = XmlDocument::parse (getAllProperties().getAllValues() [i]))
  177. e->addChildElement (childElement);
  178. else
  179. e->setAttribute (PropertyFileConstants::valueAttribute,
  180. getAllProperties().getAllValues() [i]);
  181. }
  182. ProcessScopedLock pl (createProcessLock());
  183. if (pl != nullptr && ! pl->isLocked())
  184. return false; // locking failure..
  185. if (doc.writeToFile (file, String::empty))
  186. {
  187. needsWriting = false;
  188. return true;
  189. }
  190. return false;
  191. }
  192. bool PropertiesFile::loadAsBinary()
  193. {
  194. FileInputStream fileStream (file);
  195. if (fileStream.openedOk())
  196. {
  197. const int magicNumber = fileStream.readInt();
  198. if (magicNumber == PropertyFileConstants::magicNumberCompressed)
  199. {
  200. SubregionStream subStream (&fileStream, 4, -1, false);
  201. GZIPDecompressorInputStream gzip (subStream);
  202. return loadAsBinary (gzip);
  203. }
  204. if (magicNumber == PropertyFileConstants::magicNumber)
  205. return loadAsBinary (fileStream);
  206. }
  207. return false;
  208. }
  209. bool PropertiesFile::loadAsBinary (InputStream& input)
  210. {
  211. BufferedInputStream in (input, 2048);
  212. int numValues = in.readInt();
  213. while (--numValues >= 0 && ! in.isExhausted())
  214. {
  215. const String key (in.readString());
  216. const String value (in.readString());
  217. jassert (key.isNotEmpty());
  218. if (key.isNotEmpty())
  219. getAllProperties().set (key, value);
  220. }
  221. return true;
  222. }
  223. bool PropertiesFile::saveAsBinary()
  224. {
  225. ProcessScopedLock pl (createProcessLock());
  226. if (pl != nullptr && ! pl->isLocked())
  227. return false; // locking failure..
  228. TemporaryFile tempFile (file);
  229. ScopedPointer<OutputStream> out (tempFile.getFile().createOutputStream());
  230. if (out != nullptr)
  231. {
  232. if (options.storageFormat == storeAsCompressedBinary)
  233. {
  234. out->writeInt (PropertyFileConstants::magicNumberCompressed);
  235. out->flush();
  236. out = new GZIPCompressorOutputStream (out.release(), 9, true);
  237. }
  238. else
  239. {
  240. // have you set up the storage option flags correctly?
  241. jassert (options.storageFormat == storeAsBinary);
  242. out->writeInt (PropertyFileConstants::magicNumber);
  243. }
  244. const int numProperties = getAllProperties().size();
  245. out->writeInt (numProperties);
  246. for (int i = 0; i < numProperties; ++i)
  247. {
  248. out->writeString (getAllProperties().getAllKeys() [i]);
  249. out->writeString (getAllProperties().getAllValues() [i]);
  250. }
  251. out = nullptr;
  252. if (tempFile.overwriteTargetFileWithTemporary())
  253. {
  254. needsWriting = false;
  255. return true;
  256. }
  257. }
  258. return false;
  259. }
  260. void PropertiesFile::timerCallback()
  261. {
  262. saveIfNeeded();
  263. }
  264. void PropertiesFile::propertyChanged()
  265. {
  266. sendChangeMessage();
  267. needsWriting = true;
  268. if (options.millisecondsBeforeSaving > 0)
  269. startTimer (options.millisecondsBeforeSaving);
  270. else if (options.millisecondsBeforeSaving == 0)
  271. saveIfNeeded();
  272. }