Line data Source code
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 "nsPrintfCString.h"
18 : #include "nsString.h"
19 : #include "nsToolkitCompsCID.h"
20 : #include "nsXPCOMCIDInternal.h"
21 : #include "mozilla/XREAppData.h"
22 :
23 : #include "mozilla/Services.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 : * Creates a new profile with a timestamp in the name to use for profile reset.
35 : */
36 : nsresult
37 0 : CreateResetProfile(nsIToolkitProfileService* aProfileSvc, const nsACString& aOldProfileName, nsIToolkitProfile* *aNewProfile)
38 : {
39 0 : MOZ_ASSERT(aProfileSvc, "NULL profile service");
40 :
41 0 : nsCOMPtr<nsIToolkitProfile> newProfile;
42 : // Make the new profile the old profile (or "default-") + the time in seconds since epoch for uniqueness.
43 0 : nsAutoCString newProfileName;
44 0 : if (!aOldProfileName.IsEmpty()) {
45 0 : newProfileName.Assign(aOldProfileName);
46 0 : newProfileName.Append("-");
47 : } else {
48 0 : newProfileName.Assign("default-");
49 : }
50 0 : newProfileName.Append(nsPrintfCString("%" PRId64, PR_Now() / 1000));
51 0 : nsresult rv = aProfileSvc->CreateProfile(nullptr, // choose a default dir for us
52 : newProfileName,
53 0 : getter_AddRefs(newProfile));
54 0 : if (NS_FAILED(rv)) return rv;
55 :
56 0 : rv = aProfileSvc->Flush();
57 0 : if (NS_FAILED(rv)) return rv;
58 :
59 0 : newProfile.swap(*aNewProfile);
60 :
61 0 : return NS_OK;
62 : }
63 :
64 : /**
65 : * Delete the profile directory being reset after a backup and delete the local profile directory.
66 : */
67 : nsresult
68 0 : ProfileResetCleanup(nsIToolkitProfile* aOldProfile)
69 : {
70 : nsresult rv;
71 0 : nsCOMPtr<nsIFile> profileDir;
72 0 : rv = aOldProfile->GetRootDir(getter_AddRefs(profileDir));
73 0 : if (NS_FAILED(rv)) return rv;
74 :
75 0 : nsCOMPtr<nsIFile> profileLocalDir;
76 0 : rv = aOldProfile->GetLocalDir(getter_AddRefs(profileLocalDir));
77 0 : if (NS_FAILED(rv)) return rv;
78 :
79 : // Get the friendly name for the backup directory.
80 0 : nsCOMPtr<nsIStringBundleService> sbs = mozilla::services::GetStringBundleService();
81 0 : if (!sbs) return NS_ERROR_FAILURE;
82 :
83 0 : nsCOMPtr<nsIStringBundle> sb;
84 0 : rv = sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb));
85 0 : if (!sb) return NS_ERROR_FAILURE;
86 :
87 0 : NS_ConvertUTF8toUTF16 appName(gAppData->name);
88 0 : const char16_t* params[] = {appName.get(), appName.get()};
89 :
90 0 : nsXPIDLString resetBackupDirectoryName;
91 :
92 : static const char16_t* kResetBackupDirectory = u"resetBackupDirectory";
93 0 : rv = sb->FormatStringFromName(kResetBackupDirectory, params, 2,
94 0 : getter_Copies(resetBackupDirectoryName));
95 :
96 : // Get info to copy the old root profile dir to the desktop as a backup.
97 0 : nsCOMPtr<nsIFile> backupDest, containerDest, profileDest;
98 0 : rv = NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(backupDest));
99 0 : if (NS_FAILED(rv)) {
100 : // Fall back to the home directory if the desktop is not available.
101 0 : rv = NS_GetSpecialDirectory(NS_OS_HOME_DIR, getter_AddRefs(backupDest));
102 0 : if (NS_FAILED(rv)) return rv;
103 : }
104 :
105 : // Try to create a directory for all the backups
106 0 : backupDest->Clone(getter_AddRefs(containerDest));
107 0 : containerDest->Append(resetBackupDirectoryName);
108 0 : rv = containerDest->Create(nsIFile::DIRECTORY_TYPE, 0700);
109 : // It's OK if it already exists, if and only if it is a directory
110 0 : if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
111 : bool containerIsDir;
112 0 : rv = containerDest->IsDirectory(&containerIsDir);
113 0 : if (NS_FAILED(rv) || !containerIsDir) {
114 0 : return rv;
115 : }
116 0 : } else if (NS_FAILED(rv)) {
117 0 : return rv;
118 : }
119 :
120 : // Get the name of the profile
121 0 : nsAutoString leafName;
122 0 : rv = profileDir->GetLeafName(leafName);
123 0 : if (NS_FAILED(rv)) return rv;
124 :
125 : // Try to create a unique directory for the profile:
126 0 : containerDest->Clone(getter_AddRefs(profileDest));
127 0 : profileDest->Append(leafName);
128 0 : rv = profileDest->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700);
129 0 : if (NS_FAILED(rv)) return rv;
130 :
131 : // Get the unique profile name
132 0 : rv = profileDest->GetLeafName(leafName);
133 0 : if (NS_FAILED(rv)) return rv;
134 :
135 : // Delete the empty directory that CreateUnique just created.
136 0 : rv = profileDest->Remove(false);
137 0 : if (NS_FAILED(rv)) return rv;
138 :
139 : // Show a progress window while the cleanup happens since the disk I/O can take time.
140 0 : nsCOMPtr<nsIWindowWatcher> windowWatcher(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
141 0 : if (!windowWatcher) return NS_ERROR_FAILURE;
142 :
143 0 : nsCOMPtr<nsIAppStartup> appStartup(do_GetService(NS_APPSTARTUP_CONTRACTID));
144 0 : if (!appStartup) return NS_ERROR_FAILURE;
145 :
146 0 : nsCOMPtr<mozIDOMWindowProxy> progressWindow;
147 0 : rv = windowWatcher->OpenWindow(nullptr,
148 : kResetProgressURL,
149 : "_blank",
150 : "centerscreen,chrome,titlebar",
151 : nullptr,
152 0 : getter_AddRefs(progressWindow));
153 0 : if (NS_FAILED(rv)) return rv;
154 :
155 : // Create a new thread to do the bulk of profile cleanup to stay responsive.
156 0 : nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID);
157 0 : nsCOMPtr<nsIThread> cleanupThread;
158 0 : rv = tm->NewThread(0, 0, getter_AddRefs(cleanupThread));
159 0 : if (NS_SUCCEEDED(rv)) {
160 : nsCOMPtr<nsIRunnable> runnable = new ProfileResetCleanupAsyncTask(profileDir, profileLocalDir,
161 0 : containerDest, leafName);
162 0 : cleanupThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL);
163 : // The result callback will shut down the worker thread.
164 :
165 : // Wait for the cleanup thread to complete.
166 0 : SpinEventLoopUntil([&]() { return gProfileResetCleanupCompleted; });
167 : } else {
168 0 : gProfileResetCleanupCompleted = true;
169 0 : NS_WARNING("Cleanup thread creation failed");
170 0 : return rv;
171 : }
172 : // Close the progress window now that the cleanup thread is done.
173 0 : auto* piWindow = nsPIDOMWindowOuter::From(progressWindow);
174 0 : piWindow->Close();
175 :
176 : // Delete the old profile from profiles.ini. The folder was already deleted by the thread above.
177 0 : rv = aOldProfile->Remove(false);
178 0 : if (NS_FAILED(rv)) NS_WARNING("Could not remove the profile");
179 :
180 0 : return rv;
181 : }
|