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.

346 lines
12KB

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