@@ -244,7 +244,8 @@ | |||
JUCE_ALSA="disabled" JUCE_QUICKTIME="disabled" JUCE_OPENGL="disabled" | |||
JUCE_USE_FLAC="disabled" JUCE_USE_CDBURNER="disabled" JUCE_USE_CDREADER="disabled" | |||
JUCE_USE_CAMERA="disabled" JUCE_PLUGINHOST_VST="disabled" JUCE_PLUGINHOST_AU="disabled" | |||
JUCE_USE_OGGVORBIS="disabled" JUCE_USE_COREIMAGE_LOADER="disabled"/> | |||
JUCE_USE_OGGVORBIS="disabled" JUCE_USE_COREIMAGE_LOADER="disabled" | |||
JUCE_LOG_ASSERTIONS="enabled"/> | |||
<MODULES> | |||
<MODULE id="juce_core" showAllCode="1"/> | |||
<MODULE id="juce_events" showAllCode="1"/> | |||
@@ -38,7 +38,7 @@ | |||
#endif | |||
#ifndef JUCE_LOG_ASSERTIONS | |||
//#define JUCE_LOG_ASSERTIONS | |||
#define JUCE_LOG_ASSERTIONS 1 | |||
#endif | |||
#ifndef JUCE_CHECK_MEMORY_LEAKS | |||
@@ -44,6 +44,8 @@ public: | |||
//============================================================================== | |||
void initialise (const String& commandLine) | |||
{ | |||
initialiseLogger ("log_"); | |||
LookAndFeel::setDefaultLookAndFeel (&lookAndFeel); | |||
settings = new StoredSettings(); | |||
settings->initialise(); | |||
@@ -116,6 +118,10 @@ public: | |||
settings = nullptr; | |||
LookAndFeel::setDefaultLookAndFeel (nullptr); | |||
Logger::writeToLog ("Shutdown"); | |||
deleteLogger(); | |||
} | |||
//============================================================================== | |||
@@ -463,8 +469,57 @@ public: | |||
} | |||
//============================================================================== | |||
void initialiseLogger (const char* filePrefix) | |||
{ | |||
if (logger == nullptr) | |||
{ | |||
logger = FileLogger::createDateStampedLogger (getLogFolderName(), filePrefix, ".txt", | |||
getApplicationName() + " " + getApplicationVersion()); | |||
Logger::setCurrentLogger (logger); | |||
} | |||
} | |||
void deleteLogger() | |||
{ | |||
const int maxNumLogFilesToKeep = 50; | |||
Logger::setCurrentLogger (nullptr); | |||
if (logger != nullptr) | |||
{ | |||
Array<File> logFiles; | |||
logger->getLogFile().getParentDirectory().findChildFiles (logFiles, File::findFiles, false); | |||
if (logFiles.size() > maxNumLogFilesToKeep) | |||
{ | |||
struct FileWithTime | |||
{ | |||
FileWithTime (const File& f) : file (f), time (f.getLastModificationTime()) {} | |||
FileWithTime() {} | |||
bool operator< (const FileWithTime& other) const { return time < other.time; } | |||
bool operator== (const FileWithTime& other) const { return time == other.time; } | |||
File file; | |||
Time time; | |||
}; | |||
Array <FileWithTime> files; | |||
for (int i = 0; i < logFiles.size(); ++i) | |||
files.addUsingDefaultSort (logFiles.getReference(i)); | |||
for (int i = 0; i < files.size() - maxNumLogFilesToKeep; ++i) | |||
files.getReference(i).file.deleteFile(); | |||
} | |||
} | |||
logger = nullptr; | |||
} | |||
virtual void doExtraInitialisation() {} | |||
virtual void addExtraConfigItems (Project&, TreeViewItem&) {} | |||
virtual String getLogFolderName() const { return "com.juce.introjucer"; } | |||
virtual Component* createProjectContentComponent() const | |||
{ | |||
@@ -484,6 +539,8 @@ public: | |||
ScopedPointer<Component> appearanceEditorWindow, utf8Window; | |||
ScopedPointer<FileLogger> logger; | |||
private: | |||
class AsyncQuitRetrier : private Timer | |||
{ | |||
@@ -90,22 +90,13 @@ public: | |||
project.createRequiredModules (moduleList, modules); | |||
} | |||
if (errors.size() == 0) | |||
writeAppConfigFile (modules, appConfigUserContent); | |||
if (errors.size() == 0) writeAppConfigFile (modules, appConfigUserContent); | |||
if (errors.size() == 0) writeBinaryDataFiles(); | |||
if (errors.size() == 0) writeAppHeader (modules); | |||
if (errors.size() == 0) writeProjects (modules); | |||
if (errors.size() == 0) writeAppConfigFile (modules, appConfigUserContent); // (this is repeated in case the projects added anything to it) | |||
if (errors.size() == 0) | |||
writeBinaryDataFiles(); | |||
if (errors.size() == 0) | |||
writeAppHeader (modules); | |||
if (errors.size() == 0) | |||
writeProjects (modules); | |||
if (errors.size() == 0) | |||
writeAppConfigFile (modules, appConfigUserContent); // (this is repeated in case the projects added anything to it) | |||
if (generatedCodeFolder.exists() && errors.size() == 0) | |||
if (errors.size() == 0 && generatedCodeFolder.exists()) | |||
writeReadmeFile(); | |||
if (generatedCodeFolder.exists()) | |||
@@ -47,14 +47,15 @@ namespace Tags | |||
const char* Project::projectFileExtension = ".jucer"; | |||
//============================================================================== | |||
Project::Project (const File& file_) | |||
Project::Project (const File& f) | |||
: FileBasedDocument (projectFileExtension, | |||
String ("*") + projectFileExtension, | |||
"Choose a Jucer project to load", | |||
"Save Jucer project"), | |||
projectRoot (Tags::projectRoot) | |||
{ | |||
setFile (file_); | |||
Logger::writeToLog ("Loading project: " + f.getFullPathName()); | |||
setFile (f); | |||
removeDefunctExporters(); | |||
setMissingDefaultValues(); | |||
@@ -90,31 +90,34 @@ PropertiesFile& StoredSettings::getProjectProperties (const String& projectUID) | |||
return *p; | |||
} | |||
void StoredSettings::flush() | |||
void StoredSettings::updateGlobalProps() | |||
{ | |||
for (int i = propertyFiles.size(); --i >= 0;) | |||
PropertiesFile& props = getGlobalProperties(); | |||
{ | |||
PropertiesFile* const props = propertyFiles.getUnchecked(i); | |||
const ScopedPointer<XmlElement> xml (appearance.settings.createXml()); | |||
props.setValue ("editorColours", xml); | |||
} | |||
{ | |||
const ScopedPointer<XmlElement> xml (appearance.settings.createXml()); | |||
props->setValue ("editorColours", xml); | |||
} | |||
props.setValue ("recentFiles", recentFiles.toString()); | |||
props->setValue ("recentFiles", recentFiles.toString()); | |||
props.removeValue ("keyMappings"); | |||
props->removeValue ("keyMappings"); | |||
if (commandManager != nullptr) | |||
{ | |||
const ScopedPointer <XmlElement> keys (commandManager->getKeyMappings()->createXml (true)); | |||
if (commandManager != nullptr) | |||
{ | |||
ScopedPointer <XmlElement> keys (commandManager->getKeyMappings()->createXml (true)); | |||
if (keys != nullptr) | |||
props.setValue ("keyMappings", keys); | |||
} | |||
} | |||
if (keys != nullptr) | |||
props->setValue ("keyMappings", (XmlElement*) keys); | |||
} | |||
void StoredSettings::flush() | |||
{ | |||
updateGlobalProps(); | |||
props->saveIfNeeded(); | |||
} | |||
for (int i = propertyFiles.size(); --i >= 0;) | |||
propertyFiles.getUnchecked(i)->saveIfNeeded(); | |||
} | |||
void StoredSettings::reload() | |||
@@ -71,6 +71,7 @@ public: | |||
private: | |||
OwnedArray<PropertiesFile> propertyFiles; | |||
void updateGlobalProps(); | |||
void loadSwatchColours(); | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StoredSettings); | |||
@@ -130,21 +130,29 @@ String File::parseAbsolutePath (const String& p) | |||
// expand a name of type "~dave/abc" | |||
const String userName (path.substring (1).upToFirstOccurrenceOf ("/", false, false)); | |||
struct passwd* const pw = getpwnam (userName.toUTF8()); | |||
if (pw != nullptr) | |||
if (struct passwd* const pw = getpwnam (userName.toUTF8())) | |||
path = addTrailingSeparator (pw->pw_dir) + path.fromFirstOccurrenceOf ("/", false, false); | |||
} | |||
} | |||
else if (! path.startsWithChar (separator)) | |||
{ | |||
/* When you supply a raw string to the File object constructor, it must be an absolute path. | |||
If you're trying to parse a string that may be either a relative path or an absolute path, | |||
you MUST provide a context against which the partial path can be evaluated - you can do | |||
this by simply using File::getChildFile() instead of the File constructor. E.g. saying | |||
"File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute | |||
path if that's what was supplied, or would evaluate a partial path relative to the CWD. | |||
*/ | |||
jassert (path.startsWith ("./") || path.startsWith ("../")); // (assume that a path "./xyz" is deliberately intended to be relative to the CWD) | |||
#if JUCE_DEBUG || JUCE_LOG_ASSERTIONS | |||
if (! (path.startsWith ("./") || path.startsWith ("../"))) | |||
{ | |||
/* When you supply a raw string to the File object constructor, it must be an absolute path. | |||
If you're trying to parse a string that may be either a relative path or an absolute path, | |||
you MUST provide a context against which the partial path can be evaluated - you can do | |||
this by simply using File::getChildFile() instead of the File constructor. E.g. saying | |||
"File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute | |||
path if that's what was supplied, or would evaluate a partial path relative to the CWD. | |||
*/ | |||
jassertfalse; | |||
#if JUCE_LOG_ASSERTIONS | |||
Logger::writeToLog ("Illegal absolute path: " + path); | |||
#endif | |||
} | |||
#endif | |||
return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName(); | |||
} | |||
@@ -23,19 +23,16 @@ | |||
============================================================================== | |||
*/ | |||
FileLogger::FileLogger (const File& logFile_, | |||
FileLogger::FileLogger (const File& file, | |||
const String& welcomeMessage, | |||
const int maxInitialFileSizeBytes) | |||
: logFile (logFile_) | |||
const int64 maxInitialFileSizeBytes) | |||
: logFile (file) | |||
{ | |||
if (maxInitialFileSizeBytes >= 0) | |||
trimFileSize (maxInitialFileSizeBytes); | |||
if (! logFile_.exists()) | |||
{ | |||
// do this so that the parent directories get created.. | |||
logFile_.create(); | |||
} | |||
if (! file.exists()) | |||
file.create(); // (to create the parent directories) | |||
String welcome; | |||
welcome << newLine | |||
@@ -46,23 +43,18 @@ FileLogger::FileLogger (const File& logFile_, | |||
FileLogger::logMessage (welcome); | |||
} | |||
FileLogger::~FileLogger() | |||
{ | |||
} | |||
FileLogger::~FileLogger() {} | |||
//============================================================================== | |||
void FileLogger::logMessage (const String& message) | |||
{ | |||
DBG (message); | |||
const ScopedLock sl (logLock); | |||
DBG (message); | |||
FileOutputStream out (logFile, 256); | |||
out << message << newLine; | |||
} | |||
void FileLogger::trimFileSize (int maxFileSizeBytes) const | |||
void FileLogger::trimFileSize (int64 maxFileSizeBytes) const | |||
{ | |||
if (maxFileSizeBytes <= 0) | |||
{ | |||
@@ -74,59 +66,66 @@ void FileLogger::trimFileSize (int maxFileSizeBytes) const | |||
if (fileSize > maxFileSizeBytes) | |||
{ | |||
ScopedPointer <FileInputStream> in (logFile.createInputStream()); | |||
jassert (in != nullptr); | |||
TemporaryFile tempFile (logFile); | |||
if (in != nullptr) | |||
{ | |||
in->setPosition (fileSize - maxFileSizeBytes); | |||
String content; | |||
FileOutputStream out (tempFile.getFile()); | |||
FileInputStream in (logFile); | |||
{ | |||
MemoryBlock contentToSave; | |||
contentToSave.setSize ((size_t) maxFileSizeBytes + 4); | |||
contentToSave.fillWith (0); | |||
if (! (out.openedOk() && in.openedOk())) | |||
return; | |||
in->read (contentToSave.getData(), maxFileSizeBytes); | |||
in = nullptr; | |||
in.setPosition (fileSize - maxFileSizeBytes); | |||
content = contentToSave.toString(); | |||
for (;;) | |||
{ | |||
const char c = in.readByte(); | |||
if (c == 0) | |||
return; | |||
if (c == '\n' || c == '\r') | |||
{ | |||
out << c; | |||
break; | |||
} | |||
} | |||
int newStart = 0; | |||
while (newStart < fileSize | |||
&& content[newStart] != '\n' | |||
&& content[newStart] != '\r') | |||
++newStart; | |||
logFile.deleteFile(); | |||
logFile.appendText (content.substring (newStart), false, false); | |||
out.writeFromInputStream (in, -1); | |||
} | |||
tempFile.overwriteTargetFileWithTemporary(); | |||
} | |||
} | |||
} | |||
//============================================================================== | |||
FileLogger* FileLogger::createDefaultAppLogger (const String& logFileSubDirectoryName, | |||
const String& logFileName, | |||
const String& welcomeMessage, | |||
const int maxInitialFileSizeBytes) | |||
File FileLogger::getSystemLogFileFolder() | |||
{ | |||
#if JUCE_MAC | |||
File logFile ("~/Library/Logs"); | |||
logFile = logFile.getChildFile (logFileSubDirectoryName) | |||
.getChildFile (logFileName); | |||
return File ("~/Library/Logs"); | |||
#else | |||
File logFile (File::getSpecialLocation (File::userApplicationDataDirectory)); | |||
if (logFile.isDirectory()) | |||
{ | |||
logFile = logFile.getChildFile (logFileSubDirectoryName) | |||
.getChildFile (logFileName); | |||
} | |||
return File::getSpecialLocation (File::userApplicationDataDirectory); | |||
#endif | |||
} | |||
return new FileLogger (logFile, welcomeMessage, maxInitialFileSizeBytes); | |||
FileLogger* FileLogger::createDefaultAppLogger (const String& logFileSubDirectoryName, | |||
const String& logFileName, | |||
const String& welcomeMessage, | |||
const int64 maxInitialFileSizeBytes) | |||
{ | |||
return new FileLogger (getSystemLogFileFolder().getChildFile (logFileSubDirectoryName) | |||
.getChildFile (logFileName), | |||
welcomeMessage, maxInitialFileSizeBytes); | |||
} | |||
FileLogger* FileLogger::createDateStampedLogger (const String& logFileSubDirectoryName, | |||
const String& logFileNameRoot, | |||
const String& logFileNameSuffix, | |||
const String& welcomeMessage) | |||
{ | |||
return new FileLogger (getSystemLogFileFolder().getChildFile (logFileSubDirectoryName) | |||
.getChildFile (logFileNameRoot + Time::getCurrentTime().formatted ("%Y-%m-%d_%H-%M-%S")) | |||
.withFileExtension (logFileNameSuffix) | |||
.getNonexistentSibling(), | |||
welcomeMessage, 0); | |||
} |
@@ -59,47 +59,75 @@ public: | |||
*/ | |||
FileLogger (const File& fileToWriteTo, | |||
const String& welcomeMessage, | |||
const int maxInitialFileSizeBytes = 128 * 1024); | |||
const int64 maxInitialFileSizeBytes = 128 * 1024); | |||
/** Destructor. */ | |||
~FileLogger(); | |||
//============================================================================== | |||
void logMessage (const String& message); | |||
File getLogFile() const { return logFile; } | |||
/** Returns the file that this logger is writing to. */ | |||
const File& getLogFile() const noexcept { return logFile; } | |||
//============================================================================== | |||
/** Helper function to create a log file in the correct place for this platform. | |||
On Windows this will return a logger with a path such as: | |||
c:\\Documents and Settings\\username\\Application Data\\[logFileSubDirectoryName]\\[logFileName] | |||
On the Mac it'll create something like: | |||
~/Library/Logs/[logFileName] | |||
The method might return 0 if the file can't be created for some reason. | |||
The method might return nullptr if the file can't be created for some reason. | |||
@param logFileSubDirectoryName if a subdirectory is needed, this is what it will be called - | |||
it's best to use the something like the name of your application here. | |||
@param logFileName the name of the file to create, e.g. "MyAppLog.txt". Don't just | |||
call it "log.txt" because if it goes in a directory with logs | |||
from other applications (as it will do on the Mac) then no-one | |||
will know which one is yours! | |||
@param logFileSubDirectoryName the name of the subdirectory to create inside the logs folder (as | |||
returned by getSystemLogFileFolder). It's best to use something | |||
like the name of your application here. | |||
@param logFileName the name of the file to create, e.g. "MyAppLog.txt". | |||
@param welcomeMessage a message that will be written to the log when it's opened. | |||
@param maxInitialFileSizeBytes (see the FileLogger constructor for more info on this) | |||
*/ | |||
static FileLogger* createDefaultAppLogger (const String& logFileSubDirectoryName, | |||
const String& logFileName, | |||
const String& welcomeMessage, | |||
const int maxInitialFileSizeBytes = 128 * 1024); | |||
const int64 maxInitialFileSizeBytes = 128 * 1024); | |||
/** Helper function to create a log file in the correct place for this platform. | |||
The filename used is based on the root and suffix strings provided, along with a | |||
time and date string, meaning that a new, empty log file will be always be created | |||
rather than appending to an exising one. | |||
The method might return nullptr if the file can't be created for some reason. | |||
@param logFileSubDirectoryName the name of the subdirectory to create inside the logs folder (as | |||
returned by getSystemLogFileFolder). It's best to use something | |||
like the name of your application here. | |||
@param logFileNameRoot the start of the filename to use, e.g. "MyAppLog_". This will have | |||
a timestamp and the logFileNameSuffix appended to it | |||
@param logFileNameSuffix the file suffix to use, e.g. ".txt" | |||
@param welcomeMessage a message that will be written to the log when it's opened. | |||
*/ | |||
static FileLogger* createDateStampedLogger (const String& logFileSubDirectoryName, | |||
const String& logFileNameRoot, | |||
const String& logFileNameSuffix, | |||
const String& welcomeMessage); | |||
//============================================================================== | |||
/** Returns an OS-specific folder where log-files should be stored. | |||
On Windows this will return a logger with a path such as: | |||
c:\\Documents and Settings\\username\\Application Data\\[logFileSubDirectoryName]\\[logFileName] | |||
On the Mac it'll create something like: | |||
~/Library/Logs/[logFileSubDirectoryName]/[logFileName] | |||
@see createDefaultAppLogger | |||
*/ | |||
static File getSystemLogFileFolder(); | |||
// (implementation of the Logger virtual method) | |||
void logMessage (const String&); | |||
private: | |||
//============================================================================== | |||
File logFile; | |||
CriticalSection logLock; | |||
void trimFileSize (int maxFileSizeBytes) const; | |||
void trimFileSize (int64 maxFileSizeBytes) const; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileLogger); | |||
}; | |||
@@ -23,25 +23,20 @@ | |||
============================================================================== | |||
*/ | |||
Logger::Logger() | |||
{ | |||
} | |||
Logger::Logger() {} | |||
Logger::~Logger() | |||
{ | |||
// You're deleting this logger while it's still being used! | |||
// Always call Logger::setCurrentLogger (nullptr) before deleting the active logger. | |||
jassert (currentLogger != this); | |||
} | |||
//============================================================================== | |||
Logger* Logger::currentLogger = nullptr; | |||
void Logger::setCurrentLogger (Logger* const newLogger, | |||
const bool deleteOldLogger) | |||
void Logger::setCurrentLogger (Logger* const newLogger) noexcept | |||
{ | |||
Logger* const oldLogger = currentLogger; | |||
currentLogger = newLogger; | |||
if (deleteOldLogger) | |||
delete oldLogger; | |||
} | |||
void Logger::writeToLog (const String& message) | |||
@@ -53,10 +48,10 @@ void Logger::writeToLog (const String& message) | |||
} | |||
#if JUCE_LOG_ASSERTIONS | |||
void JUCE_API logAssertion (const char* filename, const int lineNum) noexcept | |||
void JUCE_API logAssertion (const char* const filename, const int lineNum) noexcept | |||
{ | |||
String m ("JUCE Assertion failure in "); | |||
m << filename << ", line " << lineNum; | |||
m << File (filename).getFileName() << ':' << lineNum; | |||
Logger::writeToLog (m); | |||
} | |||
@@ -51,14 +51,11 @@ public: | |||
//============================================================================== | |||
/** Sets the current logging class to use. | |||
Note that the object passed in won't be deleted when no longer needed. | |||
Note that the object passed in will not be owned or deleted by the logger, so | |||
the caller must make sure that it is not deleted while still being used. | |||
A null pointer can be passed-in to disable any logging. | |||
If deleteOldLogger is set to true, the existing logger will be | |||
deleted (if there is one). | |||
*/ | |||
static void JUCE_CALLTYPE setCurrentLogger (Logger* newLogger, | |||
bool deleteOldLogger = false); | |||
static void JUCE_CALLTYPE setCurrentLogger (Logger* newLogger) noexcept; | |||
/** Writes a string to the current logger. | |||
@@ -84,7 +81,6 @@ protected: | |||
Logger(); | |||
/** This is overloaded by subclasses to implement custom logging behaviour. | |||
@see setCurrentLogger | |||
*/ | |||
virtual void logMessage (const String& message) = 0; | |||
@@ -103,17 +103,17 @@ File PropertiesFile::Options::getDefaultFile() const | |||
//============================================================================== | |||
PropertiesFile::PropertiesFile (const File& file_, const Options& options_) | |||
: PropertySet (options_.ignoreCaseOfKeyNames), | |||
file (file_), options (options_), | |||
PropertiesFile::PropertiesFile (const File& f, const Options& o) | |||
: PropertySet (o.ignoreCaseOfKeyNames), | |||
file (f), options (o), | |||
loadedOk (false), needsWriting (false) | |||
{ | |||
initialise(); | |||
} | |||
PropertiesFile::PropertiesFile (const Options& options_) | |||
: PropertySet (options_.ignoreCaseOfKeyNames), | |||
file (options_.getDefaultFile()), options (options_), | |||
PropertiesFile::PropertiesFile (const Options& o) | |||
: PropertySet (o.ignoreCaseOfKeyNames), | |||
file (o.getDefaultFile()), options (o), | |||
loadedOk (false), needsWriting (false) | |||
{ | |||
initialise(); | |||