diff --git a/extras/Introjucer/Introjucer.jucer b/extras/Introjucer/Introjucer.jucer index 1e348eb2a9..caebf159f5 100644 --- a/extras/Introjucer/Introjucer.jucer +++ b/extras/Introjucer/Introjucer.jucer @@ -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"/> diff --git a/extras/Introjucer/JuceLibraryCode/AppConfig.h b/extras/Introjucer/JuceLibraryCode/AppConfig.h index a0118bb8c8..daf33c2af6 100644 --- a/extras/Introjucer/JuceLibraryCode/AppConfig.h +++ b/extras/Introjucer/JuceLibraryCode/AppConfig.h @@ -38,7 +38,7 @@ #endif #ifndef JUCE_LOG_ASSERTIONS - //#define JUCE_LOG_ASSERTIONS + #define JUCE_LOG_ASSERTIONS 1 #endif #ifndef JUCE_CHECK_MEMORY_LEAKS diff --git a/extras/Introjucer/Source/Application/jucer_Application.h b/extras/Introjucer/Source/Application/jucer_Application.h index 9e351a1163..c8fdbb6a92 100644 --- a/extras/Introjucer/Source/Application/jucer_Application.h +++ b/extras/Introjucer/Source/Application/jucer_Application.h @@ -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 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 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 appearanceEditorWindow, utf8Window; + ScopedPointer logger; + private: class AsyncQuitRetrier : private Timer { diff --git a/extras/Introjucer/Source/Project Saving/jucer_ProjectSaver.h b/extras/Introjucer/Source/Project Saving/jucer_ProjectSaver.h index af2fa3a4fe..8f0df6e2d7 100644 --- a/extras/Introjucer/Source/Project Saving/jucer_ProjectSaver.h +++ b/extras/Introjucer/Source/Project Saving/jucer_ProjectSaver.h @@ -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()) diff --git a/extras/Introjucer/Source/Project/jucer_Project.cpp b/extras/Introjucer/Source/Project/jucer_Project.cpp index 6c8c7bcb56..ebe9ab2fb1 100644 --- a/extras/Introjucer/Source/Project/jucer_Project.cpp +++ b/extras/Introjucer/Source/Project/jucer_Project.cpp @@ -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(); diff --git a/extras/Introjucer/Source/Utility/jucer_StoredSettings.cpp b/extras/Introjucer/Source/Utility/jucer_StoredSettings.cpp index a697614307..11d17f2bab 100644 --- a/extras/Introjucer/Source/Utility/jucer_StoredSettings.cpp +++ b/extras/Introjucer/Source/Utility/jucer_StoredSettings.cpp @@ -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 xml (appearance.settings.createXml()); + props.setValue ("editorColours", xml); + } - { - const ScopedPointer 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 keys (commandManager->getKeyMappings()->createXml (true)); - if (commandManager != nullptr) - { - ScopedPointer 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() diff --git a/extras/Introjucer/Source/Utility/jucer_StoredSettings.h b/extras/Introjucer/Source/Utility/jucer_StoredSettings.h index 760221ccbd..31c61ec8ea 100644 --- a/extras/Introjucer/Source/Utility/jucer_StoredSettings.h +++ b/extras/Introjucer/Source/Utility/jucer_StoredSettings.h @@ -71,6 +71,7 @@ public: private: OwnedArray propertyFiles; + void updateGlobalProps(); void loadSwatchColours(); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StoredSettings); diff --git a/modules/juce_core/files/juce_File.cpp b/modules/juce_core/files/juce_File.cpp index f61d5aeff6..7d15844c8f 100644 --- a/modules/juce_core/files/juce_File.cpp +++ b/modules/juce_core/files/juce_File.cpp @@ -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(); } diff --git a/modules/juce_core/logging/juce_FileLogger.cpp b/modules/juce_core/logging/juce_FileLogger.cpp index 7d796a7870..c217857703 100644 --- a/modules/juce_core/logging/juce_FileLogger.cpp +++ b/modules/juce_core/logging/juce_FileLogger.cpp @@ -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 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); } diff --git a/modules/juce_core/logging/juce_FileLogger.h b/modules/juce_core/logging/juce_FileLogger.h index 1fdd329cb3..c8402da821 100644 --- a/modules/juce_core/logging/juce_FileLogger.h +++ b/modules/juce_core/logging/juce_FileLogger.h @@ -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); }; diff --git a/modules/juce_core/logging/juce_Logger.cpp b/modules/juce_core/logging/juce_Logger.cpp index 50bef88a0b..51cdb02159 100644 --- a/modules/juce_core/logging/juce_Logger.cpp +++ b/modules/juce_core/logging/juce_Logger.cpp @@ -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); } diff --git a/modules/juce_core/logging/juce_Logger.h b/modules/juce_core/logging/juce_Logger.h index 6bec018bcd..619872f8d1 100644 --- a/modules/juce_core/logging/juce_Logger.h +++ b/modules/juce_core/logging/juce_Logger.h @@ -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; diff --git a/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp b/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp index ef77829b83..315e96d855 100644 --- a/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp +++ b/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp @@ -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();