The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

345 lines
12KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "../core/juce_StandardHeader.h"
  19. BEGIN_JUCE_NAMESPACE
  20. #include "juce_PropertiesFile.h"
  21. #include "../io/files/juce_TemporaryFile.h"
  22. #include "../io/files/juce_FileInputStream.h"
  23. #include "../io/files/juce_FileOutputStream.h"
  24. #include "../io/streams/juce_BufferedInputStream.h"
  25. #include "../io/streams/juce_SubregionStream.h"
  26. #include "../io/streams/juce_GZIPDecompressorInputStream.h"
  27. #include "../io/streams/juce_GZIPCompressorOutputStream.h"
  28. #include "../memory/juce_ScopedPointer.h"
  29. #include "../core/juce_SystemStats.h"
  30. #include "../threads/juce_InterProcessLock.h"
  31. #include "../text/juce_XmlDocument.h"
  32. //==============================================================================
  33. namespace PropertyFileConstants
  34. {
  35. static const int magicNumber = (int) ByteOrder::littleEndianInt ("PROP");
  36. static const int magicNumberCompressed = (int) ByteOrder::littleEndianInt ("CPRP");
  37. static const char* const fileTag = "PROPERTIES";
  38. static const char* const valueTag = "VALUE";
  39. static const char* const nameAttribute = "name";
  40. static const char* const valueAttribute = "val";
  41. }
  42. //==============================================================================
  43. PropertiesFile::PropertiesFile (const File& f, const int millisecondsBeforeSaving,
  44. const int options_, InterProcessLock* const processLock_)
  45. : PropertySet (ignoreCaseOfKeyNames),
  46. file (f),
  47. timerInterval (millisecondsBeforeSaving),
  48. options (options_),
  49. loadedOk (false),
  50. needsWriting (false),
  51. processLock (processLock_)
  52. {
  53. // You need to correctly specify just one storage format for the file
  54. jassert ((options_ & (storeAsBinary | storeAsCompressedBinary | storeAsXML)) == storeAsBinary
  55. || (options_ & (storeAsBinary | storeAsCompressedBinary | storeAsXML)) == storeAsCompressedBinary
  56. || (options_ & (storeAsBinary | storeAsCompressedBinary | storeAsXML)) == storeAsXML);
  57. ProcessScopedLock pl (createProcessLock());
  58. if (pl != 0 && ! pl->isLocked())
  59. return; // locking failure..
  60. ScopedPointer<InputStream> fileStream (f.createInputStream());
  61. if (fileStream != 0)
  62. {
  63. int magicNumber = fileStream->readInt();
  64. if (magicNumber == PropertyFileConstants::magicNumberCompressed)
  65. {
  66. fileStream = new GZIPDecompressorInputStream (new SubregionStream (fileStream.release(), 4, -1, true), true);
  67. magicNumber = PropertyFileConstants::magicNumber;
  68. }
  69. if (magicNumber == PropertyFileConstants::magicNumber)
  70. {
  71. loadedOk = true;
  72. BufferedInputStream in (fileStream.release(), 2048, true);
  73. int numValues = in.readInt();
  74. while (--numValues >= 0 && ! in.isExhausted())
  75. {
  76. const String key (in.readString());
  77. const String value (in.readString());
  78. jassert (key.isNotEmpty());
  79. if (key.isNotEmpty())
  80. getAllProperties().set (key, value);
  81. }
  82. }
  83. else
  84. {
  85. // Not a binary props file - let's see if it's XML..
  86. fileStream = 0;
  87. XmlDocument parser (f);
  88. ScopedPointer<XmlElement> doc (parser.getDocumentElement (true));
  89. if (doc != 0 && doc->hasTagName (PropertyFileConstants::fileTag))
  90. {
  91. doc = parser.getDocumentElement();
  92. if (doc != 0)
  93. {
  94. loadedOk = true;
  95. forEachXmlChildElementWithTagName (*doc, e, PropertyFileConstants::valueTag)
  96. {
  97. const String name (e->getStringAttribute (PropertyFileConstants::nameAttribute));
  98. if (name.isNotEmpty())
  99. {
  100. getAllProperties().set (name,
  101. e->getFirstChildElement() != 0
  102. ? e->getFirstChildElement()->createDocument (String::empty, true)
  103. : e->getStringAttribute (PropertyFileConstants::valueAttribute));
  104. }
  105. }
  106. }
  107. else
  108. {
  109. // must be a pretty broken XML file we're trying to parse here,
  110. // or a sign that this object needs an InterProcessLock,
  111. // or just a failure reading the file. This last reason is why
  112. // we don't jassertfalse here.
  113. }
  114. }
  115. }
  116. }
  117. else
  118. {
  119. loadedOk = ! f.exists();
  120. }
  121. }
  122. PropertiesFile::~PropertiesFile()
  123. {
  124. if (! saveIfNeeded())
  125. jassertfalse;
  126. }
  127. InterProcessLock::ScopedLockType* PropertiesFile::createProcessLock() const
  128. {
  129. return processLock != 0 ? new InterProcessLock::ScopedLockType (*processLock) : 0;
  130. }
  131. bool PropertiesFile::saveIfNeeded()
  132. {
  133. const ScopedLock sl (getLock());
  134. return (! needsWriting) || save();
  135. }
  136. bool PropertiesFile::needsToBeSaved() const
  137. {
  138. const ScopedLock sl (getLock());
  139. return needsWriting;
  140. }
  141. void PropertiesFile::setNeedsToBeSaved (const bool needsToBeSaved_)
  142. {
  143. const ScopedLock sl (getLock());
  144. needsWriting = needsToBeSaved_;
  145. }
  146. bool PropertiesFile::save()
  147. {
  148. const ScopedLock sl (getLock());
  149. stopTimer();
  150. if (file == File::nonexistent
  151. || file.isDirectory()
  152. || ! file.getParentDirectory().createDirectory())
  153. return false;
  154. if ((options & storeAsXML) != 0)
  155. {
  156. XmlElement doc (PropertyFileConstants::fileTag);
  157. for (int i = 0; i < getAllProperties().size(); ++i)
  158. {
  159. XmlElement* const e = doc.createNewChildElement (PropertyFileConstants::valueTag);
  160. e->setAttribute (PropertyFileConstants::nameAttribute, getAllProperties().getAllKeys() [i]);
  161. // if the value seems to contain xml, store it as such..
  162. XmlElement* const childElement = XmlDocument::parse (getAllProperties().getAllValues() [i]);
  163. if (childElement != 0)
  164. e->addChildElement (childElement);
  165. else
  166. e->setAttribute (PropertyFileConstants::valueAttribute,
  167. getAllProperties().getAllValues() [i]);
  168. }
  169. ProcessScopedLock pl (createProcessLock());
  170. if (pl != 0 && ! pl->isLocked())
  171. return false; // locking failure..
  172. if (doc.writeToFile (file, String::empty))
  173. {
  174. needsWriting = false;
  175. return true;
  176. }
  177. }
  178. else
  179. {
  180. ProcessScopedLock pl (createProcessLock());
  181. if (pl != 0 && ! pl->isLocked())
  182. return false; // locking failure..
  183. TemporaryFile tempFile (file);
  184. ScopedPointer <OutputStream> out (tempFile.getFile().createOutputStream());
  185. if (out != 0)
  186. {
  187. if ((options & storeAsCompressedBinary) != 0)
  188. {
  189. out->writeInt (PropertyFileConstants::magicNumberCompressed);
  190. out->flush();
  191. out = new GZIPCompressorOutputStream (out.release(), 9, true);
  192. }
  193. else
  194. {
  195. // have you set up the storage option flags correctly?
  196. jassert ((options & storeAsBinary) != 0);
  197. out->writeInt (PropertyFileConstants::magicNumber);
  198. }
  199. const int numProperties = getAllProperties().size();
  200. out->writeInt (numProperties);
  201. for (int i = 0; i < numProperties; ++i)
  202. {
  203. out->writeString (getAllProperties().getAllKeys() [i]);
  204. out->writeString (getAllProperties().getAllValues() [i]);
  205. }
  206. out = 0;
  207. if (tempFile.overwriteTargetFileWithTemporary())
  208. {
  209. needsWriting = false;
  210. return true;
  211. }
  212. }
  213. }
  214. return false;
  215. }
  216. void PropertiesFile::timerCallback()
  217. {
  218. saveIfNeeded();
  219. }
  220. void PropertiesFile::propertyChanged()
  221. {
  222. sendChangeMessage();
  223. needsWriting = true;
  224. if (timerInterval > 0)
  225. startTimer (timerInterval);
  226. else if (timerInterval == 0)
  227. saveIfNeeded();
  228. }
  229. //==============================================================================
  230. const File PropertiesFile::getDefaultAppSettingsFile (const String& applicationName,
  231. const String& fileNameSuffix,
  232. const String& folderName,
  233. const bool commonToAllUsers)
  234. {
  235. // mustn't have illegal characters in this name..
  236. jassert (applicationName == File::createLegalFileName (applicationName));
  237. #if JUCE_MAC || JUCE_IOS
  238. File dir (commonToAllUsers ? "/Library/Preferences"
  239. : "~/Library/Preferences");
  240. if (folderName.isNotEmpty())
  241. dir = dir.getChildFile (folderName);
  242. #elif JUCE_LINUX || JUCE_ANDROID
  243. const File dir ((commonToAllUsers ? "/var/" : "~/")
  244. + (folderName.isNotEmpty() ? folderName
  245. : ("." + applicationName)));
  246. #elif JUCE_WINDOWS
  247. File dir (File::getSpecialLocation (commonToAllUsers ? File::commonApplicationDataDirectory
  248. : File::userApplicationDataDirectory));
  249. if (dir == File::nonexistent)
  250. return File::nonexistent;
  251. dir = dir.getChildFile (folderName.isNotEmpty() ? folderName
  252. : applicationName);
  253. #endif
  254. return dir.getChildFile (applicationName)
  255. .withFileExtension (fileNameSuffix);
  256. }
  257. PropertiesFile* PropertiesFile::createDefaultAppPropertiesFile (const String& applicationName,
  258. const String& fileNameSuffix,
  259. const String& folderName,
  260. const bool commonToAllUsers,
  261. const int millisecondsBeforeSaving,
  262. const int propertiesFileOptions,
  263. InterProcessLock* processLock_)
  264. {
  265. const File file (getDefaultAppSettingsFile (applicationName,
  266. fileNameSuffix,
  267. folderName,
  268. commonToAllUsers));
  269. jassert (file != File::nonexistent);
  270. if (file == File::nonexistent)
  271. return 0;
  272. return new PropertiesFile (file, millisecondsBeforeSaving, propertiesFileOptions,processLock_);
  273. }
  274. END_JUCE_NAMESPACE