Browse Source

Refactored the PropertiesFile construction, introducing a PropertiesFile::Options structure to hold all the settings that previously were just passed as parameters. Also added a mandatory option for setting the OSX preferences path to be used - see the PropertiesFile::Options::osxLibrarySubFolder value for details.

Also on OSX, changed the location used for interprocess lock files to /var/tmp - this may affect applications which try to share locks with older builds of the same app.
tags/2021-05-28
Julian Storer 14 years ago
parent
commit
74469aaa83
15 changed files with 570 additions and 435 deletions
  1. +26
    -2
      extras/Introjucer/Source/Utility/jucer_StoredSettings.cpp
  2. +7
    -4
      extras/audio plugin host/Source/HostStartup.cpp
  3. +13
    -15
      extras/the jucer/src/utility/jucer_StoredSettings.cpp
  4. +1
    -1
      extras/the jucer/src/utility/jucer_StoredSettings.h
  5. +145
    -122
      juce_amalgamated.cpp
  6. +123
    -90
      juce_amalgamated.h
  7. +18
    -26
      src/application/juce_ApplicationProperties.cpp
  8. +3
    -13
      src/application/juce_ApplicationProperties.h
  9. +9
    -0
      src/containers/juce_PropertySet.cpp
  10. +5
    -0
      src/containers/juce_PropertySet.h
  11. +1
    -1
      src/core/juce_StandardHeader.h
  12. +8
    -6
      src/native/common/juce_posix_SharedCode.h
  13. +1
    -1
      src/native/windows/juce_win32_DirectShowComponent.cpp
  14. +95
    -78
      src/utilities/juce_PropertiesFile.cpp
  15. +115
    -76
      src/utilities/juce_PropertiesFile.h

+ 26
- 2
extras/Introjucer/Source/Utility/jucer_StoredSettings.cpp View File

@@ -68,8 +68,32 @@ void StoredSettings::flush()
}
props = nullptr;
props = PropertiesFile::createDefaultAppPropertiesFile ("Jucer2", "settings", String::empty,
false, 3000, PropertiesFile::storeAsXML);
{
// These settings are used in defining the properties file's location.
PropertiesFile::Options options;
options.applicationName = "Introjucer";
options.folderName = "Introjucer";
options.filenameSuffix = "settings";
options.osxLibrarySubFolder = "Application Support";
props = new PropertiesFile (options);
// Because older versions of the introjucer saved their settings under a different
// name, this code is an example of how to migrate your old settings files...
if (! props->getFile().exists())
{
PropertiesFile::Options oldOptions;
oldOptions.applicationName = "Jucer2";
oldOptions.filenameSuffix = "settings";
oldOptions.osxLibrarySubFolder = "Preferences";
PropertiesFile oldProps (oldOptions);
if (oldProps.getFile().exists())
props->addAllPropertiesFrom (oldProps);
}
}
// recent files...
recentFiles.restoreFromString (props->getValue ("recentFiles"));


+ 7
- 4
extras/audio plugin host/Source/HostStartup.cpp View File

@@ -51,10 +51,13 @@ public:
void initialise (const String& commandLine)
{
// initialise our settings file..
ApplicationProperties::getInstance()
->setStorageParameters ("Juce Audio Plugin Host",
"settings", String::empty, 1000,
PropertiesFile::storeAsXML);
PropertiesFile::Options options;
options.applicationName = "Juce Audio Plugin Host";
options.filenameSuffix = "settings";
options.osxLibrarySubFolder = "Preferences";
ApplicationProperties::getInstance()->setStorageParameters (options);
commandManager = new ApplicationCommandManager();


+ 13
- 15
extras/the jucer/src/utility/jucer_StoredSettings.cpp View File

@@ -29,7 +29,6 @@
//==============================================================================
StoredSettings::StoredSettings()
: props (0)
{
flush();
}
@@ -37,7 +36,7 @@ StoredSettings::StoredSettings()
StoredSettings::~StoredSettings()
{
flush();
deleteAndZero (props);
props = nullptr;
clearSingletonInstance();
}
@@ -52,31 +51,30 @@ PropertiesFile& StoredSettings::getProps()
void StoredSettings::flush()
{
if (props != 0)
if (props != nullptr)
{
props->setValue ("recentFiles", recentFiles.toString());
props->removeValue ("keyMappings");
XmlElement* keys = commandManager->getKeyMappings()->createXml (true);
ScopedPointer<XmlElement> keys (commandManager->getKeyMappings()->createXml (true));
if (keys != 0)
{
if (keys != nullptr)
props->setValue ("keyMappings", keys);
delete keys;
}
for (int i = 0; i < swatchColours.size(); ++i)
props->setValue ("swatchColour" + String (i), colourToHex (swatchColours [i]));
}
deleteAndZero (props);
props = nullptr;
{
PropertiesFile::Options options;
options.applicationName = "Jucer";
options.filenameSuffix = "settings";
options.osxLibrarySubFolder = "Preferences";
props = PropertiesFile::createDefaultAppPropertiesFile ("Jucer",
"settings",
String::empty,
false, 3000,
PropertiesFile::storeAsXML);
props = new PropertiesFile (options);
}
// recent files...
recentFiles.restoreFromString (props->getValue ("recentFiles"));


+ 1
- 1
extras/the jucer/src/utility/jucer_StoredSettings.h View File

@@ -53,7 +53,7 @@ public:
Array <Colour> swatchColours;
private:
PropertiesFile* props;
ScopedPointer<PropertiesFile> props;
};


+ 145
- 122
juce_amalgamated.cpp View File

@@ -4107,6 +4107,15 @@ bool PropertySet::containsKey (const String& keyName) const noexcept
return properties.getAllKeys().contains (keyName, ignoreCaseOfKeys);
}

void PropertySet::addAllPropertiesFrom (const PropertySet& source)
{
const ScopedLock sl (source.getLock());

for (int i = 0; i < source.properties.size(); ++i)
setValue (source.properties.getAllKeys() [i],
source.properties.getAllValues() [i]);
}

void PropertySet::setFallbackPropertySet (PropertySet* fallbackProperties_) noexcept
{
const ScopedLock sl (lock);
@@ -19899,10 +19908,7 @@ BEGIN_JUCE_NAMESPACE
juce_ImplementSingleton (ApplicationProperties)

ApplicationProperties::ApplicationProperties()
: msBeforeSaving (3000),
options (PropertiesFile::storeAsBinary),
commonSettingsAreReadOnly (0),
processLock (nullptr)
: commonSettingsAreReadOnly (0)
{
}

@@ -19912,19 +19918,9 @@ ApplicationProperties::~ApplicationProperties()
clearSingletonInstance();
}

void ApplicationProperties::setStorageParameters (const String& applicationName,
const String& fileNameSuffix,
const String& folderName_,
const int millisecondsBeforeSaving,
const int propertiesFileOptions,
InterProcessLock* processLock_)
void ApplicationProperties::setStorageParameters (const PropertiesFile::Options& newOptions)
{
appName = applicationName;
fileSuffix = fileNameSuffix;
folderName = folderName_;
msBeforeSaving = millisecondsBeforeSaving;
options = propertiesFileOptions;
processLock = processLock_;
options = newOptions;
}

bool ApplicationProperties::testWriteAccess (const bool testUserSettings,
@@ -19947,9 +19943,9 @@ bool ApplicationProperties::testWriteAccess (const bool testUserSettings,
filenames << '\n' << commonProps->getFile().getFullPathName();

AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
appName + TRANS(" - Unable to save settings"),
options.applicationName + TRANS(" - Unable to save settings"),
TRANS("An error occurred when trying to save the application's settings file...\n\nIn order to save and restore its settings, ")
+ appName + TRANS(" needs to be able to write to the following files:\n")
+ options.applicationName + TRANS(" needs to be able to write to the following files:\n")
+ filenames
+ TRANS("\n\nMake sure that these files aren't read-only, and that the disk isn't full."));
}
@@ -19962,19 +19958,24 @@ bool ApplicationProperties::testWriteAccess (const bool testUserSettings,

void ApplicationProperties::openFiles()
{
// You need to call setStorageParameters() before trying to get hold of the
// properties!
jassert (appName.isNotEmpty());
// You need to call setStorageParameters() before trying to get hold of the properties!
jassert (options.applicationName.isNotEmpty());

if (appName.isNotEmpty())
if (options.applicationName.isNotEmpty())
{
PropertiesFile::Options o (options);

if (userProps == nullptr)
userProps = PropertiesFile::createDefaultAppPropertiesFile (appName, fileSuffix, folderName,
false, msBeforeSaving, options, processLock);
{
o.commonToAllUsers = false;
userProps = new PropertiesFile (o);
}

if (commonProps == nullptr)
commonProps = PropertiesFile::createDefaultAppPropertiesFile (appName, fileSuffix, folderName,
true, msBeforeSaving, options, processLock);
{
o.commonToAllUsers = true;
commonProps = new PropertiesFile (o);
}

userProps->setFallbackPropertySet (commonProps);
}
@@ -20036,27 +20037,97 @@ namespace PropertyFileConstants
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_)
PropertiesFile::Options::Options()
: commonToAllUsers (false),
ignoreCaseOfKeyNames (false),
millisecondsBeforeSaving (3000),
storageFormat (PropertiesFile::storeAsXML),
processLock (nullptr)
{
// 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);
}

File PropertiesFile::Options::getDefaultFile() const
{
// mustn't have illegal characters in this name..
jassert (applicationName == File::createLegalFileName (applicationName));

#if JUCE_MAC || JUCE_IOS
File dir (commonToAllUsers ? "/Library/"
: "~/Library/");

if (osxLibrarySubFolder != "Preferences" && osxLibrarySubFolder != "Application Support")
{
/* The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple
have changed their advice, and now stipulate that settings should go in "Library/Application Support".

Because older apps would be broken by a silent change in this class's behaviour, you must now
explicitly set the osxLibrarySubFolder value to indicate which path you want to use.

In newer apps, you should always set this to "Application Support".

If your app needs to load settings files that were created by older versions of juce and
you want to maintain backwards-compatibility, then you can set this to "Preferences".
But.. for better Apple-compliance, the recommended approach would be to write some code that
finds your old settings files in ~/Library/Preferences, moves them to ~/Library/Application Support,
and then uses the new path.
*/
jassertfalse;

dir = dir.getChildFile ("Application Support");
}
else
{
dir = dir.getChildFile (osxLibrarySubFolder);
}

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 (const File& file_, const Options& options_)
: PropertySet (options.ignoreCaseOfKeyNames),
file (file_), options (options_),
loadedOk (false), needsWriting (false)
{
initialise();
}

PropertiesFile::PropertiesFile (const Options& options_)
: PropertySet (options.ignoreCaseOfKeyNames),
file (options_.getDefaultFile()), options (options_),
loadedOk (false), needsWriting (false)
{
initialise();
}

void PropertiesFile::initialise()
{
// You need to correctly specify just one storage format for the file
ProcessScopedLock pl (createProcessLock());

if (pl != nullptr && ! pl->isLocked())
return; // locking failure..

ScopedPointer<InputStream> fileStream (f.createInputStream());
ScopedPointer<InputStream> fileStream (file.createInputStream());

if (fileStream != nullptr)
{
@@ -20090,7 +20161,7 @@ PropertiesFile::PropertiesFile (const File& f, const int millisecondsBeforeSavin
// Not a binary props file - let's see if it's XML..
fileStream = nullptr;

XmlDocument parser (f);
XmlDocument parser (file);
ScopedPointer<XmlElement> doc (parser.getDocumentElement (true));

if (doc != nullptr && doc->hasTagName (PropertyFileConstants::fileTag))
@@ -20126,7 +20197,7 @@ PropertiesFile::PropertiesFile (const File& f, const int millisecondsBeforeSavin
}
else
{
loadedOk = ! f.exists();
loadedOk = ! file.exists();
}
}

@@ -20138,7 +20209,7 @@ PropertiesFile::~PropertiesFile()

InterProcessLock::ScopedLockType* PropertiesFile::createProcessLock() const
{
return processLock != nullptr ? new InterProcessLock::ScopedLockType (*processLock) : nullptr;
return options.processLock != nullptr ? new InterProcessLock::ScopedLockType (*options.processLock) : nullptr;
}

bool PropertiesFile::saveIfNeeded()
@@ -20170,7 +20241,7 @@ bool PropertiesFile::save()
|| ! file.getParentDirectory().createDirectory())
return false;

if ((options & storeAsXML) != 0)
if (options.storageFormat == storeAsXML)
{
XmlElement doc (PropertyFileConstants::fileTag);

@@ -20212,7 +20283,7 @@ bool PropertiesFile::save()

if (out != nullptr)
{
if ((options & storeAsCompressedBinary) != 0)
if (options.storageFormat == storeAsCompressedBinary)
{
out->writeInt (PropertyFileConstants::magicNumberCompressed);
out->flush();
@@ -20222,7 +20293,7 @@ bool PropertiesFile::save()
else
{
// have you set up the storage option flags correctly?
jassert ((options & storeAsBinary) != 0);
jassert (options.storageFormat == storeAsBinary);

out->writeInt (PropertyFileConstants::magicNumber);
}
@@ -20261,66 +20332,12 @@ void PropertiesFile::propertyChanged()

needsWriting = true;

if (timerInterval > 0)
startTimer (timerInterval);
else if (timerInterval == 0)
if (options.millisecondsBeforeSaving > 0)
startTimer (options.millisecondsBeforeSaving);
else if (options.millisecondsBeforeSaving == 0)
saveIfNeeded();
}

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 nullptr;

return new PropertiesFile (file, millisecondsBeforeSaving, propertiesFileOptions,processLock_);
}

END_JUCE_NAMESPACE

/*** End of inlined file: juce_PropertiesFile.cpp ***/
@@ -253709,7 +253726,7 @@ void DirectShowComponent::play()
void DirectShowComponent::stop()
{
if (videoLoaded)
context->stop();
context->pause();
}

bool DirectShowComponent::isPlaying() const
@@ -262523,12 +262540,14 @@ public:
Pimpl (const String& name, const int timeOutMillisecs)
: handle (0), refCount (1)
{
#if JUCE_MAC
// (don't use getSpecialLocation() to avoid the temp folder being different for each app)
const File temp (File ("~/Library/Caches/Juce").getChildFile (name));
#else
const File temp (File::getSpecialLocation (File::tempDirectory).getChildFile (name));
#endif
// Note that we can't get the normal temp folder here, as it might be different for each app.
File tempFolder ("/var/tmp");

if (! tempFolder.isDirectory())
tempFolder = "/tmp";

const File temp (tempFolder.getChildFile (name));

temp.create();
handle = open (temp.getFullPathName().toUTF8(), O_RDWR);

@@ -272561,12 +272580,14 @@ public:
Pimpl (const String& name, const int timeOutMillisecs)
: handle (0), refCount (1)
{
#if JUCE_MAC
// (don't use getSpecialLocation() to avoid the temp folder being different for each app)
const File temp (File ("~/Library/Caches/Juce").getChildFile (name));
#else
const File temp (File::getSpecialLocation (File::tempDirectory).getChildFile (name));
#endif
// Note that we can't get the normal temp folder here, as it might be different for each app.
File tempFolder ("/var/tmp");

if (! tempFolder.isDirectory())
tempFolder = "/tmp";

const File temp (tempFolder.getChildFile (name));

temp.create();
handle = open (temp.getFullPathName().toUTF8(), O_RDWR);

@@ -289448,12 +289469,14 @@ public:
Pimpl (const String& name, const int timeOutMillisecs)
: handle (0), refCount (1)
{
#if JUCE_MAC
// (don't use getSpecialLocation() to avoid the temp folder being different for each app)
const File temp (File ("~/Library/Caches/Juce").getChildFile (name));
#else
const File temp (File::getSpecialLocation (File::tempDirectory).getChildFile (name));
#endif
// Note that we can't get the normal temp folder here, as it might be different for each app.
File tempFolder ("/var/tmp");

if (! tempFolder.isDirectory())
tempFolder = "/tmp";

const File temp (tempFolder.getChildFile (name));

temp.create();
handle = open (temp.getFullPathName().toUTF8(), O_RDWR);



+ 123
- 90
juce_amalgamated.h View File

@@ -73,7 +73,7 @@ namespace JuceDummyNamespace {}
*/
#define JUCE_MAJOR_VERSION 1
#define JUCE_MINOR_VERSION 53
#define JUCE_BUILDNUMBER 100
#define JUCE_BUILDNUMBER 101

/** Current Juce version number.

@@ -13871,6 +13871,11 @@ public:
*/
void setValue (const String& keyName, const XmlElement* xml);

/** This copies all the values from a source PropertySet to this one.
This won't remove any existing settings, it just adds any that it finds in the source set.
*/
void addAllPropertiesFrom (const PropertySet& source);

/** Deletes a property.

@param keyName the name of the property to delete. (This mustn't be an empty string)
@@ -34973,43 +34978,124 @@ class JUCE_API PropertiesFile : public PropertySet,
{
public:

enum FileFormatOptions
enum StorageFormat
{
ignoreCaseOfKeyNames = 1,
storeAsBinary = 2,
storeAsCompressedBinary = 4,
storeAsXML = 8
storeAsBinary,
storeAsCompressedBinary,
storeAsXML
};

/**
Creates a PropertiesFile object.

@param file the file to use
@param millisecondsBeforeSaving if this is zero or greater, then after a value
is changed, the object will wait for this amount
of time and then save the file. If zero, the file
will be written to disk immediately on being changed
(which might be slow, as it'll re-write synchronously
each time a value-change method is called). If it is
less than zero, the file won't be saved until
save() or saveIfNeeded() are explicitly called.
@param optionFlags a combination of the flags in the FileFormatOptions
enum, which specify the type of file to save, and other
options.
@param processLock an optional InterprocessLock object that will be used to
prevent multiple threads or processes from writing to the file
at the same time. The PropertiesFile will keep a pointer to
this object but will not take ownership of it - the caller is
responsible for making sure that the lock doesn't get deleted
before the PropertiesFile has been deleted.
struct Options
{
/** Creates an empty Options structure.
You'll need to fill-in the data memebers appropriately before using this structure.
*/
Options();

/** The name of your application - this is used to help generate the path and filename
at which the properties file will be stored. */
String applicationName;

/** The suffix to use for your properties file.
It doesn't really matter what this is - you may want to use ".settings" or
".properties" or something.
*/
String filenameSuffix;

/** The name of a subfolder in which you'd like your properties file to live.
See the getDefaultFile() method for more details about how this is used.
*/
String folderName;

/** If you're using properties files on a Mac, you must set this value - failure to
do so will cause a runtime assertion.

The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple
have changed their advice, and now stipulate that settings should go in "Library/Application Support".

Because older apps would be broken by a silent change in this class's behaviour, you must now
explicitly set the osxLibrarySubFolder value to indicate which path you want to use.

In newer apps, you should always set this to "Application Support".

If your app needs to load settings files that were created by older versions of juce and
you want to maintain backwards-compatibility, then you can set this to "Preferences".
But.. for better Apple-compliance, the recommended approach would be to write some code that
finds your old settings files in ~/Library/Preferences, moves them to ~/Library/Application Support,
and then uses the new path.
*/
String osxLibrarySubFolder;

/** If true, the file will be created in a location that's shared between users.
The default constructor initialises this value to false.
*/
bool commonToAllUsers;

/** If true, this means that property names are matched in a case-insensitive manner.
See the PropertySet constructor for more info.
The default constructor initialises this value to false.
*/
bool ignoreCaseOfKeyNames;

/** If this is zero or greater, then after a value is changed, the object will wait
for this amount of time and then save the file. If this zero, the file will be
written to disk immediately on being changed (which might be slow, as it'll re-write
synchronously each time a value-change method is called). If it is less than zero,
the file won't be saved until save() or saveIfNeeded() are explicitly called.
The default constructor sets this to a reasonable value of a few seconds, so you
only need to change it if you need a special case.
*/
int millisecondsBeforeSaving;

/** Specifies whether the file should be written as XML, binary, etc.
The default constructor sets this to storeAsXML, so you only need to set it explicitly
if you want to use a different format.
*/
StorageFormat storageFormat;

/** An optional InterprocessLock object that will be used to prevent multiple threads or
processes from writing to the file at the same time. The PropertiesFile will keep a
pointer to this object but will not take ownership of it - the caller is responsible for
making sure that the lock doesn't get deleted before the PropertiesFile has been deleted.
The default constructor initialises this value to nullptr, so you don't need to touch it
unless you want to use a lock.
*/
InterProcessLock* processLock;

/** This can be called to suggest a file that should be used, based on the values
in this structure.

So on a Mac, this will return a file called:
~/Library/[osxLibrarySubFolder]/[folderName]/[applicationName].[filenameSuffix]

On Windows it'll return something like:
C:\\Documents and Settings\\username\\Application Data\\[folderName]\\[applicationName].[filenameSuffix]

On Linux it'll return
~/.[folderName]/[applicationName].[filenameSuffix]

If the folderName variable is empty, it'll use the app name for this (or omit the
folder name on the Mac).

The paths will also vary depending on whether commonToAllUsers is true.
*/
File getDefaultFile() const;
};

/** Creates a PropertiesFile object.
The file used will be chosen by calling PropertiesFile::Options::getDefaultFile()
for the options provided. To set the file explicitly, use the other constructor.
*/
explicit PropertiesFile (const Options& options);

/** Creates a PropertiesFile object.
Unlike the other constructor, this one allows you to explicitly set the file that you
want to be used, rather than using the default one.
*/
PropertiesFile (const File& file,
int millisecondsBeforeSaving,
int optionFlags,
InterProcessLock* processLock = nullptr);
const Options& options);

/** Destructor.

When deleted, the file will first call saveIfNeeded() to flush any changes to disk.
*/
~PropertiesFile();
@@ -35054,64 +35140,21 @@ public:
/** Returns the file that's being used. */
File getFile() const { return file; }

/** Handy utility to create a properties file in whatever the standard OS-specific
location is for these things.

This uses getDefaultAppSettingsFile() to decide what file to create, then
creates a PropertiesFile object with the specified properties. See
getDefaultAppSettingsFile() and the class's constructor for descriptions of
what the parameters do.

@see getDefaultAppSettingsFile
*/
static PropertiesFile* createDefaultAppPropertiesFile (const String& applicationName,
const String& fileNameSuffix,
const String& folderName,
bool commonToAllUsers,
int millisecondsBeforeSaving,
int propertiesFileOptions,
InterProcessLock* processLock = nullptr);

/** Handy utility to choose a file in the standard OS-dependent location for application
settings files.

So on a Mac, this will return a file called:
~/Library/Preferences/[folderName]/[applicationName].[fileNameSuffix]

On Windows it'll return something like:
C:\\Documents and Settings\\username\\Application Data\\[folderName]\\[applicationName].[fileNameSuffix]

On Linux it'll return
~/.[folderName]/[applicationName].[fileNameSuffix]

If you pass an empty string as the folder name, it'll use the app name for this (or
omit the folder name on the Mac).

If commonToAllUsers is true, then this will return the same file for all users of the
computer, regardless of the current user. If it is false, the file will be specific to
only the current user. Use this to choose whether you're saving settings that are common
or user-specific.
*/
static File getDefaultAppSettingsFile (const String& applicationName,
const String& fileNameSuffix,
const String& folderName,
bool commonToAllUsers);

protected:
/** @internal */
virtual void propertyChanged();

private:

File file;
int timerInterval;
const int options;
Options options;
bool loadedOk, needsWriting;

InterProcessLock* processLock;
typedef const ScopedPointer<InterProcessLock::ScopedLockType> ProcessScopedLock;
InterProcessLock::ScopedLockType* createProcessLock() const;

void timerCallback();
void initialise();

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertiesFile);
};
@@ -35158,16 +35201,9 @@ public:
juce_DeclareSingleton (ApplicationProperties, false)

/** Gives the object the information it needs to create the appropriate properties files.

See the comments for PropertiesFile::createDefaultAppPropertiesFile() for more
info about how these parameters are used.
See the PropertiesFile::Options class for details about what options you need to set.
*/
void setStorageParameters (const String& applicationName,
const String& fileNameSuffix,
const String& folderName,
int millisecondsBeforeSaving,
int propertiesFileOptions,
InterProcessLock* processLock = nullptr);
void setStorageParameters (const PropertiesFile::Options& options);

/** Tests whether the files can be successfully written to, and can show
an error message if not.
@@ -35228,12 +35264,9 @@ public:

private:

PropertiesFile::Options options;
ScopedPointer <PropertiesFile> userProps, commonProps;

String appName, fileSuffix, folderName;
int msBeforeSaving, options;
int commonSettingsAreReadOnly;
InterProcessLock* processLock;

void openFiles();



+ 18
- 26
src/application/juce_ApplicationProperties.cpp View File

@@ -38,10 +38,7 @@ juce_ImplementSingleton (ApplicationProperties)
//==============================================================================
ApplicationProperties::ApplicationProperties()
: msBeforeSaving (3000),
options (PropertiesFile::storeAsBinary),
commonSettingsAreReadOnly (0),
processLock (nullptr)
: commonSettingsAreReadOnly (0)
{
}
@@ -52,19 +49,9 @@ ApplicationProperties::~ApplicationProperties()
}
//==============================================================================
void ApplicationProperties::setStorageParameters (const String& applicationName,
const String& fileNameSuffix,
const String& folderName_,
const int millisecondsBeforeSaving,
const int propertiesFileOptions,
InterProcessLock* processLock_)
void ApplicationProperties::setStorageParameters (const PropertiesFile::Options& newOptions)
{
appName = applicationName;
fileSuffix = fileNameSuffix;
folderName = folderName_;
msBeforeSaving = millisecondsBeforeSaving;
options = propertiesFileOptions;
processLock = processLock_;
options = newOptions;
}
bool ApplicationProperties::testWriteAccess (const bool testUserSettings,
@@ -87,9 +74,9 @@ bool ApplicationProperties::testWriteAccess (const bool testUserSettings,
filenames << '\n' << commonProps->getFile().getFullPathName();
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
appName + TRANS(" - Unable to save settings"),
options.applicationName + TRANS(" - Unable to save settings"),
TRANS("An error occurred when trying to save the application's settings file...\n\nIn order to save and restore its settings, ")
+ appName + TRANS(" needs to be able to write to the following files:\n")
+ options.applicationName + TRANS(" needs to be able to write to the following files:\n")
+ filenames
+ TRANS("\n\nMake sure that these files aren't read-only, and that the disk isn't full."));
}
@@ -103,19 +90,24 @@ bool ApplicationProperties::testWriteAccess (const bool testUserSettings,
//==============================================================================
void ApplicationProperties::openFiles()
{
// You need to call setStorageParameters() before trying to get hold of the
// properties!
jassert (appName.isNotEmpty());
// You need to call setStorageParameters() before trying to get hold of the properties!
jassert (options.applicationName.isNotEmpty());
if (appName.isNotEmpty())
if (options.applicationName.isNotEmpty())
{
PropertiesFile::Options o (options);
if (userProps == nullptr)
userProps = PropertiesFile::createDefaultAppPropertiesFile (appName, fileSuffix, folderName,
false, msBeforeSaving, options, processLock);
{
o.commonToAllUsers = false;
userProps = new PropertiesFile (o);
}
if (commonProps == nullptr)
commonProps = PropertiesFile::createDefaultAppPropertiesFile (appName, fileSuffix, folderName,
true, msBeforeSaving, options, processLock);
{
o.commonToAllUsers = true;
commonProps = new PropertiesFile (o);
}
userProps->setFallbackPropertySet (commonProps);
}


+ 3
- 13
src/application/juce_ApplicationProperties.h View File

@@ -73,16 +73,9 @@ public:
//==============================================================================
/** Gives the object the information it needs to create the appropriate properties files.
See the comments for PropertiesFile::createDefaultAppPropertiesFile() for more
info about how these parameters are used.
See the PropertiesFile::Options class for details about what options you need to set.
*/
void setStorageParameters (const String& applicationName,
const String& fileNameSuffix,
const String& folderName,
int millisecondsBeforeSaving,
int propertiesFileOptions,
InterProcessLock* processLock = nullptr);
void setStorageParameters (const PropertiesFile::Options& options);
/** Tests whether the files can be successfully written to, and can show
an error message if not.
@@ -146,12 +139,9 @@ public:
private:
//==============================================================================
PropertiesFile::Options options;
ScopedPointer <PropertiesFile> userProps, commonProps;
String appName, fileSuffix, folderName;
int msBeforeSaving, options;
int commonSettingsAreReadOnly;
InterProcessLock* processLock;
void openFiles();


+ 9
- 0
src/containers/juce_PropertySet.cpp View File

@@ -175,6 +175,15 @@ bool PropertySet::containsKey (const String& keyName) const noexcept
return properties.getAllKeys().contains (keyName, ignoreCaseOfKeys);
}
void PropertySet::addAllPropertiesFrom (const PropertySet& source)
{
const ScopedLock sl (source.getLock());
for (int i = 0; i < source.properties.size(); ++i)
setValue (source.properties.getAllKeys() [i],
source.properties.getAllValues() [i]);
}
void PropertySet::setFallbackPropertySet (PropertySet* fallbackProperties_) noexcept
{
const ScopedLock sl (lock);


+ 5
- 0
src/containers/juce_PropertySet.h View File

@@ -145,6 +145,11 @@ public:
*/
void setValue (const String& keyName, const XmlElement* xml);
/** This copies all the values from a source PropertySet to this one.
This won't remove any existing settings, it just adds any that it finds in the source set.
*/
void addAllPropertiesFrom (const PropertySet& source);
//==============================================================================
/** Deletes a property.


+ 1
- 1
src/core/juce_StandardHeader.h View File

@@ -33,7 +33,7 @@
*/
#define JUCE_MAJOR_VERSION 1
#define JUCE_MINOR_VERSION 53
#define JUCE_BUILDNUMBER 100
#define JUCE_BUILDNUMBER 101
/** Current Juce version number.


+ 8
- 6
src/native/common/juce_posix_SharedCode.h View File

@@ -673,12 +673,14 @@ public:
Pimpl (const String& name, const int timeOutMillisecs)
: handle (0), refCount (1)
{
#if JUCE_MAC
// (don't use getSpecialLocation() to avoid the temp folder being different for each app)
const File temp (File ("~/Library/Caches/Juce").getChildFile (name));
#else
const File temp (File::getSpecialLocation (File::tempDirectory).getChildFile (name));
#endif
// Note that we can't get the normal temp folder here, as it might be different for each app.
File tempFolder ("/var/tmp");
if (! tempFolder.isDirectory())
tempFolder = "/tmp";
const File temp (tempFolder.getChildFile (name));
temp.create();
handle = open (temp.getFullPathName().toUTF8(), O_RDWR);


+ 1
- 1
src/native/windows/juce_win32_DirectShowComponent.cpp View File

@@ -896,7 +896,7 @@ void DirectShowComponent::play()
void DirectShowComponent::stop()
{
if (videoLoaded)
context->stop();
context->pause();
}
bool DirectShowComponent::isPlaying() const


+ 95
- 78
src/utilities/juce_PropertiesFile.cpp View File

@@ -54,27 +54,99 @@ namespace PropertyFileConstants
}
//==============================================================================
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_)
PropertiesFile::Options::Options()
: commonToAllUsers (false),
ignoreCaseOfKeyNames (false),
millisecondsBeforeSaving (3000),
storageFormat (PropertiesFile::storeAsXML),
processLock (nullptr)
{
// 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);
}
File PropertiesFile::Options::getDefaultFile() const
{
// mustn't have illegal characters in this name..
jassert (applicationName == File::createLegalFileName (applicationName));
#if JUCE_MAC || JUCE_IOS
File dir (commonToAllUsers ? "/Library/"
: "~/Library/");
if (osxLibrarySubFolder != "Preferences" && osxLibrarySubFolder != "Application Support")
{
/* The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple
have changed their advice, and now stipulate that settings should go in "Library/Application Support".
Because older apps would be broken by a silent change in this class's behaviour, you must now
explicitly set the osxLibrarySubFolder value to indicate which path you want to use.
In newer apps, you should always set this to "Application Support".
If your app needs to load settings files that were created by older versions of juce and
you want to maintain backwards-compatibility, then you can set this to "Preferences".
But.. for better Apple-compliance, the recommended approach would be to write some code that
finds your old settings files in ~/Library/Preferences, moves them to ~/Library/Application Support,
and then uses the new path.
*/
jassertfalse;
dir = dir.getChildFile ("Application Support");
}
else
{
dir = dir.getChildFile (osxLibrarySubFolder);
}
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 (const File& file_, const Options& options_)
: PropertySet (options.ignoreCaseOfKeyNames),
file (file_), options (options_),
loadedOk (false), needsWriting (false)
{
initialise();
}
PropertiesFile::PropertiesFile (const Options& options_)
: PropertySet (options.ignoreCaseOfKeyNames),
file (options_.getDefaultFile()), options (options_),
loadedOk (false), needsWriting (false)
{
initialise();
}
void PropertiesFile::initialise()
{
// You need to correctly specify just one storage format for the file
ProcessScopedLock pl (createProcessLock());
if (pl != nullptr && ! pl->isLocked())
return; // locking failure..
ScopedPointer<InputStream> fileStream (f.createInputStream());
ScopedPointer<InputStream> fileStream (file.createInputStream());
if (fileStream != nullptr)
{
@@ -108,7 +180,7 @@ PropertiesFile::PropertiesFile (const File& f, const int millisecondsBeforeSavin
// Not a binary props file - let's see if it's XML..
fileStream = nullptr;
XmlDocument parser (f);
XmlDocument parser (file);
ScopedPointer<XmlElement> doc (parser.getDocumentElement (true));
if (doc != nullptr && doc->hasTagName (PropertyFileConstants::fileTag))
@@ -144,7 +216,7 @@ PropertiesFile::PropertiesFile (const File& f, const int millisecondsBeforeSavin
}
else
{
loadedOk = ! f.exists();
loadedOk = ! file.exists();
}
}
@@ -156,7 +228,7 @@ PropertiesFile::~PropertiesFile()
InterProcessLock::ScopedLockType* PropertiesFile::createProcessLock() const
{
return processLock != nullptr ? new InterProcessLock::ScopedLockType (*processLock) : nullptr;
return options.processLock != nullptr ? new InterProcessLock::ScopedLockType (*options.processLock) : nullptr;
}
bool PropertiesFile::saveIfNeeded()
@@ -188,7 +260,7 @@ bool PropertiesFile::save()
|| ! file.getParentDirectory().createDirectory())
return false;
if ((options & storeAsXML) != 0)
if (options.storageFormat == storeAsXML)
{
XmlElement doc (PropertyFileConstants::fileTag);
@@ -230,7 +302,7 @@ bool PropertiesFile::save()
if (out != nullptr)
{
if ((options & storeAsCompressedBinary) != 0)
if (options.storageFormat == storeAsCompressedBinary)
{
out->writeInt (PropertyFileConstants::magicNumberCompressed);
out->flush();
@@ -240,7 +312,7 @@ bool PropertiesFile::save()
else
{
// have you set up the storage option flags correctly?
jassert ((options & storeAsBinary) != 0);
jassert (options.storageFormat == storeAsBinary);
out->writeInt (PropertyFileConstants::magicNumber);
}
@@ -279,66 +351,11 @@ void PropertiesFile::propertyChanged()
needsWriting = true;
if (timerInterval > 0)
startTimer (timerInterval);
else if (timerInterval == 0)
if (options.millisecondsBeforeSaving > 0)
startTimer (options.millisecondsBeforeSaving);
else if (options.millisecondsBeforeSaving == 0)
saveIfNeeded();
}
//==============================================================================
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 nullptr;
return new PropertiesFile (file, millisecondsBeforeSaving, propertiesFileOptions,processLock_);
}
END_JUCE_NAMESPACE

+ 115
- 76
src/utilities/juce_PropertiesFile.h View File

@@ -53,44 +53,126 @@ class JUCE_API PropertiesFile : public PropertySet,
{
public:
//==============================================================================
enum FileFormatOptions
enum StorageFormat
{
ignoreCaseOfKeyNames = 1,
storeAsBinary = 2,
storeAsCompressedBinary = 4,
storeAsXML = 8
storeAsBinary,
storeAsCompressedBinary,
storeAsXML
};
//==============================================================================
/**
Creates a PropertiesFile object.
@param file the file to use
@param millisecondsBeforeSaving if this is zero or greater, then after a value
is changed, the object will wait for this amount
of time and then save the file. If zero, the file
will be written to disk immediately on being changed
(which might be slow, as it'll re-write synchronously
each time a value-change method is called). If it is
less than zero, the file won't be saved until
save() or saveIfNeeded() are explicitly called.
@param optionFlags a combination of the flags in the FileFormatOptions
enum, which specify the type of file to save, and other
options.
@param processLock an optional InterprocessLock object that will be used to
prevent multiple threads or processes from writing to the file
at the same time. The PropertiesFile will keep a pointer to
this object but will not take ownership of it - the caller is
responsible for making sure that the lock doesn't get deleted
before the PropertiesFile has been deleted.
struct Options
{
/** Creates an empty Options structure.
You'll need to fill-in the data memebers appropriately before using this structure.
*/
Options();
/** The name of your application - this is used to help generate the path and filename
at which the properties file will be stored. */
String applicationName;
/** The suffix to use for your properties file.
It doesn't really matter what this is - you may want to use ".settings" or
".properties" or something.
*/
String filenameSuffix;
/** The name of a subfolder in which you'd like your properties file to live.
See the getDefaultFile() method for more details about how this is used.
*/
String folderName;
/** If you're using properties files on a Mac, you must set this value - failure to
do so will cause a runtime assertion.
The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple
have changed their advice, and now stipulate that settings should go in "Library/Application Support".
Because older apps would be broken by a silent change in this class's behaviour, you must now
explicitly set the osxLibrarySubFolder value to indicate which path you want to use.
In newer apps, you should always set this to "Application Support".
If your app needs to load settings files that were created by older versions of juce and
you want to maintain backwards-compatibility, then you can set this to "Preferences".
But.. for better Apple-compliance, the recommended approach would be to write some code that
finds your old settings files in ~/Library/Preferences, moves them to ~/Library/Application Support,
and then uses the new path.
*/
String osxLibrarySubFolder;
/** If true, the file will be created in a location that's shared between users.
The default constructor initialises this value to false.
*/
bool commonToAllUsers;
/** If true, this means that property names are matched in a case-insensitive manner.
See the PropertySet constructor for more info.
The default constructor initialises this value to false.
*/
bool ignoreCaseOfKeyNames;
/** If this is zero or greater, then after a value is changed, the object will wait
for this amount of time and then save the file. If this zero, the file will be
written to disk immediately on being changed (which might be slow, as it'll re-write
synchronously each time a value-change method is called). If it is less than zero,
the file won't be saved until save() or saveIfNeeded() are explicitly called.
The default constructor sets this to a reasonable value of a few seconds, so you
only need to change it if you need a special case.
*/
int millisecondsBeforeSaving;
/** Specifies whether the file should be written as XML, binary, etc.
The default constructor sets this to storeAsXML, so you only need to set it explicitly
if you want to use a different format.
*/
StorageFormat storageFormat;
/** An optional InterprocessLock object that will be used to prevent multiple threads or
processes from writing to the file at the same time. The PropertiesFile will keep a
pointer to this object but will not take ownership of it - the caller is responsible for
making sure that the lock doesn't get deleted before the PropertiesFile has been deleted.
The default constructor initialises this value to nullptr, so you don't need to touch it
unless you want to use a lock.
*/
InterProcessLock* processLock;
/** This can be called to suggest a file that should be used, based on the values
in this structure.
So on a Mac, this will return a file called:
~/Library/[osxLibrarySubFolder]/[folderName]/[applicationName].[filenameSuffix]
On Windows it'll return something like:
C:\\Documents and Settings\\username\\Application Data\\[folderName]\\[applicationName].[filenameSuffix]
On Linux it'll return
~/.[folderName]/[applicationName].[filenameSuffix]
If the folderName variable is empty, it'll use the app name for this (or omit the
folder name on the Mac).
The paths will also vary depending on whether commonToAllUsers is true.
*/
File getDefaultFile() const;
};
//==============================================================================
/** Creates a PropertiesFile object.
The file used will be chosen by calling PropertiesFile::Options::getDefaultFile()
for the options provided. To set the file explicitly, use the other constructor.
*/
explicit PropertiesFile (const Options& options);
/** Creates a PropertiesFile object.
Unlike the other constructor, this one allows you to explicitly set the file that you
want to be used, rather than using the default one.
*/
PropertiesFile (const File& file,
int millisecondsBeforeSaving,
int optionFlags,
InterProcessLock* processLock = nullptr);
const Options& options);
/** Destructor.
When deleted, the file will first call saveIfNeeded() to flush any changes to disk.
*/
~PropertiesFile();
@@ -138,65 +220,22 @@ public:
/** Returns the file that's being used. */
File getFile() const { return file; }
//==============================================================================
/** Handy utility to create a properties file in whatever the standard OS-specific
location is for these things.
This uses getDefaultAppSettingsFile() to decide what file to create, then
creates a PropertiesFile object with the specified properties. See
getDefaultAppSettingsFile() and the class's constructor for descriptions of
what the parameters do.
@see getDefaultAppSettingsFile
*/
static PropertiesFile* createDefaultAppPropertiesFile (const String& applicationName,
const String& fileNameSuffix,
const String& folderName,
bool commonToAllUsers,
int millisecondsBeforeSaving,
int propertiesFileOptions,
InterProcessLock* processLock = nullptr);
/** Handy utility to choose a file in the standard OS-dependent location for application
settings files.
So on a Mac, this will return a file called:
~/Library/Preferences/[folderName]/[applicationName].[fileNameSuffix]
On Windows it'll return something like:
C:\\Documents and Settings\\username\\Application Data\\[folderName]\\[applicationName].[fileNameSuffix]
On Linux it'll return
~/.[folderName]/[applicationName].[fileNameSuffix]
If you pass an empty string as the folder name, it'll use the app name for this (or
omit the folder name on the Mac).
If commonToAllUsers is true, then this will return the same file for all users of the
computer, regardless of the current user. If it is false, the file will be specific to
only the current user. Use this to choose whether you're saving settings that are common
or user-specific.
*/
static File getDefaultAppSettingsFile (const String& applicationName,
const String& fileNameSuffix,
const String& folderName,
bool commonToAllUsers);
protected:
/** @internal */
virtual void propertyChanged();
private:
//==============================================================================
File file;
int timerInterval;
const int options;
Options options;
bool loadedOk, needsWriting;
InterProcessLock* processLock;
typedef const ScopedPointer<InterProcessLock::ScopedLockType> ProcessScopedLock;
InterProcessLock::ScopedLockType* createProcessLock() const;
void timerCallback();
void initialise();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertiesFile);
};


Loading…
Cancel
Save