1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "nsIAppStartup.h"
7 #include "nsIFile.h"
8 #include "nsIStringBundle.h"
9 #include "nsIToolkitProfile.h"
10 #include "nsIWindowWatcher.h"
11 
12 #include "ProfileReset.h"
13 
14 #include "nsDirectoryServiceDefs.h"
15 #include "nsDirectoryServiceUtils.h"
16 #include "nsPIDOMWindow.h"
17 #include "nsString.h"
18 #include "nsXPCOMCIDInternal.h"
19 #include "mozilla/Components.h"
20 #include "mozilla/XREAppData.h"
21 
22 #include "mozilla/SpinEventLoopUntil.h"
23 #include "mozilla/Unused.h"
24 #include "prtime.h"
25 
26 using namespace mozilla;
27 
28 extern const XREAppData* gAppData;
29 
30 static const char kProfileProperties[] =
31     "chrome://mozapps/locale/profile/profileSelection.properties";
32 
33 /**
34  * Spin up a thread to backup the old profile's main directory and delete the
35  * profile's local directory. Once complete have the profile service remove the
36  * old profile and if necessary make the new profile the default.
37  */
ProfileResetCleanup(nsToolkitProfileService * aService,nsIToolkitProfile * aOldProfile)38 nsresult ProfileResetCleanup(nsToolkitProfileService* aService,
39                              nsIToolkitProfile* aOldProfile) {
40   nsresult rv;
41   nsCOMPtr<nsIFile> profileDir;
42   rv = aOldProfile->GetRootDir(getter_AddRefs(profileDir));
43   if (NS_FAILED(rv)) return rv;
44 
45   nsCOMPtr<nsIFile> profileLocalDir;
46   rv = aOldProfile->GetLocalDir(getter_AddRefs(profileLocalDir));
47   if (NS_FAILED(rv)) return rv;
48 
49   // Get the friendly name for the backup directory.
50   nsCOMPtr<nsIStringBundleService> sbs =
51       mozilla::components::StringBundle::Service();
52   if (!sbs) return NS_ERROR_FAILURE;
53 
54   nsCOMPtr<nsIStringBundle> sb;
55   Unused << sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb));
56   if (!sb) return NS_ERROR_FAILURE;
57 
58   NS_ConvertUTF8toUTF16 appName(gAppData->name);
59   AutoTArray<nsString, 2> params = {appName, appName};
60 
61   nsAutoString resetBackupDirectoryName;
62 
63   static const char* kResetBackupDirectory = "resetBackupDirectory";
64   rv = sb->FormatStringFromName(kResetBackupDirectory, params,
65                                 resetBackupDirectoryName);
66   if (NS_FAILED(rv)) return rv;
67 
68   // Get info to copy the old root profile dir to the desktop as a backup.
69   nsCOMPtr<nsIFile> backupDest, containerDest, profileDest;
70   rv = NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(backupDest));
71   if (NS_FAILED(rv)) {
72     // Fall back to the home directory if the desktop is not available.
73     rv = NS_GetSpecialDirectory(NS_OS_HOME_DIR, getter_AddRefs(backupDest));
74     if (NS_FAILED(rv)) return rv;
75   }
76 
77   // Try to create a directory for all the backups
78   backupDest->Clone(getter_AddRefs(containerDest));
79   containerDest->Append(resetBackupDirectoryName);
80   rv = containerDest->Create(nsIFile::DIRECTORY_TYPE, 0700);
81   // It's OK if it already exists, if and only if it is a directory
82   if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
83     bool containerIsDir;
84     rv = containerDest->IsDirectory(&containerIsDir);
85     if (NS_FAILED(rv) || !containerIsDir) {
86       return rv;
87     }
88   } else if (NS_FAILED(rv)) {
89     return rv;
90   }
91 
92   // Get the name of the profile
93   nsAutoString leafName;
94   rv = profileDir->GetLeafName(leafName);
95   if (NS_FAILED(rv)) return rv;
96 
97   // Try to create a unique directory for the profile:
98   containerDest->Clone(getter_AddRefs(profileDest));
99   profileDest->Append(leafName);
100   rv = profileDest->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700);
101   if (NS_FAILED(rv)) return rv;
102 
103   // Get the unique profile name
104   rv = profileDest->GetLeafName(leafName);
105   if (NS_FAILED(rv)) return rv;
106 
107   // Delete the empty directory that CreateUnique just created.
108   rv = profileDest->Remove(false);
109   if (NS_FAILED(rv)) return rv;
110 
111   // Show a progress window while the cleanup happens since the disk I/O can
112   // take time.
113   nsCOMPtr<nsIWindowWatcher> windowWatcher(
114       do_GetService(NS_WINDOWWATCHER_CONTRACTID));
115   if (!windowWatcher) return NS_ERROR_FAILURE;
116 
117   nsCOMPtr<nsIAppStartup> appStartup(components::AppStartup::Service());
118   if (!appStartup) return NS_ERROR_FAILURE;
119 
120   nsCOMPtr<mozIDOMWindowProxy> progressWindow;
121   rv = windowWatcher->OpenWindow(nullptr, nsDependentCString(kResetProgressURL),
122                                  "_blank"_ns, "centerscreen,chrome,titlebar"_ns,
123                                  nullptr, getter_AddRefs(progressWindow));
124   if (NS_FAILED(rv)) return rv;
125 
126   // Create a new thread to do the bulk of profile cleanup to stay responsive.
127   nsCOMPtr<nsIThread> cleanupThread;
128   rv = NS_NewNamedThread("ResetCleanup", getter_AddRefs(cleanupThread));
129   if (NS_SUCCEEDED(rv)) {
130     nsCOMPtr<nsIRunnable> runnable = new ProfileResetCleanupAsyncTask(
131         profileDir, profileLocalDir, containerDest, leafName);
132     cleanupThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL);
133     // The result callback will shut down the worker thread.
134 
135     // Wait for the cleanup thread to complete.
136     SpinEventLoopUntil([&]() { return gProfileResetCleanupCompleted; });
137   } else {
138     gProfileResetCleanupCompleted = true;
139     NS_WARNING("Cleanup thread creation failed");
140     return rv;
141   }
142   // Close the progress window now that the cleanup thread is done.
143   auto* piWindow = nsPIDOMWindowOuter::From(progressWindow);
144   piWindow->Close();
145 
146   return aService->ApplyResetProfile(aOldProfile);
147 }
148