/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-11 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #include "../core/juce_StandardHeader.h" BEGIN_JUCE_NAMESPACE #include "juce_PropertiesFile.h" #include "../io/files/juce_TemporaryFile.h" #include "../io/files/juce_FileInputStream.h" #include "../io/files/juce_FileOutputStream.h" #include "../io/streams/juce_BufferedInputStream.h" #include "../io/streams/juce_SubregionStream.h" #include "../io/streams/juce_GZIPDecompressorInputStream.h" #include "../io/streams/juce_GZIPCompressorOutputStream.h" #include "../memory/juce_ScopedPointer.h" #include "../core/juce_SystemStats.h" #include "../threads/juce_InterProcessLock.h" #include "../text/juce_XmlDocument.h" //============================================================================== namespace PropertyFileConstants { static const int magicNumber = (int) ByteOrder::littleEndianInt ("PROP"); static const int magicNumberCompressed = (int) ByteOrder::littleEndianInt ("CPRP"); static const char* const fileTag = "PROPERTIES"; static const char* const valueTag = "VALUE"; static const char* const nameAttribute = "name"; static const char* const valueAttribute = "val"; } //============================================================================== PropertiesFile::PropertiesFile (const File& f, const int millisecondsBeforeSaving, const int options_, InterProcessLock* const processLock_) : PropertySet (ignoreCaseOfKeyNames), file (f), timerInterval (millisecondsBeforeSaving), options (options_), loadedOk (false), needsWriting (false), processLock (processLock_) { // You need to correctly specify just one storage format for the file jassert ((options_ & (storeAsBinary | storeAsCompressedBinary | storeAsXML)) == storeAsBinary || (options_ & (storeAsBinary | storeAsCompressedBinary | storeAsXML)) == storeAsCompressedBinary || (options_ & (storeAsBinary | storeAsCompressedBinary | storeAsXML)) == storeAsXML); ProcessScopedLock pl (createProcessLock()); if (pl != 0 && ! pl->isLocked()) return; // locking failure.. ScopedPointer fileStream (f.createInputStream()); if (fileStream != 0) { int magicNumber = fileStream->readInt(); if (magicNumber == PropertyFileConstants::magicNumberCompressed) { fileStream = new GZIPDecompressorInputStream (new SubregionStream (fileStream.release(), 4, -1, true), true); magicNumber = PropertyFileConstants::magicNumber; } if (magicNumber == PropertyFileConstants::magicNumber) { loadedOk = true; BufferedInputStream in (fileStream.release(), 2048, true); int numValues = in.readInt(); while (--numValues >= 0 && ! in.isExhausted()) { const String key (in.readString()); const String value (in.readString()); jassert (key.isNotEmpty()); if (key.isNotEmpty()) getAllProperties().set (key, value); } } else { // Not a binary props file - let's see if it's XML.. fileStream = 0; XmlDocument parser (f); ScopedPointer doc (parser.getDocumentElement (true)); if (doc != 0 && doc->hasTagName (PropertyFileConstants::fileTag)) { doc = parser.getDocumentElement(); if (doc != 0) { loadedOk = true; forEachXmlChildElementWithTagName (*doc, e, PropertyFileConstants::valueTag) { const String name (e->getStringAttribute (PropertyFileConstants::nameAttribute)); if (name.isNotEmpty()) { getAllProperties().set (name, e->getFirstChildElement() != 0 ? e->getFirstChildElement()->createDocument (String::empty, true) : e->getStringAttribute (PropertyFileConstants::valueAttribute)); } } } else { // must be a pretty broken XML file we're trying to parse here, // or a sign that this object needs an InterProcessLock, // or just a failure reading the file. This last reason is why // we don't jassertfalse here. } } } } else { loadedOk = ! f.exists(); } } PropertiesFile::~PropertiesFile() { if (! saveIfNeeded()) jassertfalse; } InterProcessLock::ScopedLockType* PropertiesFile::createProcessLock() const { return processLock != 0 ? new InterProcessLock::ScopedLockType (*processLock) : 0; } bool PropertiesFile::saveIfNeeded() { const ScopedLock sl (getLock()); return (! needsWriting) || save(); } bool PropertiesFile::needsToBeSaved() const { const ScopedLock sl (getLock()); return needsWriting; } void PropertiesFile::setNeedsToBeSaved (const bool needsToBeSaved_) { const ScopedLock sl (getLock()); needsWriting = needsToBeSaved_; } bool PropertiesFile::save() { const ScopedLock sl (getLock()); stopTimer(); if (file == File::nonexistent || file.isDirectory() || ! file.getParentDirectory().createDirectory()) return false; if ((options & storeAsXML) != 0) { XmlElement doc (PropertyFileConstants::fileTag); for (int i = 0; i < getAllProperties().size(); ++i) { XmlElement* const e = doc.createNewChildElement (PropertyFileConstants::valueTag); e->setAttribute (PropertyFileConstants::nameAttribute, getAllProperties().getAllKeys() [i]); // if the value seems to contain xml, store it as such.. XmlElement* const childElement = XmlDocument::parse (getAllProperties().getAllValues() [i]); if (childElement != 0) e->addChildElement (childElement); else e->setAttribute (PropertyFileConstants::valueAttribute, getAllProperties().getAllValues() [i]); } ProcessScopedLock pl (createProcessLock()); if (pl != 0 && ! pl->isLocked()) return false; // locking failure.. if (doc.writeToFile (file, String::empty)) { needsWriting = false; return true; } } else { ProcessScopedLock pl (createProcessLock()); if (pl != 0 && ! pl->isLocked()) return false; // locking failure.. TemporaryFile tempFile (file); ScopedPointer out (tempFile.getFile().createOutputStream()); if (out != 0) { if ((options & storeAsCompressedBinary) != 0) { out->writeInt (PropertyFileConstants::magicNumberCompressed); out->flush(); out = new GZIPCompressorOutputStream (out.release(), 9, true); } else { // have you set up the storage option flags correctly? jassert ((options & storeAsBinary) != 0); out->writeInt (PropertyFileConstants::magicNumber); } const int numProperties = getAllProperties().size(); out->writeInt (numProperties); for (int i = 0; i < numProperties; ++i) { out->writeString (getAllProperties().getAllKeys() [i]); out->writeString (getAllProperties().getAllValues() [i]); } out = 0; if (tempFile.overwriteTargetFileWithTemporary()) { needsWriting = false; return true; } } } return false; } void PropertiesFile::timerCallback() { saveIfNeeded(); } void PropertiesFile::propertyChanged() { sendChangeMessage(); needsWriting = true; if (timerInterval > 0) startTimer (timerInterval); else if (timerInterval == 0) saveIfNeeded(); } //============================================================================== const File PropertiesFile::getDefaultAppSettingsFile (const String& applicationName, const String& fileNameSuffix, const String& folderName, const bool commonToAllUsers) { // mustn't have illegal characters in this name.. jassert (applicationName == File::createLegalFileName (applicationName)); #if JUCE_MAC || JUCE_IOS File dir (commonToAllUsers ? "/Library/Preferences" : "~/Library/Preferences"); if (folderName.isNotEmpty()) dir = dir.getChildFile (folderName); #elif JUCE_LINUX || JUCE_ANDROID const File dir ((commonToAllUsers ? "/var/" : "~/") + (folderName.isNotEmpty() ? folderName : ("." + applicationName))); #elif JUCE_WINDOWS File dir (File::getSpecialLocation (commonToAllUsers ? File::commonApplicationDataDirectory : File::userApplicationDataDirectory)); if (dir == File::nonexistent) return File::nonexistent; dir = dir.getChildFile (folderName.isNotEmpty() ? folderName : applicationName); #endif return dir.getChildFile (applicationName) .withFileExtension (fileNameSuffix); } PropertiesFile* PropertiesFile::createDefaultAppPropertiesFile (const String& applicationName, const String& fileNameSuffix, const String& folderName, const bool commonToAllUsers, const int millisecondsBeforeSaving, const int propertiesFileOptions, InterProcessLock* processLock_) { const File file (getDefaultAppSettingsFile (applicationName, fileNameSuffix, folderName, commonToAllUsers)); jassert (file != File::nonexistent); if (file == File::nonexistent) return 0; return new PropertiesFile (file, millisecondsBeforeSaving, propertiesFileOptions,processLock_); } END_JUCE_NAMESPACE