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.

364 lines
11KB

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