* Use separate folder for disk cache in debug mode to avoid mixing debug/release-mode object files while testing * Quote the server's file name as it may contain spaces etc. * Fix saving source files during compilation on Windows * Fix JuceDemo for live builds on Mac: long chains of recursive operator<< invocations caused compiler crash * Move code for creating disabled Build tab to extra function and add names to improve readability * Implement new "subscribe" behavior for createDisabledBuildTab * Clean up trailing spacestags/2021-05-28
| @@ -119,7 +119,9 @@ static String getAllSystemInfo() | |||
| << "User region: " << SystemStats::getUserRegion() << newLine | |||
| << "User language: " << SystemStats::getUserLanguage() << newLine | |||
| << "Display language: " << SystemStats::getDisplayLanguage() << newLine | |||
| << newLine | |||
| << newLine; | |||
| systemInfo | |||
| << "Number of CPUs: " << SystemStats::getNumCpus() << newLine | |||
| << "Memory size: " << SystemStats::getMemorySizeInMegabytes() << " MB" << newLine | |||
| << "CPU vendor: " << SystemStats::getCpuVendor() << newLine | |||
| @@ -134,12 +136,16 @@ static String getAllSystemInfo() | |||
| << "CPU has 3DNOW: " << (SystemStats::has3DNow() ? "yes" : "no") << newLine | |||
| << "CPU has AVX: " << (SystemStats::hasAVX() ? "yes" : "no") << newLine | |||
| << "CPU has AVX2: " << (SystemStats::hasAVX2() ? "yes" : "no") << newLine | |||
| << newLine | |||
| << newLine; | |||
| systemInfo | |||
| << "Current working directory: " << File::getCurrentWorkingDirectory().getFullPathName() << newLine | |||
| << "Current application file: " << File::getSpecialLocation (File::currentApplicationFile).getFullPathName() << newLine | |||
| << "Current executable file: " << File::getSpecialLocation (File::currentExecutableFile) .getFullPathName() << newLine | |||
| << "Invoked executable file: " << File::getSpecialLocation (File::invokedExecutableFile) .getFullPathName() << newLine | |||
| << newLine | |||
| << newLine; | |||
| systemInfo | |||
| << "User home folder: " << File::getSpecialLocation (File::userHomeDirectory) .getFullPathName() << newLine | |||
| << "User desktop folder: " << File::getSpecialLocation (File::userDesktopDirectory) .getFullPathName() << newLine | |||
| << "User documents folder: " << File::getSpecialLocation (File::userDocumentsDirectory) .getFullPathName() << newLine | |||
| @@ -150,7 +156,9 @@ static String getAllSystemInfo() | |||
| << "Common application data folder: " << File::getSpecialLocation (File::commonApplicationDataDirectory).getFullPathName() << newLine | |||
| << "Common documents folder: " << File::getSpecialLocation (File::commonDocumentsDirectory) .getFullPathName() << newLine | |||
| << "Local temp folder: " << File::getSpecialLocation (File::tempDirectory) .getFullPathName() << newLine | |||
| << newLine | |||
| << newLine; | |||
| systemInfo | |||
| << "File System roots: " << getFileSystemRoots() << newLine | |||
| << "Free space in home folder: " << File::descriptionOfSizeInBytes (File::getSpecialLocation (File::userHomeDirectory) | |||
| .getBytesFreeOnVolume()) << newLine | |||
| @@ -89,9 +89,15 @@ namespace ProjectProperties | |||
| static File getCacheLocation (Project& project) | |||
| { | |||
| String cacheFolderName = project.getProjectFilenameRoot() + "_" + project.getProjectUID(); | |||
| #if JUCE_DEBUG | |||
| cacheFolderName += "_debug"; | |||
| #endif | |||
| return getProjucerTempFolder() | |||
| .getChildFile ("Intermediate Files") | |||
| .getChildFile (project.getProjectFilenameRoot() + "_" + project.getProjectUID()); | |||
| .getChildFile (cacheFolderName); | |||
| } | |||
| } | |||
| @@ -230,7 +230,7 @@ String createCommandLineForLaunchingServer (const String& pipeName, const String | |||
| const File exe (File::getSpecialLocation (File::currentExecutableFile).getFullPathName()); | |||
| return exe.getFullPathName() + " " + commandPrefix + info.joinIntoString (commandTokenSeparator); | |||
| return "\"" + exe.getFullPathName() + "\" " + commandPrefix + info.joinIntoString (commandTokenSeparator); | |||
| } | |||
| static ServerIPC* currentServer = nullptr; | |||
| @@ -354,65 +354,95 @@ struct BuildTabComponent : public ConcertinaPanel | |||
| struct ProjucerDisabledComp : public Component, | |||
| private Button::Listener | |||
| { | |||
| ProjucerDisabledComp (String message, bool loggedIn, bool canLogin, bool requirePurchase = false, | |||
| const String& loginName = String()) | |||
| : isLoggedIn (loggedIn), isPurchaseButton (requirePurchase) | |||
| ProjucerDisabledComp (String message, bool loggedIn, bool showSubscribeButton, | |||
| bool showSignInButton, bool showSwitchAccountButton) | |||
| : isLoggedIn (loggedIn) | |||
| { | |||
| infoLabel.setColour (Label::textColourId, findColour (mainBackgroundColourId).contrasting (0.7f)); | |||
| infoLabel.setJustificationType (Justification::centred); | |||
| infoLabel.setText (message, dontSendNotification); | |||
| addAndMakeVisible (infoLabel); | |||
| if (canLogin) | |||
| if (showSubscribeButton) | |||
| { | |||
| addAndMakeVisible (loginButton); | |||
| loginButton.addListener (this); | |||
| subscribeButton = new TextButton (String ( "Subscribe...")); | |||
| addAndMakeVisible (*subscribeButton); | |||
| subscribeButton->addListener (this); | |||
| } | |||
| if (isPurchaseButton) | |||
| { | |||
| loginButton.setButtonText ("Purchase JUCE Pro..."); | |||
| signOutButton = new TextButton (String ("Sign Out ") + loginName); | |||
| addAndMakeVisible (*signOutButton); | |||
| signOutButton->addListener (this); | |||
| } | |||
| if (showSignInButton) | |||
| { | |||
| signInButton = new TextButton (String ( "Sign in...")); | |||
| addAndMakeVisible (*signInButton); | |||
| signInButton->addListener (this); | |||
| } | |||
| if (showSwitchAccountButton) | |||
| { | |||
| switchAccountButton = new TextButton (String ("Switch account...")); | |||
| addAndMakeVisible (*switchAccountButton); | |||
| switchAccountButton->addListener (this); | |||
| } | |||
| } | |||
| void resized() override | |||
| { | |||
| infoLabel.centreWithSize (proportionOfWidth (0.9f), 200); | |||
| loginButton.setSize (jmin (getWidth() - 10, 150), 22); | |||
| loginButton.setCentrePosition (infoLabel.getBounds().getCentreX(), | |||
| infoLabel.getBottom() + loginButton.getHeight() * 2); | |||
| int infoWidth = proportionOfWidth (0.9f); | |||
| int infoHeight = 100; | |||
| infoLabel.centreWithSize (infoWidth, infoHeight); | |||
| if (signOutButton != nullptr) | |||
| int buttonWidth = jmin (getWidth() - 10, 150); | |||
| int buttonHeight = 22; | |||
| int itemDistance = 10; | |||
| int buttonCenterX = infoLabel.getBounds().getCentreX(); | |||
| int buttonCenterY = infoLabel.getBottom() + itemDistance + buttonHeight / 2; | |||
| if (subscribeButton.get() != nullptr) | |||
| { | |||
| signOutButton->setSize (jmin (getWidth() - 10, 150), 22); | |||
| signOutButton->setCentrePosition (infoLabel.getBounds().getCentreX(), | |||
| loginButton.getBottom() + 20); | |||
| subscribeButton->setSize (buttonWidth, buttonHeight); | |||
| subscribeButton->setCentrePosition (buttonCenterX, buttonCenterY); | |||
| buttonCenterY += itemDistance + buttonHeight; | |||
| } | |||
| if (signInButton.get() != nullptr) | |||
| { | |||
| signInButton->setSize (buttonWidth, buttonHeight); | |||
| signInButton->setCentrePosition (buttonCenterX, buttonCenterY); | |||
| buttonCenterY += itemDistance + buttonHeight; | |||
| } | |||
| if (switchAccountButton.get() != nullptr) | |||
| { | |||
| switchAccountButton->setSize (buttonWidth, buttonHeight); | |||
| switchAccountButton->setCentrePosition (buttonCenterX, buttonCenterY); | |||
| } | |||
| } | |||
| void buttonClicked (Button* btn) override | |||
| { | |||
| if (btn == &loginButton) | |||
| if (btn == subscribeButton.get()) | |||
| { | |||
| URL ("http://www.juce.com/get-juce").launchInDefaultBrowser(); | |||
| } | |||
| else if (btn == signInButton.get()) | |||
| { | |||
| if (isPurchaseButton) | |||
| URL ("http://www.juce.com").launchInDefaultBrowser(); | |||
| else | |||
| ProjucerApplication::getApp().showLoginForm(); | |||
| ProjucerApplication::getApp().showLoginForm(); | |||
| } | |||
| else if (btn == signOutButton.get()) | |||
| else if (btn == switchAccountButton.get()) | |||
| { | |||
| ProjucerLicenses::getInstance()->logout(); | |||
| ProjucerApplication::getApp().updateAllBuildTabs(); | |||
| ProjucerApplication::getApp().showLoginForm(); | |||
| } | |||
| } | |||
| bool isLoggedIn; | |||
| private: | |||
| Label infoLabel { "info", String() }; | |||
| TextButton loginButton { "Log-in..." }; | |||
| ScopedPointer<TextButton> signOutButton; | |||
| bool isLoggedIn, isPurchaseButton; | |||
| ScopedPointer<TextButton> subscribeButton; | |||
| ScopedPointer<TextButton> signInButton; | |||
| ScopedPointer<TextButton> switchAccountButton; | |||
| }; | |||
| struct EnableBuildComp : public Component | |||
| @@ -458,33 +488,30 @@ Component* ProjectContentComponent::createBuildTab (CompileEngineChildProcess* c | |||
| return new BuildTabComponent (child, new ProjucerAppClasses::ErrorListComp (child->errorList)); | |||
| } | |||
| auto& unlockStatus = *ProjucerLicenses::getInstance(); | |||
| if (unlockStatus.hasLiveCodingLicence() | |||
| && project != nullptr | |||
| && LiveBuildProjectSettings::isBuildDisabled (*project)) | |||
| return new EnableBuildComp(); | |||
| if (unlockStatus.isLoggedIn()) | |||
| return new ProjucerDisabledComp (String ("The Projucer's live-build features are currently disabled!") + newLine | |||
| + newLine | |||
| + "Your account " + unlockStatus.getLoginName().quoted() | |||
| + " does not have an asscociated JUCE Pro license:", | |||
| true, true, true, unlockStatus.getLoginName()); | |||
| if (! unlockStatus.isDLLPresent()) | |||
| return new ProjucerDisabledComp (String ("The live-building DLL is missing!") + newLine | |||
| + newLine | |||
| + "To enable the compiler, you'll need to install the missing DLL " | |||
| + CompileEngineDLL::getDLLName().quoted() + newLine | |||
| + newLine | |||
| + "Visit the JUCE website/forum for more help on getting and installing the DLL!", | |||
| false, false); | |||
| return new ProjucerDisabledComp ("The Projucer's live-build features are currently disabled!\n\n" | |||
| "To enable them, you'll need to log-in with your JUCE account details:", | |||
| false, true, false); | |||
| jassert (project != nullptr); | |||
| const auto& unlockStatus = *ProjucerLicenses::getInstance(); | |||
| if (unlockStatus.hasLiveCodingLicence()) | |||
| { | |||
| jassert (unlockStatus.isLoggedIn()); | |||
| jassert (unlockStatus.isDLLPresent()); | |||
| return new EnableBuildComp(); | |||
| } | |||
| return createDisabledBuildTab(unlockStatus.isLoggedIn(), | |||
| unlockStatus.isDLLPresent()); | |||
| #endif | |||
| }; | |||
| //============================================================================== | |||
| Component* ProjectContentComponent::createDisabledBuildTab(bool loggedIn, bool dllPresent) { | |||
| bool showSubscribeButton = true; | |||
| bool showSignInButton = dllPresent && ! loggedIn; | |||
| bool showSwitchAccountButton = dllPresent && loggedIn; | |||
| return new ProjucerDisabledComp ( | |||
| "Subscribe to JUCE Pro or Indie to use the Projucer's live-build features:", | |||
| loggedIn, showSubscribeButton, showSignInButton, showSwitchAccountButton); | |||
| } | |||
| BuildTabComponent* findBuildTab (const TabbedComponent& tabs) | |||
| @@ -1499,7 +1526,8 @@ void ProjectContentComponent::handleMissingSystemHeaders() | |||
| deleteProjectTabs(); | |||
| createProjectTabs(); | |||
| ProjucerDisabledComp* buildTab = new ProjucerDisabledComp (tabMessage, false, false); | |||
| bool isLoggedIn = ProjucerLicenses::getInstance()->isLoggedIn(); | |||
| ProjucerDisabledComp* buildTab = new ProjucerDisabledComp (tabMessage, isLoggedIn, false, false, false); | |||
| treeViewTabs.addTab ("Build", Colours::transparentBlack, buildTab, true); | |||
| showBuildTab(); | |||
| @@ -154,6 +154,8 @@ private: | |||
| void timerCallback() override; | |||
| Component* createBuildTab (CompileEngineChildProcess*); | |||
| Component* createDisabledBuildTab (bool loggedIn, bool dllPresent); | |||
| bool isContinuousRebuildEnabled() { return getAppSettings().getGlobalProperties().getBoolValue ("continuousRebuild", true); } | |||
| void setContinuousRebuildEnabled (bool b) { getAppSettings().getGlobalProperties().setValue ("continuousRebuild", b); } | |||
| void rebuildNow(); | |||
| @@ -300,6 +300,21 @@ bool File::copyFileTo (const File& newFile) const | |||
| || (exists() && newFile.deleteFile() && copyInternal (newFile)); | |||
| } | |||
| bool File::replaceFileIn (const File& newFile) const | |||
| { | |||
| if (newFile.fullPath == fullPath) | |||
| return true; | |||
| if (! newFile.exists()) | |||
| return moveFileTo (newFile); | |||
| if (! replaceInternal (newFile)) | |||
| return false; | |||
| deleteFile(); | |||
| return true; | |||
| } | |||
| bool File::copyDirectoryTo (const File& newDirectory) const | |||
| { | |||
| if (isDirectory() && newDirectory.createDirectory()) | |||
| @@ -503,6 +503,18 @@ public: | |||
| */ | |||
| bool copyFileTo (const File& targetLocation) const; | |||
| /** Replaces a file. | |||
| Replace the file in the given location, assuming the replaced files identity. | |||
| Depending on the file system this will preserve file attributes such as | |||
| creation date, short file name, etc. | |||
| If replacement succeeds the original file is deleted. | |||
| @returns true if the operation succeeds | |||
| */ | |||
| bool replaceFileIn (const File& targetLocation) const; | |||
| /** Copies a directory. | |||
| Tries to copy an entire directory, recursively. | |||
| @@ -982,6 +994,7 @@ private: | |||
| Result createDirectoryInternal (const String&) const; | |||
| bool copyInternal (const File&) const; | |||
| bool moveInternal (const File&) const; | |||
| bool replaceInternal (const File&) const; | |||
| bool setFileTimesInternal (int64 m, int64 a, int64 c) const; | |||
| void getFileTimesInternal (int64& m, int64& a, int64& c) const; | |||
| bool setFileReadOnlyInternal (bool) const; | |||
| @@ -86,7 +86,7 @@ bool TemporaryFile::overwriteTargetFileWithTemporary() const | |||
| // Have a few attempts at overwriting the file before giving up.. | |||
| for (int i = 5; --i >= 0;) | |||
| { | |||
| if (temporaryFile.moveFileTo (targetFile)) | |||
| if (temporaryFile.replaceFileIn (targetFile)) | |||
| return true; | |||
| Thread::sleep (100); | |||
| @@ -440,6 +440,11 @@ bool File::moveInternal (const File& dest) const | |||
| return false; | |||
| } | |||
| bool File::replaceInternal (const File& dest) const | |||
| { | |||
| return moveInternal (dest); | |||
| } | |||
| Result File::createDirectoryInternal (const String& fileName) const | |||
| { | |||
| return getResultForReturnValue (mkdir (fileName.toUTF8(), 0777)); | |||
| @@ -220,6 +220,15 @@ bool File::moveInternal (const File& dest) const | |||
| return MoveFile (fullPath.toWideCharPointer(), dest.getFullPathName().toWideCharPointer()) != 0; | |||
| } | |||
| bool File::replaceInternal (const File& dest) const | |||
| { | |||
| void* lpExclude = 0; | |||
| void* lpReserved = 0; | |||
| return ReplaceFile (dest.getFullPathName().toWideCharPointer(), fullPath.toWideCharPointer(), | |||
| 0, REPLACEFILE_IGNORE_MERGE_ERRORS, lpExclude, lpReserved) != 0; | |||
| } | |||
| Result File::createDirectoryInternal (const String& fileName) const | |||
| { | |||
| return CreateDirectory (fileName.toWideCharPointer(), 0) ? Result::ok() | |||