Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(61)

Side by Side Diff: chrome/browser/download/download_path_reservation_tracker.cc

Issue 12212010: Truncate the download file name if it exceeds the filesystem limit. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Review fix (#14), rebase. Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « base/file_util_win.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/download/download_path_reservation_tracker.h" 5 #include "chrome/browser/download/download_path_reservation_tracker.h"
6 6
7 #include <map> 7 #include <map>
8 8
9 #include "base/bind.h" 9 #include "base/bind.h"
10 #include "base/callback.h" 10 #include "base/callback.h"
11 #include "base/file_util.h" 11 #include "base/file_util.h"
12 #include "base/logging.h" 12 #include "base/logging.h"
13 #include "base/path_service.h" 13 #include "base/path_service.h"
14 #include "base/stl_util.h" 14 #include "base/stl_util.h"
15 #include "base/string_util.h"
16 #include "base/third_party/icu/icu_utf.h"
15 #include "chrome/browser/download/download_util.h" 17 #include "chrome/browser/download/download_util.h"
16 #include "chrome/common/chrome_paths.h" 18 #include "chrome/common/chrome_paths.h"
17 #include "content/public/browser/browser_thread.h" 19 #include "content/public/browser/browser_thread.h"
18 #include "content/public/browser/download_id.h" 20 #include "content/public/browser/download_id.h"
19 #include "content/public/browser/download_item.h" 21 #include "content/public/browser/download_item.h"
20 22
21 using content::BrowserThread; 23 using content::BrowserThread;
22 using content::DownloadId; 24 using content::DownloadId;
23 using content::DownloadItem; 25 using content::DownloadItem;
24 26
25 namespace { 27 namespace {
26 28
27 typedef std::map<content::DownloadId, base::FilePath> ReservationMap; 29 typedef std::map<content::DownloadId, base::FilePath> ReservationMap;
28 30
31 // The lower bound for file name truncation. If the truncation results in a name
32 // shorter than this limit, we give up automatic truncation and prompt the user.
33 static const size_t kTruncatedNameLengthLowerbound = 5;
34
35 // The length of the suffix string we append for an intermediate file name.
36 // In the file name truncation, we keep the margin to append the suffix.
37 // TODO(kinaba): remove the margin. The user should be able to set maximum
38 // possible filename.
39 static const size_t kIntermediateNameSuffixLength = sizeof(".crdownload") - 1;
40
29 // Map of download path reservations. Each reserved path is associated with a 41 // Map of download path reservations. Each reserved path is associated with a
30 // DownloadId. This object is destroyed in |Revoke()| when there are no more 42 // DownloadId. This object is destroyed in |Revoke()| when there are no more
31 // reservations. 43 // reservations.
32 // 44 //
33 // It is not an error, although undesirable, to have multiple DownloadIds that 45 // It is not an error, although undesirable, to have multiple DownloadIds that
34 // are mapped to the same path. This can happen if a reservation is created that 46 // are mapped to the same path. This can happen if a reservation is created that
35 // is supposed to overwrite an existing reservation. 47 // is supposed to overwrite an existing reservation.
36 ReservationMap* g_reservation_map = NULL; 48 ReservationMap* g_reservation_map = NULL;
37 49
38 // Observes a DownloadItem for changes to its target path and state. Updates or 50 // Observes a DownloadItem for changes to its target path and state. Updates or
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
83 if (IsPathReserved(path)) 95 if (IsPathReserved(path))
84 return true; 96 return true;
85 97
86 // If the path exists in the file system, then the path is in use. 98 // If the path exists in the file system, then the path is in use.
87 if (file_util::PathExists(path)) 99 if (file_util::PathExists(path))
88 return true; 100 return true;
89 101
90 return false; 102 return false;
91 } 103 }
92 104
105 // Truncates path->BaseName() to satisfy the condition .value().size() <= limit.
asanka 2013/02/19 17:59:15 Nit: path->BaseName().value().size() <= limit. |l
kinaba 2013/02/20 13:57:41 Done.
106 // - It keeps the extension as is. Only truncates the body part.
asanka 2013/02/19 17:59:15 Nit: "base filename" would, I think, be clearer th
kinaba 2013/02/20 13:57:41 Done.
107 // - It secures the body part length to be >= kTruncatedNameLengthLowerbound.
108 // If it was unable to shorten the name, returns false.
109 bool TruncateFileName(base::FilePath* path, size_t limit) {
110 base::FilePath basename(path->BaseName());
111 // It is already short enough.
112 if (basename.value().size() <= limit)
113 return true;
114
115 base::FilePath dir(path->DirName());
116 base::FilePath::StringType ext(basename.Extension());
117 base::FilePath::StringType name(basename.RemoveExtension().value());
118
119 // Impossible to satisfy the limit.
120 if (limit < kTruncatedNameLengthLowerbound + ext.size())
121 return false;
122 limit -= ext.size();
123
124 // Encoding specific truncation logic.
125 base::FilePath::StringType truncated;
126 #if defined(OS_CHROMEOS) || defined(OS_MACOSX)
127 // UTF-8.
128 TruncateUTF8ToByteSize(name, limit, &truncated);
129 #elif defined(OS_WIN)
130 // UTF-16.
131 DCHECK(name.size() > limit);
132 truncated = name.substr(
133 0, limit > 0 && CBU16_IS_TRAIL(name[limit]) ? limit - 1 : limit);
asanka 2013/02/19 17:59:15 Nit: limit > 0 is always true at this point.
kinaba 2013/02/20 13:57:41 Done.
134 #else
135 // We cannot generally assume that the file name encoding is in UTF-8 (see
136 // the comment for FilePath::AsUTF8Unsafe), hence no safe way to truncate.
137 #endif
138
139 if (truncated.size() < kTruncatedNameLengthLowerbound)
140 return false;
141 *path = dir.Append(truncated + ext);
142 return true;
143 }
144
93 // Called on the FILE thread to reserve a download path. This method: 145 // Called on the FILE thread to reserve a download path. This method:
94 // - Creates directory |default_download_path| if it doesn't exist. 146 // - Creates directory |default_download_path| if it doesn't exist.
95 // - Verifies that the parent directory of |suggested_path| exists and is 147 // - Verifies that the parent directory of |suggested_path| exists and is
96 // writeable. 148 // writeable.
149 // - Truncates the suggested name if it exceeds the filesystem's limit.
97 // - Uniquifies |suggested_path| if |should_uniquify_path| is true. 150 // - Uniquifies |suggested_path| if |should_uniquify_path| is true.
98 // - Schedules |callback| on the UI thread with the reserved path and a flag 151 // - Schedules |callback| on the UI thread with the reserved path and a flag
99 // indicating whether the returned path has been successfully verified. 152 // indicating whether the returned path has been successfully verified.
100 void CreateReservation( 153 void CreateReservation(
101 DownloadId download_id, 154 DownloadId download_id,
102 const base::FilePath& suggested_path, 155 const base::FilePath& suggested_path,
103 const base::FilePath& default_download_path, 156 const base::FilePath& default_download_path,
104 bool should_uniquify, 157 bool should_uniquify,
105 const DownloadPathReservationTracker::ReservedPathCallback& callback) { 158 const DownloadPathReservationTracker::ReservedPathCallback& callback) {
106 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 159 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
107 DCHECK(download_id.IsValid()); 160 DCHECK(download_id.IsValid());
108 DCHECK(suggested_path.IsAbsolute()); 161 DCHECK(suggested_path.IsAbsolute());
109 162
110 // Create a reservation map if one doesn't exist. It will be automatically 163 // Create a reservation map if one doesn't exist. It will be automatically
111 // deleted when all the reservations are revoked. 164 // deleted when all the reservations are revoked.
112 if (g_reservation_map == NULL) 165 if (g_reservation_map == NULL)
113 g_reservation_map = new ReservationMap; 166 g_reservation_map = new ReservationMap;
114 167
115 ReservationMap& reservations = *g_reservation_map; 168 ReservationMap& reservations = *g_reservation_map;
116 DCHECK(!ContainsKey(reservations, download_id)); 169 DCHECK(!ContainsKey(reservations, download_id));
117 170
118 base::FilePath target_path(suggested_path.NormalizePathSeparators()); 171 base::FilePath target_path(suggested_path.NormalizePathSeparators());
119 bool is_path_writeable = true; 172 bool is_path_writeable = true;
120 bool has_conflicts = false; 173 bool has_conflicts = false;
174 bool name_too_long = false;
121 175
122 // Create the default download path if it doesn't already exist and is where 176 // Create the default download path if it doesn't already exist and is where
123 // we are going to create the downloaded file. |target_path| might point 177 // we are going to create the downloaded file. |target_path| might point
124 // elsewhere if this was a programmatic download. 178 // elsewhere if this was a programmatic download.
125 if (!default_download_path.empty() && 179 if (!default_download_path.empty() &&
126 default_download_path == target_path.DirName() && 180 default_download_path == target_path.DirName() &&
127 !file_util::DirectoryExists(default_download_path)) { 181 !file_util::DirectoryExists(default_download_path)) {
128 file_util::CreateDirectory(default_download_path); 182 file_util::CreateDirectory(default_download_path);
129 } 183 }
130 184
131 // Check writability of the suggested path. If we can't write to it, default 185 // Check writability of the suggested path. If we can't write to it, default
132 // to the user's "My Documents" directory. We'll prompt them in this case. 186 // to the user's "My Documents" directory. We'll prompt them in this case.
133 base::FilePath dir = target_path.DirName(); 187 base::FilePath dir = target_path.DirName();
134 base::FilePath filename = target_path.BaseName(); 188 base::FilePath filename = target_path.BaseName();
135 if (!file_util::PathIsWritable(dir)) { 189 if (!file_util::PathIsWritable(dir)) {
136 DVLOG(1) << "Unable to write to directory \"" << dir.value() << "\""; 190 DVLOG(1) << "Unable to write to directory \"" << dir.value() << "\"";
137 is_path_writeable = false; 191 is_path_writeable = false;
138 PathService::Get(chrome::DIR_USER_DOCUMENTS, &dir); 192 PathService::Get(chrome::DIR_USER_DOCUMENTS, &dir);
139 target_path = dir.Append(filename); 193 target_path = dir.Append(filename);
140 } 194 }
141 195
142 if (is_path_writeable && should_uniquify && IsPathInUse(target_path)) { 196 if (is_path_writeable) {
143 has_conflicts = true; 197 // Check the limit of file name length if it could be obtained. When the
144 for (int uniquifier = 1; 198 // suggested name exceeds the limit, truncate or prompt the user.
145 uniquifier <= DownloadPathReservationTracker::kMaxUniqueFiles; 199 int max_length = file_util::GetMaximumPathComponentLength(dir);
146 ++uniquifier) { 200 if (max_length != -1) {
147 base::FilePath path_to_check(target_path.InsertBeforeExtensionASCII( 201 int limit = max_length - kIntermediateNameSuffixLength;
148 StringPrintf(" (%d)", uniquifier))); 202 if (limit <= 0 || !TruncateFileName(&target_path, limit))
149 if (!IsPathInUse(path_to_check)) { 203 name_too_long = true;
150 target_path = path_to_check; 204 }
151 has_conflicts = false; 205
152 break; 206 // Uniquify the name, if it already exists.
207 if (!name_too_long && should_uniquify && IsPathInUse(target_path)) {
208 has_conflicts = true;
209 for (int uniquifier = 1;
210 uniquifier <= DownloadPathReservationTracker::kMaxUniqueFiles;
211 ++uniquifier) {
212 // Append uniquifier. If necessary, truncate the name.
213 std::string suffix(StringPrintf(" (%d)", uniquifier));
asanka 2013/02/19 17:59:15 Nit: Use base::StringPrintf() and include "base/st
kinaba 2013/02/20 13:57:41 Done.
214 base::FilePath path_to_check(target_path);
215 int limit = max_length - kIntermediateNameSuffixLength - suffix.size();
216 if (limit <= 0 || !TruncateFileName(&path_to_check, limit))
217 break;
asanka 2013/02/19 17:59:15 Nit: We could get here if |max_length| is -1 and t
kinaba 2013/02/20 13:57:41 Good catch. Thanks. Explicitly dispatched the |max
218 path_to_check = path_to_check.InsertBeforeExtensionASCII(suffix);
219
220 if (!IsPathInUse(path_to_check)) {
221 target_path = path_to_check;
222 has_conflicts = false;
223 break;
224 }
153 } 225 }
154 } 226 }
155 } 227 }
228
156 reservations[download_id] = target_path; 229 reservations[download_id] = target_path;
157 BrowserThread::PostTask( 230 bool verified = (is_path_writeable && !has_conflicts && !name_too_long);
158 BrowserThread::UI, FROM_HERE, 231 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
159 base::Bind(callback, target_path, (is_path_writeable && !has_conflicts))); 232 base::Bind(callback, target_path, verified));
160 } 233 }
161 234
162 // Called on the FILE thread to update the path of the reservation associated 235 // Called on the FILE thread to update the path of the reservation associated
163 // with |download_id| to |new_path|. 236 // with |download_id| to |new_path|.
164 void UpdateReservation(DownloadId download_id, const base::FilePath& new_path) { 237 void UpdateReservation(DownloadId download_id, const base::FilePath& new_path) {
165 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 238 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
166 DCHECK(g_reservation_map != NULL); 239 DCHECK(g_reservation_map != NULL);
167 ReservationMap::iterator iter = g_reservation_map->find(download_id); 240 ReservationMap::iterator iter = g_reservation_map->find(download_id);
168 if (iter != g_reservation_map->end()) { 241 if (iter != g_reservation_map->end()) {
169 iter->second = new_path; 242 iter->second = new_path;
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after
267 BrowserThread::FILE, FROM_HERE, 340 BrowserThread::FILE, FROM_HERE,
268 base::Bind(&CreateReservation, download_item.GetGlobalId(), 341 base::Bind(&CreateReservation, download_item.GetGlobalId(),
269 target_path, default_path, uniquify_path, callback)); 342 target_path, default_path, uniquify_path, callback));
270 } 343 }
271 344
272 // static 345 // static
273 bool DownloadPathReservationTracker::IsPathInUseForTesting( 346 bool DownloadPathReservationTracker::IsPathInUseForTesting(
274 const base::FilePath& path) { 347 const base::FilePath& path) {
275 return IsPathInUse(path); 348 return IsPathInUse(path);
276 } 349 }
OLDNEW
« no previous file with comments | « base/file_util_win.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698