1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-20 01:14:20 +00:00
JUCE/src/utilities/juce_PropertiesFile.cpp

344 lines
12 KiB
C++

/*
==============================================================================
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<InputStream> 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<XmlElement> 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 <OutputStream> 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