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 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. namespace juce
  14. {
  15. namespace PropertyFileConstants
  16. {
  17. constexpr static const int magicNumber = (int) ByteOrder::makeInt ('P', 'R', 'O', 'P');
  18. constexpr static const int magicNumberCompressed = (int) ByteOrder::makeInt ('C', 'P', 'R', 'P');
  19. constexpr static const char* const fileTag = "PROPERTIES";
  20. constexpr static const char* const valueTag = "VALUE";
  21. constexpr static const char* const nameAttribute = "name";
  22. constexpr static const char* const valueAttribute = "val";
  23. }
  24. //==============================================================================
  25. PropertiesFile::Options::Options()
  26. : commonToAllUsers (false),
  27. ignoreCaseOfKeyNames (false),
  28. doNotSave (false),
  29. millisecondsBeforeSaving (3000),
  30. storageFormat (PropertiesFile::storeAsXML),
  31. processLock (nullptr)
  32. {
  33. }
  34. File PropertiesFile::Options::getDefaultFile() const
  35. {
  36. // mustn't have illegal characters in this name..
  37. jassert (applicationName == File::createLegalFileName (applicationName));
  38. #if JUCE_MAC || JUCE_IOS
  39. File dir (commonToAllUsers ? "/Library/"
  40. : "~/Library/");
  41. if (osxLibrarySubFolder != "Preferences"
  42. && ! osxLibrarySubFolder.startsWith ("Application Support")
  43. && ! osxLibrarySubFolder.startsWith ("Containers"))
  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. or Library/Containers/[app_bundle_id] for a sandboxed app.
  48. Because older apps would be broken by a silent change in this class's behaviour, you must now
  49. explicitly set the osxLibrarySubFolder value to indicate which path you want to use.
  50. In newer apps, you should always set this to "Application Support"
  51. or "Application Support/YourSubFolderName".
  52. If your app needs to load settings files that were created by older versions of juce and
  53. you want to maintain backwards-compatibility, then you can set this to "Preferences".
  54. But.. for better Apple-compliance, the recommended approach would be to write some code that
  55. finds your old settings files in ~/Library/Preferences, moves them to ~/Library/Application Support,
  56. and then uses the new path.
  57. */
  58. jassertfalse;
  59. dir = dir.getChildFile ("Application Support");
  60. }
  61. else
  62. {
  63. dir = dir.getChildFile (osxLibrarySubFolder);
  64. }
  65. if (folderName.isNotEmpty())
  66. dir = dir.getChildFile (folderName);
  67. #elif JUCE_LINUX || JUCE_ANDROID
  68. auto dir = File (commonToAllUsers ? "/var" : "~")
  69. .getChildFile (folderName.isNotEmpty() ? folderName
  70. : ("." + applicationName));
  71. #elif JUCE_WINDOWS
  72. auto dir = File::getSpecialLocation (commonToAllUsers ? File::commonApplicationDataDirectory
  73. : File::userApplicationDataDirectory);
  74. if (dir == File())
  75. return {};
  76. dir = dir.getChildFile (folderName.isNotEmpty() ? folderName
  77. : applicationName);
  78. #endif
  79. return (filenameSuffix.startsWithChar (L'.')
  80. ? dir.getChildFile (applicationName).withFileExtension (filenameSuffix)
  81. : dir.getChildFile (applicationName + "." + filenameSuffix));
  82. }
  83. //==============================================================================
  84. PropertiesFile::PropertiesFile (const File& f, const Options& o)
  85. : PropertySet (o.ignoreCaseOfKeyNames),
  86. file (f), options (o)
  87. {
  88. reload();
  89. }
  90. PropertiesFile::PropertiesFile (const Options& o)
  91. : PropertySet (o.ignoreCaseOfKeyNames),
  92. file (o.getDefaultFile()), options (o)
  93. {
  94. reload();
  95. }
  96. bool PropertiesFile::reload()
  97. {
  98. ProcessScopedLock pl (createProcessLock());
  99. if (pl != nullptr && ! pl->isLocked())
  100. return false; // locking failure..
  101. loadedOk = (! file.exists()) || loadAsBinary() || loadAsXml();
  102. return loadedOk;
  103. }
  104. PropertiesFile::~PropertiesFile()
  105. {
  106. saveIfNeeded();
  107. }
  108. InterProcessLock::ScopedLockType* PropertiesFile::createProcessLock() const
  109. {
  110. return options.processLock != nullptr ? new InterProcessLock::ScopedLockType (*options.processLock) : nullptr;
  111. }
  112. bool PropertiesFile::saveIfNeeded()
  113. {
  114. const ScopedLock sl (getLock());
  115. return (! needsWriting) || save();
  116. }
  117. bool PropertiesFile::needsToBeSaved() const
  118. {
  119. const ScopedLock sl (getLock());
  120. return needsWriting;
  121. }
  122. void PropertiesFile::setNeedsToBeSaved (const bool needsToBeSaved_)
  123. {
  124. const ScopedLock sl (getLock());
  125. needsWriting = needsToBeSaved_;
  126. }
  127. bool PropertiesFile::save()
  128. {
  129. const ScopedLock sl (getLock());
  130. stopTimer();
  131. if (options.doNotSave
  132. || file == File()
  133. || file.isDirectory()
  134. || ! file.getParentDirectory().createDirectory())
  135. return false;
  136. if (options.storageFormat == storeAsXML)
  137. return saveAsXml();
  138. return saveAsBinary();
  139. }
  140. bool PropertiesFile::loadAsXml()
  141. {
  142. if (auto doc = parseXMLIfTagMatches (file, PropertyFileConstants::fileTag))
  143. {
  144. forEachXmlChildElementWithTagName (*doc, e, PropertyFileConstants::valueTag)
  145. {
  146. auto name = e->getStringAttribute (PropertyFileConstants::nameAttribute);
  147. if (name.isNotEmpty())
  148. getAllProperties().set (name,
  149. e->getFirstChildElement() != nullptr
  150. ? e->getFirstChildElement()->toString (XmlElement::TextFormat().singleLine().withoutHeader())
  151. : e->getStringAttribute (PropertyFileConstants::valueAttribute));
  152. }
  153. return true;
  154. }
  155. return false;
  156. }
  157. bool PropertiesFile::saveAsXml()
  158. {
  159. XmlElement doc (PropertyFileConstants::fileTag);
  160. auto& props = getAllProperties();
  161. for (int i = 0; i < props.size(); ++i)
  162. {
  163. auto* e = doc.createNewChildElement (PropertyFileConstants::valueTag);
  164. e->setAttribute (PropertyFileConstants::nameAttribute, props.getAllKeys() [i]);
  165. // if the value seems to contain xml, store it as such..
  166. if (auto childElement = parseXML (props.getAllValues() [i]))
  167. e->addChildElement (childElement.release());
  168. else
  169. e->setAttribute (PropertyFileConstants::valueAttribute, props.getAllValues() [i]);
  170. }
  171. ProcessScopedLock pl (createProcessLock());
  172. if (pl != nullptr && ! pl->isLocked())
  173. return false; // locking failure..
  174. if (doc.writeTo (file, {}))
  175. {
  176. needsWriting = false;
  177. return true;
  178. }
  179. return false;
  180. }
  181. bool PropertiesFile::loadAsBinary()
  182. {
  183. FileInputStream fileStream (file);
  184. if (fileStream.openedOk())
  185. {
  186. auto magicNumber = fileStream.readInt();
  187. if (magicNumber == PropertyFileConstants::magicNumberCompressed)
  188. {
  189. SubregionStream subStream (&fileStream, 4, -1, false);
  190. GZIPDecompressorInputStream gzip (subStream);
  191. return loadAsBinary (gzip);
  192. }
  193. if (magicNumber == PropertyFileConstants::magicNumber)
  194. return loadAsBinary (fileStream);
  195. }
  196. return false;
  197. }
  198. bool PropertiesFile::loadAsBinary (InputStream& input)
  199. {
  200. BufferedInputStream in (input, 2048);
  201. int numValues = in.readInt();
  202. while (--numValues >= 0 && ! in.isExhausted())
  203. {
  204. auto key = in.readString();
  205. auto value = in.readString();
  206. jassert (key.isNotEmpty());
  207. if (key.isNotEmpty())
  208. getAllProperties().set (key, value);
  209. }
  210. return true;
  211. }
  212. bool PropertiesFile::saveAsBinary()
  213. {
  214. ProcessScopedLock pl (createProcessLock());
  215. if (pl != nullptr && ! pl->isLocked())
  216. return false; // locking failure..
  217. TemporaryFile tempFile (file);
  218. {
  219. FileOutputStream out (tempFile.getFile());
  220. if (! out.openedOk())
  221. return false;
  222. if (options.storageFormat == storeAsCompressedBinary)
  223. {
  224. out.writeInt (PropertyFileConstants::magicNumberCompressed);
  225. out.flush();
  226. GZIPCompressorOutputStream zipped (out, 9);
  227. if (! writeToStream (zipped))
  228. return false;
  229. }
  230. else
  231. {
  232. // have you set up the storage option flags correctly?
  233. jassert (options.storageFormat == storeAsBinary);
  234. out.writeInt (PropertyFileConstants::magicNumber);
  235. if (! writeToStream (out))
  236. return false;
  237. }
  238. }
  239. if (! tempFile.overwriteTargetFileWithTemporary())
  240. return false;
  241. needsWriting = false;
  242. return true;
  243. }
  244. bool PropertiesFile::writeToStream (OutputStream& out)
  245. {
  246. auto& props = getAllProperties();
  247. auto& keys = props.getAllKeys();
  248. auto& values = props.getAllValues();
  249. auto numProperties = props.size();
  250. if (! out.writeInt (numProperties))
  251. return false;
  252. for (int i = 0; i < numProperties; ++i)
  253. {
  254. if (! out.writeString (keys[i])) return false;
  255. if (! out.writeString (values[i])) return false;
  256. }
  257. return true;
  258. }
  259. void PropertiesFile::timerCallback()
  260. {
  261. saveIfNeeded();
  262. }
  263. void PropertiesFile::propertyChanged()
  264. {
  265. sendChangeMessage();
  266. needsWriting = true;
  267. if (options.millisecondsBeforeSaving > 0)
  268. startTimer (options.millisecondsBeforeSaving);
  269. else if (options.millisecondsBeforeSaving == 0)
  270. saveIfNeeded();
  271. }
  272. } // namespace juce