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
3 // found in the LICENSE file.
5 #include "chrome/browser/chromeos/drive/file_system/copy_operation.h"
9 #include "base/callback_helpers.h"
10 #include "base/file_util.h"
11 #include "base/task_runner_util.h"
12 #include "chrome/browser/chromeos/drive/drive.pb.h"
13 #include "chrome/browser/chromeos/drive/file_cache.h"
14 #include "chrome/browser/chromeos/drive/file_system/create_file_operation.h"
15 #include "chrome/browser/chromeos/drive/file_system/operation_observer.h"
16 #include "chrome/browser/chromeos/drive/file_system_util.h"
17 #include "chrome/browser/chromeos/drive/job_scheduler.h"
18 #include "chrome/browser/chromeos/drive/resource_entry_conversion.h"
19 #include "chrome/browser/chromeos/drive/resource_metadata.h"
20 #include "chrome/browser/drive/drive_api_util.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "google_apis/drive/drive_api_parser.h"
24 using content::BrowserThread
;
27 namespace file_system
{
31 FileError
PrepareCopy(internal::ResourceMetadata
* metadata
,
32 const base::FilePath
& src_path
,
33 const base::FilePath
& dest_path
,
34 ResourceEntry
* src_entry
,
35 std::string
* parent_resource_id
) {
36 FileError error
= metadata
->GetResourceEntryByPath(src_path
, src_entry
);
37 if (error
!= FILE_ERROR_OK
)
40 ResourceEntry parent_entry
;
41 error
= metadata
->GetResourceEntryByPath(dest_path
.DirName(), &parent_entry
);
42 if (error
!= FILE_ERROR_OK
)
45 if (!parent_entry
.file_info().is_directory())
46 return FILE_ERROR_NOT_A_DIRECTORY
;
48 // Drive File System doesn't support recursive copy.
49 if (src_entry
->file_info().is_directory())
50 return FILE_ERROR_NOT_A_FILE
;
52 *parent_resource_id
= parent_entry
.resource_id();
56 int64
GetFileSize(const base::FilePath
& file_path
) {
58 if (!base::GetFileSize(file_path
, &file_size
))
63 // Stores the copied entry and returns its path.
64 FileError
UpdateLocalStateForServerSideCopy(
65 internal::ResourceMetadata
* metadata
,
66 scoped_ptr
<google_apis::ResourceEntry
> resource_entry
,
67 base::FilePath
* file_path
) {
68 DCHECK(resource_entry
);
71 std::string parent_resource_id
;
72 if (!ConvertToResourceEntry(*resource_entry
, &entry
, &parent_resource_id
) ||
73 parent_resource_id
.empty())
74 return FILE_ERROR_NOT_A_FILE
;
76 std::string parent_local_id
;
77 FileError error
= metadata
->GetIdByResourceId(parent_resource_id
,
79 if (error
!= FILE_ERROR_OK
)
81 entry
.set_parent_local_id(parent_local_id
);
84 error
= metadata
->AddEntry(entry
, &local_id
);
85 // Depending on timing, the metadata may have inserted via change list
86 // already. So, FILE_ERROR_EXISTS is not an error.
87 if (error
== FILE_ERROR_EXISTS
)
88 error
= metadata
->GetIdByResourceId(entry
.resource_id(), &local_id
);
90 if (error
== FILE_ERROR_OK
)
91 *file_path
= metadata
->GetFilePath(local_id
);
96 // Stores the file at |local_file_path| to the cache as a content of entry at
97 // |remote_dest_path|, and marks it dirty.
98 FileError
UpdateLocalStateForScheduleTransfer(
99 internal::ResourceMetadata
* metadata
,
100 internal::FileCache
* cache
,
101 const base::FilePath
& local_src_path
,
102 const base::FilePath
& remote_dest_path
,
103 std::string
* local_id
) {
104 FileError error
= metadata
->GetIdByPath(remote_dest_path
, local_id
);
105 if (error
!= FILE_ERROR_OK
)
109 error
= metadata
->GetResourceEntryById(*local_id
, &entry
);
110 if (error
!= FILE_ERROR_OK
)
113 error
= cache
->Store(
114 *local_id
, entry
.file_specific_info().md5(), local_src_path
,
115 internal::FileCache::FILE_OPERATION_COPY
);
116 if (error
!= FILE_ERROR_OK
)
119 scoped_ptr
<base::ScopedClosureRunner
> file_closer
;
120 error
= cache
->OpenForWrite(*local_id
, &file_closer
);
121 if (error
!= FILE_ERROR_OK
)
124 return FILE_ERROR_OK
;
127 // Gets the file size of the |local_path|, and the ResourceEntry for the parent
128 // of |remote_path| to prepare the necessary information for transfer.
129 FileError
PrepareTransferFileFromLocalToRemote(
130 internal::ResourceMetadata
* metadata
,
131 const base::FilePath
& local_src_path
,
132 const base::FilePath
& remote_dest_path
,
133 std::string
* gdoc_resource_id
,
134 std::string
* parent_resource_id
) {
135 ResourceEntry parent_entry
;
136 FileError error
= metadata
->GetResourceEntryByPath(
137 remote_dest_path
.DirName(), &parent_entry
);
138 if (error
!= FILE_ERROR_OK
)
141 // The destination's parent must be a directory.
142 if (!parent_entry
.file_info().is_directory())
143 return FILE_ERROR_NOT_A_DIRECTORY
;
145 // Try to parse GDoc File and extract the resource id, if necessary.
146 // Failing isn't problem. It'd be handled as a regular file, then.
147 if (util::HasGDocFileExtension(local_src_path
)) {
148 *gdoc_resource_id
= util::ReadResourceIdFromGDocFile(local_src_path
);
149 *parent_resource_id
= parent_entry
.resource_id();
152 return FILE_ERROR_OK
;
157 struct CopyOperation::CopyParams
{
158 base::FilePath dest_file_path
;
159 bool preserve_last_modified
;
160 FileOperationCallback callback
;
163 CopyOperation::CopyOperation(base::SequencedTaskRunner
* blocking_task_runner
,
164 OperationObserver
* observer
,
165 JobScheduler
* scheduler
,
166 internal::ResourceMetadata
* metadata
,
167 internal::FileCache
* cache
,
168 const ResourceIdCanonicalizer
& id_canonicalizer
)
169 : blocking_task_runner_(blocking_task_runner
),
171 scheduler_(scheduler
),
174 id_canonicalizer_(id_canonicalizer
),
175 create_file_operation_(new CreateFileOperation(blocking_task_runner
,
180 weak_ptr_factory_(this) {
181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
184 CopyOperation::~CopyOperation() {
185 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
188 void CopyOperation::Copy(const base::FilePath
& src_file_path
,
189 const base::FilePath
& dest_file_path
,
190 bool preserve_last_modified
,
191 const FileOperationCallback
& callback
) {
192 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
193 DCHECK(!callback
.is_null());
196 params
.dest_file_path
= dest_file_path
;
197 params
.preserve_last_modified
= preserve_last_modified
;
198 params
.callback
= callback
;
200 ResourceEntry
* src_entry
= new ResourceEntry
;
201 std::string
* parent_resource_id
= new std::string
;
202 base::PostTaskAndReplyWithResult(
203 blocking_task_runner_
.get(),
205 base::Bind(&PrepareCopy
,
206 metadata_
, src_file_path
, dest_file_path
,
207 src_entry
, parent_resource_id
),
208 base::Bind(&CopyOperation::CopyAfterPrepare
,
209 weak_ptr_factory_
.GetWeakPtr(), params
,
210 base::Owned(src_entry
), base::Owned(parent_resource_id
)));
213 void CopyOperation::CopyAfterPrepare(const CopyParams
& params
,
214 ResourceEntry
* src_entry
,
215 std::string
* parent_resource_id
,
217 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
218 DCHECK(!params
.callback
.is_null());
220 if (error
!= FILE_ERROR_OK
) {
221 params
.callback
.Run(error
);
225 base::FilePath new_title
= params
.dest_file_path
.BaseName();
226 if (src_entry
->file_specific_info().is_hosted_document()) {
227 // Drop the document extension, which should not be in the title.
228 // TODO(yoshiki): Remove this code with crbug.com/223304.
229 new_title
= new_title
.RemoveExtension();
232 base::Time last_modified
=
233 params
.preserve_last_modified
?
234 base::Time::FromInternalValue(src_entry
->file_info().last_modified()) :
237 CopyResourceOnServer(
238 src_entry
->resource_id(), *parent_resource_id
,
239 new_title
.AsUTF8Unsafe(), last_modified
, params
.callback
);
242 void CopyOperation::TransferFileFromLocalToRemote(
243 const base::FilePath
& local_src_path
,
244 const base::FilePath
& remote_dest_path
,
245 const FileOperationCallback
& callback
) {
246 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
247 DCHECK(!callback
.is_null());
249 std::string
* gdoc_resource_id
= new std::string
;
250 std::string
* parent_resource_id
= new std::string
;
251 base::PostTaskAndReplyWithResult(
252 blocking_task_runner_
.get(),
255 &PrepareTransferFileFromLocalToRemote
,
256 metadata_
, local_src_path
, remote_dest_path
,
257 gdoc_resource_id
, parent_resource_id
),
259 &CopyOperation::TransferFileFromLocalToRemoteAfterPrepare
,
260 weak_ptr_factory_
.GetWeakPtr(),
261 local_src_path
, remote_dest_path
, callback
,
262 base::Owned(gdoc_resource_id
), base::Owned(parent_resource_id
)));
265 void CopyOperation::TransferFileFromLocalToRemoteAfterPrepare(
266 const base::FilePath
& local_src_path
,
267 const base::FilePath
& remote_dest_path
,
268 const FileOperationCallback
& callback
,
269 std::string
* gdoc_resource_id
,
270 std::string
* parent_resource_id
,
272 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
273 DCHECK(!callback
.is_null());
275 if (error
!= FILE_ERROR_OK
) {
280 // For regular files, schedule the transfer.
281 if (gdoc_resource_id
->empty()) {
282 ScheduleTransferRegularFile(local_src_path
, remote_dest_path
, callback
);
286 // This is uploading a JSON file representing a hosted document.
287 // Copy the document on the Drive server.
289 // GDoc file may contain a resource ID in the old format.
290 const std::string canonicalized_resource_id
=
291 id_canonicalizer_
.Run(*gdoc_resource_id
);
293 CopyResourceOnServer(
294 canonicalized_resource_id
, *parent_resource_id
,
295 // Drop the document extension, which should not be in the title.
296 // TODO(yoshiki): Remove this code with crbug.com/223304.
297 remote_dest_path
.BaseName().RemoveExtension().AsUTF8Unsafe(),
302 void CopyOperation::CopyResourceOnServer(
303 const std::string
& resource_id
,
304 const std::string
& parent_resource_id
,
305 const std::string
& new_title
,
306 const base::Time
& last_modified
,
307 const FileOperationCallback
& callback
) {
308 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
309 DCHECK(!callback
.is_null());
311 scheduler_
->CopyResource(
312 resource_id
, parent_resource_id
, new_title
, last_modified
,
313 base::Bind(&CopyOperation::CopyResourceOnServerAfterServerSideCopy
,
314 weak_ptr_factory_
.GetWeakPtr(),
318 void CopyOperation::CopyResourceOnServerAfterServerSideCopy(
319 const FileOperationCallback
& callback
,
320 google_apis::GDataErrorCode status
,
321 scoped_ptr
<google_apis::ResourceEntry
> resource_entry
) {
322 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
323 DCHECK(!callback
.is_null());
325 FileError error
= GDataToFileError(status
);
326 if (error
!= FILE_ERROR_OK
) {
331 // The copy on the server side is completed successfully. Update the local
333 base::FilePath
* file_path
= new base::FilePath
;
334 base::PostTaskAndReplyWithResult(
335 blocking_task_runner_
.get(),
337 base::Bind(&UpdateLocalStateForServerSideCopy
,
338 metadata_
, base::Passed(&resource_entry
), file_path
),
339 base::Bind(&CopyOperation::CopyResourceOnServerAfterUpdateLocalState
,
340 weak_ptr_factory_
.GetWeakPtr(),
341 callback
, base::Owned(file_path
)));
344 void CopyOperation::CopyResourceOnServerAfterUpdateLocalState(
345 const FileOperationCallback
& callback
,
346 base::FilePath
* file_path
,
348 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
349 DCHECK(!callback
.is_null());
351 if (error
== FILE_ERROR_OK
)
352 observer_
->OnDirectoryChangedByOperation(file_path
->DirName());
356 void CopyOperation::ScheduleTransferRegularFile(
357 const base::FilePath
& local_src_path
,
358 const base::FilePath
& remote_dest_path
,
359 const FileOperationCallback
& callback
) {
360 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
361 DCHECK(!callback
.is_null());
363 base::PostTaskAndReplyWithResult(
364 blocking_task_runner_
.get(),
366 base::Bind(&GetFileSize
, local_src_path
),
367 base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterGetFileSize
,
368 weak_ptr_factory_
.GetWeakPtr(),
369 local_src_path
, remote_dest_path
, callback
));
372 void CopyOperation::ScheduleTransferRegularFileAfterGetFileSize(
373 const base::FilePath
& local_src_path
,
374 const base::FilePath
& remote_dest_path
,
375 const FileOperationCallback
& callback
,
376 int64 local_file_size
) {
377 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
378 DCHECK(!callback
.is_null());
380 if (local_file_size
< 0) {
381 callback
.Run(FILE_ERROR_NOT_FOUND
);
385 // For regular files, check the server-side quota whether sufficient space
386 // is available for the file to be uploaded.
387 scheduler_
->GetAboutResource(
389 &CopyOperation::ScheduleTransferRegularFileAfterGetAboutResource
,
390 weak_ptr_factory_
.GetWeakPtr(),
391 local_src_path
, remote_dest_path
, callback
, local_file_size
));
394 void CopyOperation::ScheduleTransferRegularFileAfterGetAboutResource(
395 const base::FilePath
& local_src_path
,
396 const base::FilePath
& remote_dest_path
,
397 const FileOperationCallback
& callback
,
398 int64 local_file_size
,
399 google_apis::GDataErrorCode status
,
400 scoped_ptr
<google_apis::AboutResource
> about_resource
) {
401 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
402 DCHECK(!callback
.is_null());
404 FileError error
= GDataToFileError(status
);
405 if (error
!= FILE_ERROR_OK
) {
410 DCHECK(about_resource
);
412 about_resource
->quota_bytes_total() - about_resource
->quota_bytes_used();
413 if (space
< local_file_size
) {
414 callback
.Run(FILE_ERROR_NO_SERVER_SPACE
);
418 create_file_operation_
->CreateFile(
420 true, // Exclusive (i.e. fail if a file already exists).
421 std::string(), // no specific mime type; CreateFile should guess it.
422 base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterCreate
,
423 weak_ptr_factory_
.GetWeakPtr(),
424 local_src_path
, remote_dest_path
, callback
));
427 void CopyOperation::ScheduleTransferRegularFileAfterCreate(
428 const base::FilePath
& local_src_path
,
429 const base::FilePath
& remote_dest_path
,
430 const FileOperationCallback
& callback
,
432 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
433 DCHECK(!callback
.is_null());
435 if (error
!= FILE_ERROR_OK
) {
440 std::string
* local_id
= new std::string
;
441 base::PostTaskAndReplyWithResult(
442 blocking_task_runner_
.get(),
445 &UpdateLocalStateForScheduleTransfer
,
446 metadata_
, cache_
, local_src_path
, remote_dest_path
, local_id
),
448 &CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState
,
449 weak_ptr_factory_
.GetWeakPtr(), callback
, base::Owned(local_id
)));
452 void CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState(
453 const FileOperationCallback
& callback
,
454 std::string
* local_id
,
456 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
457 DCHECK(!callback
.is_null());
459 if (error
== FILE_ERROR_OK
)
460 observer_
->OnCacheFileUploadNeededByOperation(*local_id
);
464 } // namespace file_system