michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsIAppStartup.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsIFile.h" michael@0: #include "nsIStringBundle.h" michael@0: #include "nsIToolkitProfile.h" michael@0: #include "nsIWindowWatcher.h" michael@0: michael@0: #include "ProfileReset.h" michael@0: michael@0: #include "nsDirectoryServiceDefs.h" michael@0: #include "nsDirectoryServiceUtils.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsToolkitCompsCID.h" michael@0: #include "nsXPCOMCIDInternal.h" michael@0: #include "nsXREAppData.h" michael@0: michael@0: #include "mozilla/Services.h" michael@0: #include "prtime.h" michael@0: michael@0: extern const nsXREAppData* gAppData; michael@0: michael@0: static const char kProfileProperties[] = michael@0: "chrome://mozapps/locale/profile/profileSelection.properties"; michael@0: michael@0: /** michael@0: * Creates a new profile with a timestamp in the name to use for profile reset. michael@0: */ michael@0: nsresult michael@0: CreateResetProfile(nsIToolkitProfileService* aProfileSvc, nsIToolkitProfile* *aNewProfile) michael@0: { michael@0: NS_ABORT_IF_FALSE(aProfileSvc, "NULL profile service"); michael@0: michael@0: nsCOMPtr newProfile; michael@0: // Make the new profile "default-" + the time in seconds since epoch for uniqueness. michael@0: nsAutoCString newProfileName("default-"); michael@0: newProfileName.Append(nsPrintfCString("%lld", PR_Now() / 1000)); michael@0: nsresult rv = aProfileSvc->CreateProfile(nullptr, // choose a default dir for us michael@0: newProfileName, michael@0: getter_AddRefs(newProfile)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = aProfileSvc->Flush(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: newProfile.swap(*aNewProfile); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Delete the profile directory being reset after a backup and delete the local profile directory. michael@0: */ michael@0: nsresult michael@0: ProfileResetCleanup(nsIToolkitProfile* aOldProfile) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr profileDir; michael@0: rv = aOldProfile->GetRootDir(getter_AddRefs(profileDir)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr profileLocalDir; michael@0: rv = aOldProfile->GetLocalDir(getter_AddRefs(profileLocalDir)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Get the friendly name for the backup directory. michael@0: nsCOMPtr sbs = mozilla::services::GetStringBundleService(); michael@0: if (!sbs) return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr sb; michael@0: rv = sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb)); michael@0: if (!sb) return NS_ERROR_FAILURE; michael@0: michael@0: NS_ConvertUTF8toUTF16 appName(gAppData->name); michael@0: const char16_t* params[] = {appName.get(), appName.get()}; michael@0: michael@0: nsXPIDLString resetBackupDirectoryName; michael@0: michael@0: static const char16_t* kResetBackupDirectory = MOZ_UTF16("resetBackupDirectory"); michael@0: rv = sb->FormatStringFromName(kResetBackupDirectory, params, 2, michael@0: getter_Copies(resetBackupDirectoryName)); michael@0: michael@0: // Get info to copy the old root profile dir to the desktop as a backup. michael@0: nsCOMPtr backupDest, containerDest, profileDest; michael@0: rv = NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(backupDest)); michael@0: if (NS_FAILED(rv)) { michael@0: // Fall back to the home directory if the desktop is not available. michael@0: rv = NS_GetSpecialDirectory(NS_OS_HOME_DIR, getter_AddRefs(backupDest)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: // Try to create a directory for all the backups michael@0: backupDest->Clone(getter_AddRefs(containerDest)); michael@0: containerDest->Append(resetBackupDirectoryName); michael@0: rv = containerDest->Create(nsIFile::DIRECTORY_TYPE, 0700); michael@0: // It's OK if it already exists, if and only if it is a directory michael@0: if (rv == NS_ERROR_FILE_ALREADY_EXISTS) { michael@0: bool containerIsDir; michael@0: rv = containerDest->IsDirectory(&containerIsDir); michael@0: if (NS_FAILED(rv) || !containerIsDir) { michael@0: return rv; michael@0: } michael@0: } else if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: // Get the name of the profile michael@0: nsAutoString leafName; michael@0: rv = profileDir->GetLeafName(leafName); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Try to create a unique directory for the profile: michael@0: containerDest->Clone(getter_AddRefs(profileDest)); michael@0: profileDest->Append(leafName); michael@0: rv = profileDest->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Get the unique profile name michael@0: rv = profileDest->GetLeafName(leafName); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Delete the empty directory that CreateUnique just created. michael@0: rv = profileDest->Remove(false); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Show a progress window while the cleanup happens since the disk I/O can take time. michael@0: nsCOMPtr windowWatcher(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); michael@0: if (!windowWatcher) return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr appStartup(do_GetService(NS_APPSTARTUP_CONTRACTID)); michael@0: if (!appStartup) return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr progressWindow; michael@0: rv = windowWatcher->OpenWindow(nullptr, michael@0: kResetProgressURL, michael@0: "_blank", michael@0: "centerscreen,chrome,titlebar", michael@0: nullptr, michael@0: getter_AddRefs(progressWindow)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Create a new thread to do the bulk of profile cleanup to stay responsive. michael@0: nsCOMPtr tm = do_GetService(NS_THREADMANAGER_CONTRACTID); michael@0: nsCOMPtr cleanupThread; michael@0: rv = tm->NewThread(0, 0, getter_AddRefs(cleanupThread)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsCOMPtr runnable = new ProfileResetCleanupAsyncTask(profileDir, profileLocalDir, michael@0: containerDest, leafName); michael@0: cleanupThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL); michael@0: // The result callback will shut down the worker thread. michael@0: michael@0: nsIThread *thread = NS_GetCurrentThread(); michael@0: // Wait for the cleanup thread to complete. michael@0: while(!gProfileResetCleanupCompleted) { michael@0: NS_ProcessNextEvent(thread); michael@0: } michael@0: } else { michael@0: gProfileResetCleanupCompleted = true; michael@0: NS_WARNING("Cleanup thread creation failed"); michael@0: return rv; michael@0: } michael@0: // Close the progress window now that the cleanup thread is done. michael@0: progressWindow->Close(); michael@0: michael@0: // Delete the old profile from profiles.ini. The folder was already deleted by the thread above. michael@0: rv = aOldProfile->Remove(false); michael@0: if (NS_FAILED(rv)) NS_WARNING("Could not remove the profile"); michael@0: michael@0: return rv; michael@0: }