| @@ -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(); | |||