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

Side by Side Diff: third_party/gsutil/gslib/commands/cp.py

Issue 10199002: Upgrade gsutil to 3.4 (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Addressed comments Created 8 years, 8 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
OLDNEW
(Empty)
1 # Copyright 2011 Google Inc.
2 # Copyright 2011, Nexenta Systems Inc.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 import boto
17 import ctypes
18 import errno
19 import gzip
20 import hashlib
21 import mimetypes
22 import os
23 import platform
24 import re
25 import subprocess
26 import sys
27 import tempfile
28 import threading
29 import time
30
31 from boto.gs.resumable_upload_handler import ResumableUploadHandler
32 from boto.s3.resumable_download_handler import ResumableDownloadHandler
33 from gslib.bucket_listing_ref import BucketListingRef
34 from gslib.command import Command
35 from gslib.command import COMMAND_NAME
36 from gslib.command import COMMAND_NAME_ALIASES
37 from gslib.command import CONFIG_REQUIRED
38 from gslib.command import FILE_URIS_OK
39 from gslib.command import MAX_ARGS
40 from gslib.command import MIN_ARGS
41 from gslib.command import PROVIDER_URIS_OK
42 from gslib.command import SUPPORTED_SUB_ARGS
43 from gslib.command import URIS_START_ARG
44 from gslib.exception import CommandException
45 from gslib.help_provider import HELP_NAME
46 from gslib.help_provider import HELP_NAME_ALIASES
47 from gslib.help_provider import HELP_ONE_LINE_SUMMARY
48 from gslib.help_provider import HELP_TEXT
49 from gslib.help_provider import HelpType
50 from gslib.help_provider import HELP_TYPE
51 from gslib.util import MakeHumanReadable
52 from gslib.util import NO_MAX
53 from gslib.util import ONE_MB
54 from gslib.wildcard_iterator import ContainsWildcard
55
56 _detailed_help_text = ("""
57 <B>SYNOPSIS</B>
58 gsutil cp [-a canned_acl] [-e] [-p] [-z ext1,ext2,...] src_uri dst_uri
59 - or -
60 gsutil cp [-a canned_acl] [-e] [-p] [-R] [-z extensions] uri... dst_uri
61
62
63 <B>DESCRIPTION</B>
64 The gsutil cp command allows you to copy data between your local file
65 system and the cloud, copy data within the cloud, and copy data between
66 cloud storage providers. For example, to copy all text files from the
67 local directory to a bucket you could do:
68
69 gsutil cp *.txt gs://my_bucket
70
71 Similarly, you can download text files from a bucket by doing:
72
73 gsutil cp gs://my_bucket/*.txt .
74
75 If you want to copy an entire directory tree you need to use the -R option:
76
77 gsutil cp -R dir gs://my_bucket
78
79 If you have a large number of files to upload you might want to use the
80 gsutil -m option, to perform a parallel (multi-threaded/multi-processing)
81 copy:
82
83 gsutil -m cp -R dir gs://my_bucket
84
85
86 <B>HOW NAMES ARE CONSTRUCTED</B>
87 The gsutil cp command strives to name objects in a way consistent with how
88 UNIX cp works, which causes names to be constructed varying ways depending on
89 whether you're performing a recursive directory copy or copying individually
90 named objects; and whether you're copying to an existing or non-existent
91 directory.
92
93 When performing recursive directory copies, object names are constructed
94 that mirror the source directory structure starting at the point of
95 recursive processing. For example, the command:
96
97 gsutil cp -R dir1/dir2 gs://my_bucket
98
99 will create objects named like gs://my_bucket/dir2/a/b/c, assuming
100 dir1/dir2 contains the file a/b/c.
101
102 In contrast, copying individually named files will result in objects named
103 by the final path component of the source files. For example, the command:
104
105 gsutil cp dir1/dir2/** gs://my_bucket
106
107 will create objects named like gs://my_bucket/c.
108
109 The same rules apply for downloads: recursive copies of buckets and
110 bucket subdirectories produce mirrored filename structure, while copying
111 individually (or wildcard) named objects produce flatly named files.
112
113 Note that in the above example the '**' wildcard matches all names
114 anywhere under dir. The wildcard '*' will match just one level deep
115 names. For more details see 'gsutil help wildcards'.
116
117 There's an additional wrinkle when working with subdirectories: the resulting
118 names depend on whether the destination subdirectory exists. For example,
119 if gs://my_bucket/subdir exists as a subdirectory, the command:
120
121 gsutil cp -R dir1/dir2 gs://my_bucket/subdir
122
123 will create objects named like gs://my_bucket/subdir/dir2/a/b/c. In contrast,
124 if gs://my_bucket/subdir does not exist, this same gsutil cp command will
125 create objects named like gs://my_bucket/subdir/a/b/c.
126
127
128 <B>COPYING TO/FROM SUBDIRECTORIES; DISTRIBUTING TRANSFERS ACROSS MACHINES</B>
129 You can use gsutil to copy to and from subdirectories by using a command like:
130
131 gsutil cp -R dir gs://my_bucket/data
132
133 This will cause dir and all of its files and nested subdirectories to be
134 copied under the specified destination, resulting in objects with names like
135 gs://my_bucket/data/dir/a/b/c. Similarly you can download from bucket
136 subdirectories by using a command like:
137
138 gsutil cp -R gs://my_bucket/data dir
139
140 This will cause everything nested under gs://my_bucket/data dir to be
141 downloaded to files, resulting in files with names like dir/data/a/b/c.
142
143 Copying subdirectories is useful if you want to add data to an existing
144 bucket directory structure over time. It's also useful if you want
145 to parallelize uploads and downloads across multiple machines (often
146 reducing overall transfer time compared with simply running gsutil -m
147 cp on one machine). For example, if your bucket contains this structure:
148
149 gs://my_bucket/data/result_set_01/
150 gs://my_bucket/data/result_set_02/
151 ...
152 gs://my_bucket/data/result_set_99/
153
154 you could perform concurrent downloads across 3 machines by running these
155 commands on each machine, respectively:
156
157 gsutil cp -R gs://my_bucket/data/result_set_[0-3]* dir
158 gsutil cp -R gs://my_bucket/data/result_set_[4-6]* dir
159 gsutil cp -R gs://my_bucket/data/result_set_[7-9]* dir
160
161 Note that dir could be a local directory on each machine, or it could
162 be a directory mounted off of a shared file server; whether the latter
163 performs acceptably may depend on a number of things, so we recommend
164 you experiment and find out what works best for you.
165
166
167 <B>COPYING IN THE CLOUD AND METADATA PRESERVATION</B>
168 If both the source and destination URI are cloud URIs from the same
169 provider, gsutil copies data "in the cloud" (i.e., without downloading
170 to and uploading from the machine where you run gsutil). In addition to
171 the performance and cost advantages of doing this, copying in the cloud
172 preserves metadata (like Content-Type and Cache-Control). In contrast,
173 when you download data from the cloud it ends up in a file, which has
174 no associated metadata. Thus, unless you have some way to hold on to
175 or re-create that metadata, downloading to a file will not retain the
176 metadata.
177
178 Note that by default, the gsutil cp command does not copy the object
179 ACL to the new object, and instead will use the default bucket ACL (see
180 "gsutil help setdefacl"). You can override this behavior with the -p
181 option (see OPTIONS below).
182
183
184 <B>RESUMABLE TRANSFERS</B>
185 gsutil automatically uses the Google Cloud Storage resumable upload
186 feature whenever you use the cp command to upload an object that is larger
187 than 1 MB. You do not need to specify any special command line options
188 to make this happen. If your upload is interrupted you can restart the
189 upload by running the same cp command that you ran to start the upload.
190
191 Similarly, gsutil automatically performs resumable downloads (using HTTP
192 standard Range GET operations) whenever you use the cp command to download an
193 object larger than 1 MB.
194
195 Resumable uploads and downloads store some state information in a file named
196 by the file being uploaded (or object being downloaded) in ~/.gsutil. If you
197 attempt to resume a transfer from a machine with a different directory, the
198 transfer will start over from scratch.
199
200 See also "gsutil help prod" for details on using resumable transfers
201 in production.
202
203
204 <B>STREAMING TRANSFERS</B>
205 Use '-' in place of src_uri or dst_uri to perform a streaming
206 transfer. For example:
207 long_running_computation | gsutil cp - gs://my_bucket/obj
208
209 Streaming transfers do not support resumable uploads/downloads.
210
211
212 <B>OPTIONS</B>
213 -a Sets named canned_acl when uploaded objects created. See
214 'gsutil help acls' for further details.
215
216 -e Exclude symlinks. When specified, symbolic links will not be
217 copied.
218
219 -p Causes ACL to be preserved when copying in the cloud. Note that
220 this option has performance and cost implications, because it
221 is essentially performing three requests (getacl, cp, setacl).
222 (The performance issue can be mitigated to some degree by
223 using gsutil -m cp to cause parallel copying.)
224
225 -R, -r Causes directories, buckets, and bucket subdirectories to be
226 copied recursively. If you neglect to use this option for
227 an upload, gsutil will copy any files it finds and skip any
228 directories. Similarly, neglecting to specify -R for a download
229 will cause gsutil to copy any objects at the current bucket
230 directory level, and skip any subdirectories.
231
232 -t DEPRECATED. This option used to be used to request setting
233 Content-Type based on file extension and/or content, which is
234 now the default behavior. The -t option is left in place for
235 now to avoid breaking existing scripts. It will be removed at
236 a future date.
237
238 -z 'txt,html' Compresses file uploads with the given extensions.
239 If you are uploading a large file with compressible content,
240 such as a .js, .css, or .html file, you can gzip-compress the
241 file during the upload process by specifying the -z <extensions>
242 option. Compressing data before upload saves on usage charges
243 because you are uploading a smaller amount of data.
244
245 When you specify the -z option, the data from your files is
246 compressed before it is uploaded, but your actual files are left
247 uncompressed on the local disk. The uploaded objects retain the
248 original content type and name as the original files but are given
249 a Content-Encoding header with the value "gzip" to indicate that
250 the object data stored compressed on the Google Cloud Storage
251 servers.
252
253 The -z option is most useful in combination with Content-Type
254 recognition (see "gsutil help metadata"). For example, the
255 following command:
256
257 gsutil cp -z html -a public-read cattypes.html gs://mycats
258
259 will do all of the following:
260 - Upload as the object gs://mycats/cattypes.html (cp command)
261 - Set the Content-Type to text/html (based on file extension)
262 - Compress the data in the file cattypes.html (-z option)
263 - Set the Content-Encoding to gzip (-z option)
264 - Set the ACL to public-read (-a option)
265 - If a user tries to view cattypes.html in a browser, the
266 browser will know to uncompress the data based on the
267 Content-Encoding header, and to render it as HTML based on
268 the Content-Type header.
269 """)
270
271
272 class CpCommand(Command):
273 """Implementation of gsutil cp command."""
274
275 # Set default Content-Type type.
276 DEFAULT_CONTENT_TYPE = 'application/octet-stream'
277 USE_MAGICFILE = boto.config.getbool('GSUtil', 'use_magicfile', False)
278
279 # Command specification (processed by parent class).
280 command_spec = {
281 # Name of command.
282 COMMAND_NAME : 'cp',
283 # List of command name aliases.
284 COMMAND_NAME_ALIASES : ['copy'],
285 # Min number of args required by this command.
286 MIN_ARGS : 2,
287 # Max number of args required by this command, or NO_MAX.
288 MAX_ARGS : NO_MAX,
289 # Getopt-style string specifying acceptable sub args.
290 # -t is deprecated but leave intact for now to avoid breakage.
291 SUPPORTED_SUB_ARGS : 'a:eMprRtz:',
292 # True if file URIs acceptable for this command.
293 FILE_URIS_OK : True,
294 # True if provider-only URIs acceptable for this command.
295 PROVIDER_URIS_OK : False,
296 # Index in args of first URI arg.
297 URIS_START_ARG : 0,
298 # True if must configure gsutil before running command.
299 CONFIG_REQUIRED : True,
300 }
301 help_spec = {
302 # Name of command or auxiliary help info for which this help applies.
303 HELP_NAME : 'cp',
304 # List of help name aliases.
305 HELP_NAME_ALIASES : ['copy'],
306 # Type of help:
307 HELP_TYPE : HelpType.COMMAND_HELP,
308 # One line summary of this help.
309 HELP_ONE_LINE_SUMMARY : 'Copy files and objects',
310 # The full help text.
311 HELP_TEXT : _detailed_help_text,
312 }
313
314 def _CheckFinalMd5(self, key, file_name):
315 """
316 Checks that etag from server agrees with md5 computed after the
317 download completes. This is important, since the download could
318 have spanned a number of hours and multiple processes (e.g.,
319 gsutil runs), and the user could change some of the file and not
320 realize they have inconsistent data.
321 """
322 # Open file in binary mode to avoid surprises in Windows.
323 fp = open(file_name, 'rb')
324 try:
325 file_md5 = key.compute_md5(fp)[0]
326 finally:
327 fp.close()
328 obj_md5 = key.etag.strip('"\'')
329 if self.debug:
330 print 'Checking file md5 against etag. (%s/%s)' % (file_md5, obj_md5)
331 if file_md5 != obj_md5:
332 # Checksums don't match - remove file and raise exception.
333 os.unlink(file_name)
334 raise CommandException(
335 'File changed during download: md5 signature doesn\'t match '
336 'etag (incorrect downloaded file deleted)')
337
338 def _CheckForDirFileConflict(self, exp_src_uri, dst_uri):
339 """Checks whether copying exp_src_uri into dst_uri is not possible.
340
341 This happens if a directory exists in local file system where a file
342 needs to go or vice versa. In that case we print an error message and
343 exits. Example: if the file "./x" exists and you try to do:
344 gsutil cp gs://mybucket/x/y .
345 the request can't succeed because it requires a directory where
346 the file x exists.
347
348 Note that we don't enforce any corresponding restrictions for buckets,
349 because the flat namespace semantics for buckets doesn't prohibit such
350 cases the way hierarchical file systems do. For example, if a bucket
351 contains an object called gs://bucket/dir and then you run the command:
352 gsutil cp file1 file2 gs://bucket/dir
353 you'll end up with objects gs://bucket/dir, gs://bucket/dir/file1, and
354 gs://bucket/dir/file2.
355
356 Args:
357 exp_src_uri: Expanded source StorageUri of copy.
358 dst_uri: Destination URI.
359
360 Raises:
361 CommandException: if errors encountered.
362 """
363 if dst_uri.is_cloud_uri():
364 # The problem can only happen for file destination URIs.
365 return
366 dst_path = dst_uri.object_name
367 final_dir = os.path.dirname(dst_path)
368 if os.path.isfile(final_dir):
369 raise CommandException('Cannot retrieve %s because a file exists '
370 'where a directory needs to be created (%s).' %
371 (exp_src_uri, final_dir))
372 if os.path.isdir(dst_path):
373 raise CommandException('Cannot retrieve %s because a directory exists '
374 '(%s) where the file needs to be created.' %
375 (exp_src_uri, dst_path))
376
377 def _InsistDstUriNamesContainer(self, exp_dst_uri,
378 have_existing_dst_container, command_name):
379 """
380 Raises an exception if URI doesn't name a directory, bucket, or bucket
381 subdir, with special exception for cp -R (see comments below).
382
383 Args:
384 exp_dst_uri: Wildcard-expanding dst_uri.
385 have_existing_dst_container: bool indicator of whether exp_dst_uri
386 names a container (directory, bucket, or bucket subdir).
387 command_name: Name of command making call. May not be the same as
388 self.command_name in the case of commands implemented atop other
389 commands (like mv command).
390
391 Raises:
392 CommandException: if the URI being checked does not name a container.
393 """
394 if exp_dst_uri.is_file_uri():
395 ok = exp_dst_uri.names_directory()
396 else:
397 if have_existing_dst_container:
398 ok = True
399 else:
400 # It's ok to specify a non-existing bucket subdir if this is a
401 # recursive copy, such as:
402 # gsutil cp -R dir gs://bucket/abc
403 # where there is no existing subdir gs://bucket/abc.
404 ok = self.recursion_requested and exp_dst_uri.names_object()
405 if not ok:
406 raise CommandException('Destination URI must name a directory, bucket, '
407 'or bucket\nsubdirectory for the multiple '
408 'source form of the %s command.' % command_name)
409
410 class _FileCopyCallbackHandler(object):
411 """Outputs progress info for large copy requests."""
412
413 def __init__(self, upload):
414 if upload:
415 self.announce_text = 'Uploading'
416 else:
417 self.announce_text = 'Downloading'
418
419 def call(self, total_bytes_transferred, total_size):
420 sys.stderr.write('%s: %s/%s \r' % (
421 self.announce_text,
422 MakeHumanReadable(total_bytes_transferred),
423 MakeHumanReadable(total_size)))
424 if total_bytes_transferred == total_size:
425 sys.stderr.write('\n')
426
427 class _StreamCopyCallbackHandler(object):
428 """Outputs progress info for Stream copy to cloud.
429 Total Size of the stream is not known, so we output
430 only the bytes transferred.
431 """
432
433 def call(self, total_bytes_transferred, total_size):
434 sys.stderr.write('Uploading: %s \r' % (
435 MakeHumanReadable(total_bytes_transferred)))
436 if total_size and total_bytes_transferred == total_size:
437 sys.stderr.write('\n')
438
439 def _GetTransferHandlers(self, uri, key, file_size, upload):
440 """
441 Selects upload/download and callback handlers.
442
443 We use a callback handler that shows a simple textual progress indicator
444 if file_size is above the configurable threshold.
445
446 We use a resumable transfer handler if file_size is >= the configurable
447 threshold and resumable transfers are supported by the given provider.
448 boto supports resumable downloads for all providers, but resumable
449 uploads are currently only supported by GS.
450 """
451 config = boto.config
452 resumable_threshold = config.getint('GSUtil', 'resumable_threshold', ONE_MB)
453 if file_size >= resumable_threshold:
454 cb = self._FileCopyCallbackHandler(upload).call
455 num_cb = int(file_size / ONE_MB)
456 resumable_tracker_dir = config.get(
457 'GSUtil', 'resumable_tracker_dir',
458 os.path.expanduser('~' + os.sep + '.gsutil'))
459 if not os.path.exists(resumable_tracker_dir):
460 os.makedirs(resumable_tracker_dir)
461 if upload:
462 # Encode the src bucket and key into the tracker file name.
463 res_tracker_file_name = (
464 re.sub('[/\\\\]', '_', 'resumable_upload__%s__%s.url' %
465 (key.bucket.name, key.name)))
466 else:
467 # Encode the fully-qualified src file name into the tracker file name.
468 res_tracker_file_name = (
469 re.sub('[/\\\\]', '_', 'resumable_download__%s.etag' %
470 (os.path.realpath(uri.object_name))))
471
472 res_tracker_file_name = _hash_filename(res_tracker_file_name)
473 tracker_file = '%s%s%s' % (resumable_tracker_dir, os.sep,
474 res_tracker_file_name)
475 if upload:
476 if uri.scheme == 'gs':
477 transfer_handler = ResumableUploadHandler(tracker_file)
478 else:
479 transfer_handler = None
480 else:
481 transfer_handler = ResumableDownloadHandler(tracker_file)
482 else:
483 transfer_handler = None
484 cb = None
485 num_cb = None
486 return (cb, num_cb, transfer_handler)
487
488 # We pass the headers explicitly to this call instead of using self.headers
489 # so we can set different metadata (like Content-Type type) for each object.
490 def _CopyObjToObjSameProvider(self, src_key, src_uri, dst_uri, headers):
491 # Do Object -> object copy within same provider (uses
492 # x-<provider>-copy-source metadata HTTP header to request copying at the
493 # server).
494 src_bucket = src_uri.get_bucket(False, headers)
495 dst_bucket = dst_uri.get_bucket(False, headers)
496 preserve_acl = False
497 if self.sub_opts:
498 for o, a in self.sub_opts:
499 if o == '-p':
500 preserve_acl = True
501 start_time = time.time()
502 # Pass headers in headers param not metadata param, so boto will copy
503 # existing key's metadata and just set the additional headers specified
504 # in the headers param (rather than using the headers to override existing
505 # metadata). In particular this allows us to copy the existing key's
506 # Content-Type and other metadata users need while still being able to
507 # set headers the API needs (like x-goog-project-id).
508 dst_bucket.copy_key(dst_uri.object_name, src_bucket.name,
509 src_uri.object_name, preserve_acl=preserve_acl,
510 headers=headers)
511 end_time = time.time()
512 return (end_time - start_time, src_key.size)
513
514 def _CheckFreeSpace(self, path):
515 """Return path/drive free space (in bytes)."""
516 if platform.system() == 'Windows':
517 free_bytes = ctypes.c_ulonglong(0)
518 ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(path), None,
519 None,
520 ctypes.pointer(free_bytes))
521 return free_bytes.value
522 else:
523 (_, f_frsize, _, _, f_bavail, _, _, _, _, _) = os.statvfs(path)
524 return f_frsize * f_bavail
525
526 def _PerformResumableUploadIfApplies(self, fp, dst_uri, canned_acl, headers):
527 """
528 Performs resumable upload if supported by provider and file is above
529 threshold, else performs non-resumable upload.
530
531 Returns (elapsed_time, bytes_transferred).
532 """
533 start_time = time.time()
534 file_size = os.path.getsize(fp.name)
535 dst_key = dst_uri.new_key(False, headers)
536 (cb, num_cb, res_upload_handler) = self._GetTransferHandlers(
537 dst_uri, dst_key, file_size, True)
538 if dst_uri.scheme == 'gs':
539 # Resumable upload protocol is Google Cloud Storage-specific.
540 dst_key.set_contents_from_file(fp, headers, policy=canned_acl,
541 cb=cb, num_cb=num_cb,
542 res_upload_handler=res_upload_handler)
543 else:
544 dst_key.set_contents_from_file(fp, headers, policy=canned_acl,
545 cb=cb, num_cb=num_cb)
546 if res_upload_handler:
547 bytes_transferred = file_size - res_upload_handler.upload_start_point
548 else:
549 bytes_transferred = file_size
550 end_time = time.time()
551 return (end_time - start_time, bytes_transferred)
552
553 def _PerformStreamUpload(self, fp, dst_uri, headers, canned_acl=None):
554 """
555 Performs Stream upload to cloud.
556
557 Args:
558 fp: The file whose contents to upload.
559 dst_uri: Destination StorageUri.
560 headers: A copy of the headers dictionary.
561 canned_acl: Optional canned ACL to set on the object.
562
563 Returns (elapsed_time, bytes_transferred).
564 """
565 start_time = time.time()
566 dst_key = dst_uri.new_key(False, headers)
567
568 cb = self._StreamCopyCallbackHandler().call
569 dst_key.set_contents_from_stream(fp, headers, policy=canned_acl, cb=cb)
570 try:
571 bytes_transferred = fp.tell()
572 except:
573 bytes_transferred = 0
574
575 end_time = time.time()
576 return (end_time - start_time, bytes_transferred)
577
578 def _GetContentType(self, object_name):
579 # Streams (denoted by '-') are expected to be 'application/octet-stream'
580 # and 'file' would partially consume them.
581 if not object_name == '-':
582 if self.USE_MAGICFILE:
583 p = subprocess.Popen(['file', '--mime-type', object_name],
584 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
585 output, error = p.communicate()
586 if p.returncode != 0 or error:
587 raise CommandException(
588 'Encountered error running "file --mime-type %s" (returncode=%d).'
589 '\n%s' % (object_name, p.returncode, error))
590 # Parse output by removing line delimiter and splitting on last ": ".
591 mime_type = output.rstrip().rpartition(': ')[2]
592 if mime_type:
593 return mime_type
594 else:
595 return mimetypes.guess_type(object_name)[0]
596 return self.DEFAULT_CONTENT_TYPE
597
598 def _UploadFileToObject(self, src_key, src_uri, dst_uri, headers):
599 """Helper method for uploading a local file to an object.
600
601 Args:
602 src_key: Source StorageUri. Must be a file URI.
603 src_uri: Source StorageUri.
604 dst_uri: Destination StorageUri.
605 headers: The headers dictionary.
606 Returns:
607 (elapsed_time, bytes_transferred) excluding overhead like initial HEAD.
608
609 Raises:
610 CommandException: if errors encountered.
611 """
612 gzip_exts = []
613 canned_acl = None
614 # Previously, the -t option was used to request automatic content
615 # type detection, however, whether -t was specified for not, content
616 # detection was being done. To repair this problem while preserving
617 # backward compatibilty, the -t option has been deprecated and content
618 # type detection is now enabled by default unless the Content-Type
619 # header is explicitly specified via the -h option.
620 if self.sub_opts:
621 for o, a in self.sub_opts:
622 if o == '-a':
623 canned_acls = dst_uri.canned_acls()
624 if a not in canned_acls:
625 raise CommandException('Invalid canned ACL "%s".' % a)
626 canned_acl = a
627 elif o == '-t':
628 print 'Warning: -t is deprecated. Content type detection is ' + (
629 'enabled by default,\nunless inhibited by specifying ') + (
630 'a Content-Type header via the -h option.')
631 elif o == '-z':
632 gzip_exts = a.split(',')
633
634 if 'Content-Type' in headers:
635 # Process Content-Type header. If specified via -h option with empty
636 # string (i.e. -h "Content-Type:") set header to None, which will
637 # inhibit boto from sending the CT header. Otherwise, boto will pass
638 # through the user specified CT header.
639 if not headers['Content-Type']:
640 headers['Content-Type'] = None
641 else:
642 # If no CT header was specified via the -h option, we do auto-content
643 # detection and use the results to formulate the Content-Type.
644 mime_type = self._GetContentType(src_uri.object_name)
645 if mime_type:
646 headers['Content-Type'] = mime_type
647 print '\t[Setting Content-Type=%s]' % mime_type
648 else:
649 print '\t[Unknown content type -> using %s]' % self.DEFAULT_CONTENT_TYPE
650
651 fname_parts = src_uri.object_name.split('.')
652 if len(fname_parts) > 1 and fname_parts[-1] in gzip_exts:
653 if self.debug:
654 print 'Compressing %s (to tmp)...' % src_key
655 gzip_tmp = tempfile.mkstemp()
656 gzip_path = gzip_tmp[1]
657 # Check for temp space. Assume the compressed object is at most 2x
658 # the size of the object (normally should compress to smaller than
659 # the object)
660 if self._CheckFreeSpace(gzip_path) < 2*int(os.path.getsize(src_key.name)):
661 raise CommandException('Inadequate temp space available to compress '
662 '%s' % src_key.name)
663 gzip_fp = gzip.open(gzip_path, 'wb')
664 try:
665 gzip_fp.writelines(src_key.fp)
666 finally:
667 gzip_fp.close()
668 headers['Content-Encoding'] = 'gzip'
669 gzip_fp = open(gzip_path, 'rb')
670 try:
671 (elapsed_time, bytes_transferred) = (
672 self._PerformResumableUploadIfApplies(gzip_fp, dst_uri,
673 canned_acl, headers))
674 finally:
675 gzip_fp.close()
676 os.unlink(gzip_path)
677 elif (src_key.is_stream()
678 and dst_uri.get_provider().supports_chunked_transfer()):
679 (elapsed_time, bytes_transferred) = self._PerformStreamUpload(
680 src_key.fp, dst_uri, headers, canned_acl)
681 else:
682 if src_key.is_stream():
683 # For Providers that doesn't support chunked Transfers
684 tmp = tempfile.NamedTemporaryFile()
685 file_uri = self.suri_builder.StorageUri('file://%s' % tmp.name)
686 try:
687 file_uri.new_key(False, headers).set_contents_from_file(
688 src_key.fp, headers)
689 src_key = file_uri.get_key()
690 finally:
691 file_uri.close()
692 try:
693 (elapsed_time, bytes_transferred) = (
694 self._PerformResumableUploadIfApplies(src_key.fp, dst_uri,
695 canned_acl, headers))
696 finally:
697 if src_key.is_stream():
698 tmp.close()
699 else:
700 src_key.close()
701
702 return (elapsed_time, bytes_transferred)
703
704 def _DownloadObjectToFile(self, src_key, src_uri, dst_uri, headers):
705 (cb, num_cb, res_download_handler) = self._GetTransferHandlers(
706 src_uri, src_key, src_key.size, False)
707 file_name = dst_uri.object_name
708 dir_name = os.path.dirname(file_name)
709 if dir_name and not os.path.exists(dir_name):
710 # Do dir creation in try block so can ignore case where dir already
711 # exists. This is needed to avoid a race condition when running gsutil
712 # -m cp.
713 try:
714 os.makedirs(dir_name)
715 except OSError, e:
716 if e.errno != errno.EEXIST:
717 raise
718 # For gzipped objects not named *.gz download to a temp file and unzip.
719 if (hasattr(src_key, 'content_encoding')
720 and src_key.content_encoding == 'gzip'
721 and not file_name.endswith('.gz')):
722 # We can't use tempfile.mkstemp() here because we need a predictable
723 # filename for resumable downloads.
724 download_file_name = '%s_.gztmp' % file_name
725 need_to_unzip = True
726 else:
727 download_file_name = file_name
728 need_to_unzip = False
729 fp = None
730 try:
731 if res_download_handler:
732 fp = open(download_file_name, 'ab')
733 else:
734 fp = open(download_file_name, 'wb')
735 start_time = time.time()
736 src_key.get_contents_to_file(fp, headers, cb=cb, num_cb=num_cb,
737 res_download_handler=res_download_handler)
738 # If a custom test method is defined, call it here. For the copy command,
739 # test methods are expected to take one argument: an open file pointer,
740 # and are used to perturb the open file during download to exercise
741 # download error detection.
742 if self.test_method:
743 self.test_method(fp)
744 end_time = time.time()
745 finally:
746 if fp:
747 fp.close()
748
749 # Verify downloaded file checksum matched source object's checksum.
750 self._CheckFinalMd5(src_key, download_file_name)
751
752 if res_download_handler:
753 bytes_transferred = (
754 src_key.size - res_download_handler.download_start_point)
755 else:
756 bytes_transferred = src_key.size
757 if need_to_unzip:
758 if self.debug:
759 sys.stderr.write('Uncompressing tmp to %s...\n' % file_name)
760 # Downloaded gzipped file to a filename w/o .gz extension, so unzip.
761 f_in = gzip.open(download_file_name, 'rb')
762 f_out = open(file_name, 'wb')
763 try:
764 f_out.writelines(f_in)
765 finally:
766 f_out.close()
767 f_in.close()
768 os.unlink(download_file_name)
769 return (end_time - start_time, bytes_transferred)
770
771 def _PerformDownloadToStream(self, src_key, src_uri, str_fp, headers):
772 (cb, num_cb, res_download_handler) = self._GetTransferHandlers(
773 src_uri, src_key, src_key.size, False)
774 start_time = time.time()
775 src_key.get_contents_to_file(str_fp, headers, cb=cb, num_cb=num_cb)
776 end_time = time.time()
777 bytes_transferred = src_key.size
778 end_time = time.time()
779 return (end_time - start_time, bytes_transferred)
780
781 def _CopyFileToFile(self, src_key, dst_uri, headers):
782 dst_key = dst_uri.new_key(False, headers)
783 start_time = time.time()
784 dst_key.set_contents_from_file(src_key.fp, headers)
785 end_time = time.time()
786 return (end_time - start_time, os.path.getsize(src_key.fp.name))
787
788 def _CopyObjToObjDiffProvider(self, src_key, src_uri, dst_uri, headers):
789 # If destination is GS, We can avoid the local copying through a local file
790 # as GS supports chunked transfer.
791 if dst_uri.scheme == 'gs':
792 canned_acls = None
793 if self.sub_opts:
794 for o, a in self.sub_opts:
795 if o == '-a':
796 canned_acls = dst_uri.canned_acls()
797 if a not in canned_acls:
798 raise CommandException('Invalid canned ACL "%s".' % a)
799 canned_acl = a
800 elif o == '-p':
801 # We don't attempt to preserve ACLs across providers because
802 # GCS and S3 support different ACLs.
803 raise NotImplementedError('Cross-provider cp -p not supported')
804 elif o == '-t':
805 mime_type = self._GetContentType(src_uri.object_name)
806 if mime_type:
807 headers['Content-Type'] = mime_type
808 print '\t[Setting Content-Type=%s]' % mime_type
809 else:
810 print '\t[Unknown content type -> using application/octet stream]'
811
812 # TODO: This _PerformStreamUpload call passes in a Key for fp
813 # param, relying on Python "duck typing" (the fact that the lower-level
814 # methods that expect an fp only happen to call fp methods that are
815 # defined and semantically equivalent to those defined on src_key). This
816 # should be replaced by a class that wraps an fp interface around the
817 # Key, throwing 'not implemented' for methods (like seek) that aren't
818 # implemented by non-file Keys.
819 return self._PerformStreamUpload(src_key, dst_uri, headers, canned_acls)
820
821 # If destination is not GS, We implement object copy through a local
822 # temp file. Note that a downside of this approach is that killing the
823 # gsutil process partway through and then restarting will always repeat the
824 # download and upload, because the temp file name is different for each
825 # incarnation. (If however you just leave the process running and failures
826 # happen along the way, they will continue to restart and make progress
827 # as long as not too many failures happen in a row with no progress.)
828 tmp = tempfile.NamedTemporaryFile()
829 if self._CheckFreeSpace(tempfile.tempdir) < src_key.size:
830 raise CommandException('Inadequate temp space available to perform the '
831 'requested copy')
832 start_time = time.time()
833 file_uri = self.suri_builder.StorageUri('file://%s' % tmp.name)
834 try:
835 self._DownloadObjectToFile(src_key, src_uri, file_uri, headers)
836 self._UploadFileToObject(file_uri.get_key(), file_uri, dst_uri, headers)
837 finally:
838 tmp.close()
839 end_time = time.time()
840 return (end_time - start_time, src_key.size)
841
842 def _PerformCopy(self, src_uri, dst_uri):
843 """Performs copy from src_uri to dst_uri, handling various special cases.
844
845 Args:
846 src_uri: Source StorageUri.
847 dst_uri: Destination StorageUri.
848
849 Returns:
850 (elapsed_time, bytes_transferred) excluding overhead like initial HEAD.
851
852 Raises:
853 CommandException: if errors encountered.
854 """
855 # Make a copy of the input headers each time so we can set a different
856 # MIME type for each object.
857 if self.headers:
858 headers = self.headers.copy()
859 else:
860 headers = {}
861
862 src_key = src_uri.get_key(False, headers)
863 if not src_key:
864 raise CommandException('"%s" does not exist.' % src_uri)
865
866 # Separately handle cases to avoid extra file and network copying of
867 # potentially very large files/objects.
868
869 if src_uri.is_cloud_uri() and dst_uri.is_cloud_uri():
870 if src_uri.scheme == dst_uri.scheme:
871 return self._CopyObjToObjSameProvider(src_key, src_uri, dst_uri,
872 headers)
873 else:
874 return self._CopyObjToObjDiffProvider(src_key, src_uri, dst_uri,
875 headers)
876 elif src_uri.is_file_uri() and dst_uri.is_cloud_uri():
877 return self._UploadFileToObject(src_key, src_uri, dst_uri, headers)
878 elif src_uri.is_cloud_uri() and dst_uri.is_file_uri():
879 return self._DownloadObjectToFile(src_key, src_uri, dst_uri, headers)
880 elif src_uri.is_file_uri() and dst_uri.is_file_uri():
881 return self._CopyFileToFile(src_key, dst_uri, headers)
882 else:
883 raise CommandException('Unexpected src/dest case')
884
885 def _ExpandDstUri(self, src_uri_expansion, dst_uri_str):
886 """
887 Expands wildcard if present in dst_uri_str.
888
889 Args:
890 src_uri_expansion: gslib.name_expansion.NameExpansionResult.
891 dst_uri_str: String representation of requested dst_uri.
892
893 Returns:
894 (exp_dst_uri, have_existing_dst_container)
895 where have_existing_dst_container is a bool indicating whether
896 exp_dst_uri names an existing directory, bucket, or bucket subdirectory.
897
898 Raises:
899 CommandException: if dst_uri_str matched more than 1 URI.
900 """
901 dst_uri = self.suri_builder.StorageUri(dst_uri_str)
902
903 # Handle wildcarded dst_uri case.
904 if ContainsWildcard(dst_uri):
905 blr_expansion = list(self.exp_handler.WildcardIterator(dst_uri))
906 if len(blr_expansion) != 1:
907 raise CommandException('Destination (%s) must match exactly 1 URI' %
908 dst_uri_str)
909 blr = blr_expansion[0]
910 uri = blr.GetUri()
911 if uri.is_cloud_uri():
912 return (uri, uri.names_bucket() or blr.HasPrefix())
913 else:
914 return (uri, uri.names_directory())
915
916 # Handle non-wildcarded dst_uri:
917 if dst_uri.is_file_uri():
918 return (dst_uri, dst_uri.names_directory())
919 if dst_uri.names_bucket():
920 return (dst_uri, True)
921 # For object URIs we need to do a wildcard expansion with
922 # dst_uri + "*" and then find if there's a Prefix matching dst_uri.
923 blr_expansion = list(self.exp_handler.WildcardIterator(
924 '%s*' % dst_uri_str.rstrip(dst_uri.delim)))
925 for blr in blr_expansion:
926 if (blr.GetRStrippedUriString() == dst_uri_str.rstrip(dst_uri.delim)):
927 return (dst_uri, blr.HasPrefix())
928 return (dst_uri, False)
929
930 def _ConstructDstUri(self, src_uri, exp_src_uri,
931 src_uri_names_container, src_uri_expands_to_multi,
932 have_multiple_srcs, exp_dst_uri,
933 have_existing_dest_subdir):
934 """
935 Constructs the destination URI for a given exp_src_uri/exp_dst_uri pair,
936 using context-dependent naming rules intended to mimic UNIX cp semantics.
937
938 Args:
939 src_uri: src_uri to be copied.
940 exp_src_uri: Single StorageUri from wildcard expansion of src_uri.
941 src_uri_names_container: True if src_uri names a container (including the
942 case of a wildcard-named bucket subdir (like gs://bucket/abc,
943 where gs://bucket/abc/* matched some objects). Note that this is
944 additional semantics tha src_uri.names_container() doesn't understand
945 because the latter only understands StorageUris, not wildcards.
946 src_uri_expands_to_multi: True if src_uri expanded to multiple URIs.
947 have_multiple_srcs: True if this is a multi-source request. This can be
948 true if src_uri wildcard-expanded to multiple URIs or if there were
949 multiple source URIs in the request.
950 exp_dst_uri: the expanded StorageUri requested for the cp destination.
951 Final written path is constructed from this plus a context-dependent
952 variant of src_uri.
953 have_existing_dest_subdir: bool indicator whether dest is an existing
954 subdirectory.
955
956 Returns:
957 StorageUri to use for copy.
958
959 Raises:
960 CommandException if destination object name not specified for
961 source and source is a stream.
962 """
963 if self._ShouldTreatDstUriAsSingleton(
964 have_multiple_srcs, have_existing_dest_subdir, exp_dst_uri):
965 # We're copying one file or object to one file or object.
966 return exp_dst_uri
967
968 if not self.recursion_requested and not have_multiple_srcs:
969 # We're copying one file or object to a subdirectory. Append final comp
970 # of exp_src_uri to exp_dest_uri.
971 src_final_comp = exp_src_uri.object_name.rpartition(src_uri.delim)[-1]
972 return self.suri_builder.StorageUri('%s%s%s' % (
973 exp_dst_uri.uri.rstrip(exp_dst_uri.delim), exp_dst_uri.delim,
974 src_final_comp))
975
976 # Else we're copying multiple sources to a directory, bucket, or a bucket
977 # "sub-directory".
978
979 # Ensure exp_dst_uri ends in delim char if we're doing a multi-src copy or
980 # a copy to a directory. (The check for copying to a directory needs
981 # special-case handling so that the command:
982 # gsutil cp gs://bucket/obj dir
983 # will turn into file://dir/ instead of file://dir -- the latter would cause
984 # the file "dirobj" to be created.)
985 # Note: need to check have_multiple_srcs or src_uri.names_container()
986 # because src_uri could be a bucket containing a single object, named
987 # as gs://bucket.
988 if ((have_multiple_srcs or src_uri.names_container()
989 or os.path.isdir(exp_dst_uri.object_name))
990 and not exp_dst_uri.uri.endswith(exp_dst_uri.delim)):
991 exp_dst_uri = exp_dst_uri.clone_replace_name(
992 '%s%s' % (exp_dst_uri.object_name, exp_dst_uri.delim)
993 )
994
995 # There are 3 cases for copying multiple sources to a dir/bucket/bucket
996 # subdir needed to match the naming semantics of the UNIX cp command:
997 # 1. For the "mv -R" command, people expect renaming to occur at the
998 # level of the src subdir, vs appending that subdir beneath
999 # the dst subdir like is done for copying. For example:
1000 # gsutil -m rm -R gs://bucket
1001 # gsutil -m cp -R cloudreader gs://bucket
1002 # gsutil -m cp -R cloudauth gs://bucket/subdir1
1003 # gsutil -m mv -R gs://bucket/subdir1 gs://bucket/subdir2
1004 # would (if using cp semantics) end up with paths like:
1005 # gs://bucket/subdir2/subdir1/cloudauth/.svn/all-wcprops
1006 # whereas people expect:
1007 # gs://bucket/subdir2/cloudauth/.svn/all-wcprops
1008 # 2. Copying from directories, buckets, or bucket subdirs should result in
1009 # objects/files mirroring the source directory hierarchy. For example:
1010 # gsutil cp dir1/dir2 gs://bucket
1011 # should create the object gs://bucket/dir2/file2, assuming dir1/dir2
1012 # contains file2).
1013 # To be consistent with UNIX cp behavior, there's one more wrinkle when
1014 # working with subdirs: The resulting object names depend on whether the
1015 # destination subdirectory exists. For example, if gs://bucket/subdir
1016 # exists, the command:
1017 # gsutil cp -R dir1/dir2 gs://bucket/subdir
1018 # should create objects named like gs://bucket/subdir/dir2/a/b/c. In
1019 # contrast, if gs://bucket/subdir does not exist, this same command
1020 # should create objects named like gs://bucket/subdir/a/b/c.
1021 # 3. Copying individual files or objects to dirs, buckets or bucket subdirs
1022 # should result in objects/files named by the final source file name
1023 # component. Example:
1024 # gsutil cp dir1/*.txt gs://bucket
1025 # should create the objects gs://bucket/f1.txt and gs://bucket/f2.txt,
1026 # assuming dir1 contains f1.txt and f2.txt.
1027
1028 if (self.mv_naming_semantics and self.recursion_requested
1029 and src_uri_expands_to_multi):
1030 # Case 1. Handle naming semantics for recursive bucket subdir mv.
1031 # Here we want to line up the src_uri against its expansion, to find
1032 # the base to build the new name. For example, starting with:
1033 # gsutil mv -R gs://bucket/abcd gs://bucket/xyz
1034 # and exp_src_uri being gs://bucket/abcd/123
1035 # we want exp_src_uri_tail to be /123
1036 # Note: mv.py code disallows wildcard specification of source URI.
1037 exp_src_uri_tail = exp_src_uri.uri[len(src_uri.uri):]
1038 dst_key_name = '%s/%s' % (exp_dst_uri.object_name.rstrip('/'),
1039 exp_src_uri_tail.strip('/'))
1040 return exp_dst_uri.clone_replace_name(dst_key_name)
1041
1042 if src_uri_names_container and not exp_dst_uri.names_file():
1043 # Case 2. Build dst_key_name from subpath of exp_src_uri past
1044 # where src_uri ends. For example, for src_uri=gs://bucket/ and
1045 # exp_src_uri=gs://bucket/src_subdir/obj, dst_key_name should be
1046 # src_subdir/obj.
1047 src_uri_path_sans_final_dir = _GetPathBeforeFinalDir(src_uri)
1048 dst_key_name = exp_src_uri.uri[
1049 len(src_uri_path_sans_final_dir):].lstrip(src_uri.delim)
1050 # Handle case where dst_uri is a non-existent subdir.
1051 if not have_existing_dest_subdir:
1052 dst_key_name = dst_key_name.partition(exp_dst_uri.delim)[-1]
1053 # Handle special case where src_uri was a directory named with '.' or
1054 # './', so that running a command like:
1055 # gsutil cp -r . gs://dest
1056 # will produce obj names of the form gs://dest/abc instead of
1057 # gs://dest/./abc.
1058 if dst_key_name.startswith('./'):
1059 dst_key_name = dst_key_name[2:]
1060
1061 else:
1062 # Case 3.
1063 if exp_src_uri.is_stream():
1064 raise CommandException('Destination object name needed when '
1065 'source is a stream')
1066 dst_key_name = exp_src_uri.object_name.rpartition(src_uri.delim)[-1]
1067
1068 if (exp_dst_uri.is_file_uri()
1069 or self._ShouldTreatDstUriAsBucketSubDir(
1070 have_multiple_srcs, exp_dst_uri)):
1071 dst_key_name = '%s%s' % (exp_dst_uri.object_name, dst_key_name)
1072
1073 return exp_dst_uri.clone_replace_name(dst_key_name)
1074
1075 def _FixWindowsNaming(self, src_uri, dst_uri):
1076 """
1077 Rewrites the destination URI built by _ConstructDstUri() to translate
1078 Windows pathnames to cloud pathnames if neeeded.
1079
1080 Args:
1081 src_uri: src_uri to be copied.
1082 dst_uri: the destination URI built by _ConstructDstUri().
1083
1084 Returns:
1085 StorageUri to use for copy.
1086 """
1087 if (src_uri.is_file_uri() and src_uri.delim == '\\'
1088 and dst_uri.is_cloud_uri()):
1089 trans_uri_str = re.sub(r'\\', '/', dst_uri.uri)
1090 dst_uri = self.suri_builder.StorageUri(trans_uri_str)
1091 return dst_uri
1092
1093 # Command entry point.
1094 def RunCommand(self):
1095
1096 # Inner funcs.
1097 def _CopyExceptionHandler(e):
1098 """Simple exception handler to allow post-completion status."""
1099 self.THREADED_LOGGER.error(str(e))
1100 self.copy_failure_count += 1
1101
1102 def _CopyFunc(src_uri, exp_src_uri, src_uri_names_container,
1103 src_uri_expands_to_multi, have_multiple_srcs,
1104 have_existing_dest_subdir):
1105 """Worker function for performing the actual copy."""
1106 if exp_src_uri.is_file_uri() and exp_src_uri.is_stream():
1107 sys.stderr.write("Copying from <STDIN>...\n")
1108 else:
1109 self.THREADED_LOGGER.info('Copying %s...', exp_src_uri)
1110 dst_uri = self._ConstructDstUri(src_uri, exp_src_uri,
1111 src_uri_names_container,
1112 src_uri_expands_to_multi,
1113 have_multiple_srcs, exp_dst_uri,
1114 have_existing_dest_subdir)
1115 dst_uri = self._FixWindowsNaming(src_uri, dst_uri)
1116
1117 self._CheckForDirFileConflict(exp_src_uri, dst_uri)
1118 if self._SrcDstSame(exp_src_uri, dst_uri):
1119 raise CommandException('cp: "%s" and "%s" are the same file - '
1120 'abort.' % (exp_src_uri, dst_uri))
1121
1122 (elapsed_time, bytes_transferred) = self._PerformCopy(exp_src_uri,
1123 dst_uri)
1124 stats_lock.acquire()
1125 self.total_elapsed_time += elapsed_time
1126 self.total_bytes_transferred += bytes_transferred
1127 stats_lock.release()
1128
1129 # Start of RunCommand code.
1130 self._ParseArgs()
1131
1132 self.total_elapsed_time = self.total_bytes_transferred = 0
1133 if self.args[-1] == '-' or self.args[-1] == 'file://-':
1134 self._HandleStreamingDownload()
1135 return
1136
1137 src_uri_expansion = self.exp_handler.ExpandWildcardsAndContainers(
1138 self.args[0:len(self.args)-1], self.recursion_requested)
1139 (exp_dst_uri, have_existing_dst_container) = self._ExpandDstUri(
1140 src_uri_expansion, self.args[-1])
1141
1142 self._SanityCheckRequest(src_uri_expansion, exp_dst_uri,
1143 have_existing_dst_container)
1144
1145 # Use a lock to ensure accurate statistics in the face of
1146 # multi-threading/multi-processing.
1147 stats_lock = threading.Lock()
1148
1149 # Tracks if any copies failed.
1150 self.copy_failure_count = 0
1151
1152 # Start the clock.
1153 start_time = time.time()
1154
1155 # Tuple of attributes to share/manage across multiple processes in
1156 # parallel (-m) mode.
1157 shared_attrs = ('copy_failure_count', 'total_bytes_transferred')
1158
1159 # Perform copy requests in parallel (-m) mode, if requested, using
1160 # configured number of parallel processes and threads. Otherwise,
1161 # perform requests with sequential function calls in current process.
1162 self.Apply(_CopyFunc, src_uri_expansion, _CopyExceptionHandler,
1163 have_existing_dst_container, shared_attrs)
1164 if self.debug:
1165 print 'total_bytes_transferred:' + str(self.total_bytes_transferred)
1166
1167 end_time = time.time()
1168 self.total_elapsed_time = end_time - start_time
1169
1170 if self.debug == 3:
1171 # Note that this only counts the actual GET and PUT bytes for the copy
1172 # - not any transfers for doing wildcard expansion, the initial HEAD
1173 # request boto performs when doing a bucket.get_key() operation, etc.
1174 if self.total_bytes_transferred != 0:
1175 sys.stderr.write(
1176 'Total bytes copied=%d, total elapsed time=%5.3f secs (%sps)\n' % (
1177 self.total_bytes_transferred, self.total_elapsed_time,
1178 MakeHumanReadable(float(self.total_bytes_transferred) /
1179 float(self.total_elapsed_time))))
1180 if self.copy_failure_count:
1181 plural_str = ''
1182 if self.copy_failure_count > 1:
1183 plural_str = 's'
1184 raise CommandException('%d file%s/object%s could not be transferred.' % (
1185 self.copy_failure_count, plural_str, plural_str))
1186
1187 # test specification, see definition of test_steps in base class for
1188 # details on how to populate these fields
1189 test_steps = [
1190 # (test name, cmd line, ret code, (result_file, expect_file))
1191 ('upload', 'gsutil cp $F1 gs://$B1/$O1', 0, None),
1192 ('download', 'gsutil cp gs://$B1/$O1 $F9', 0, ('$F9', '$F1')),
1193 ('stream upload', 'cat $F1 | gsutil cp - gs://$B1/$O1', 0, None),
1194 ('check stream upload', 'gsutil cp gs://$B1/$O1 $F9', 0, ('$F9', '$F1')),
1195 # Clean up if we got interupted.
1196 ('remove test files',
1197 'rm -f test.mp3 test_mp3.mime test.gif test_gif.mime test.foo',
1198 0, None),
1199 ('setup mp3 file', 'cp gslib/test_data/test.mp3 test.mp3', 0, None),
1200 ('setup mp3 mime', 'echo audio/mpeg >test_mp3.mime', 0, None),
1201 ('setup gif file', 'cp gslib/test_data/test.gif test.gif', 0, None),
1202 ('setup gif mime', 'echo image/gif >test_gif.mime', 0, None),
1203 # TODO: we don't need test.app and test.bin anymore if
1204 # USE_MAGICFILE=True. Implement a way to test both with and without using
1205 # magic file.
1206 #('setup app file', 'echo application/octet-stream >test.app', 0, None),
1207 ('setup foo file', 'echo foo/bar >test.foo', 0, None),
1208 ('upload mp3', 'gsutil cp test.mp3 gs://$B1/$O1', 0, None),
1209 ('verify mp3', 'gsutil ls -L gs://$B1/$O1 | grep MIME | cut -f3 >$F1',
1210 0, ('$F1', 'test_mp3.mime')),
1211 ('upload gif', 'gsutil cp test.gif gs://$B1/$O1', 0, None),
1212 ('verify gif', 'gsutil ls -L gs://$B1/$O1 | grep MIME | cut -f3 >$F1',
1213 0, ('$F1', 'test_gif.mime')),
1214 # TODO: The commented-out /noCT test below fails with USE_MAGICFILE=True.
1215 ('upload mp3/noCT',
1216 'gsutil -h "Content-Type:" cp test.mp3 gs://$B1/$O1', 0, None),
1217 ('verify mp3/noCT', 'gsutil ls -L gs://$B1/$O1 | grep MIME | cut -f3 >$F1',
1218 0, ('$F1', 'test_mp3.mime')),
1219 ('upload gif/noCT',
1220 'gsutil -h "Content-Type:" cp test.gif gs://$B1/$O1', 0, None),
1221 ('verify gif/noCT', 'gsutil ls -L gs://$B1/$O1 | grep MIME | cut -f3 >$F1',
1222 0, ('$F1', 'test_gif.mime')),
1223 #('upload foo/noCT', 'gsutil -h "Content-Type:" cp test.foo gs://$B1/$O1',
1224 # 0, None),
1225 #('verify foo/noCT', 'gsutil ls -L gs://$B1/$O1 | grep MIME | cut -f3 >$F1',
1226 # 0, ('$F1', 'test_bin.mime')),
1227 ('upload mp3/-h gif',
1228 'gsutil -h "Content-Type:image/gif" cp test.mp3 gs://$B1/$O1', 0, None),
1229 ('verify mp3/-h gif',
1230 'gsutil ls -L gs://$B1/$O1 | grep MIME | cut -f3 >$F1',
1231 0, ('$F1', 'test_gif.mime')),
1232 ('upload gif/-h gif',
1233 'gsutil -h "Content-Type:image/gif" cp test.gif gs://$B1/$O1', 0, None),
1234 ('verify gif/-h gif',
1235 'gsutil ls -L gs://$B1/$O1 | grep MIME | cut -f3 >$F1',
1236 0, ('$F1', 'test_gif.mime')),
1237 ('upload foo/-h gif',
1238 'gsutil -h "Content-Type: image/gif" cp test.foo gs://$B1/$O1', 0, None),
1239 ('verify foo/-h gif',
1240 'gsutil ls -L gs://$B1/$O1 | grep MIME | cut -f3 >$F1',
1241 0, ('$F1', 'test_gif.mime')),
1242 ('remove test files',
1243 'rm -f test.mp3 test_mp3.mime test.gif test_gif.mime test.foo',
1244 0, None),
1245 ]
1246
1247 def _ParseArgs(self):
1248 self.mv_naming_semantics = False
1249 self.exclude_symlinks = False
1250 # self.recursion_requested initialized in command.py (so can be checked
1251 # in parent class for all commands).
1252 if self.sub_opts:
1253 for o, unused_a in self.sub_opts:
1254 if o == '-e':
1255 self.exclude_symlinks = True
1256 if o == '-M':
1257 # Note that we signal to the cp command to use the alternate naming
1258 # semantics by passing the undocumented (for internal use) -m option
1259 # when running the cp command from mv.py. These semantics only apply
1260 # for mv -R applied to bucket subdirs.
1261 self.mv_naming_semantics = True
1262 elif o == '-r' or o == '-R':
1263 self.recursion_requested = True
1264
1265 def _SanityCheckRequest(self, src_uri_expansion, exp_dst_uri,
1266 have_existing_dst_container):
1267 if src_uri_expansion.IsEmpty():
1268 raise CommandException('No URIs matched')
1269 for src_uri in src_uri_expansion.GetSrcUris():
1270 if src_uri.names_provider():
1271 raise CommandException('Provider-only src_uri (%s)')
1272 if src_uri_expansion.IsMultiSrcRequest():
1273 self._InsistDstUriNamesContainer(exp_dst_uri, have_existing_dst_container,
1274 self.command_name)
1275 if (exp_dst_uri.is_file_uri()
1276 and not os.path.exists(exp_dst_uri.object_name)):
1277 os.makedirs(exp_dst_uri.object_name)
1278
1279 def _HandleStreamingDownload(self):
1280 # Destination is <STDOUT>. Manipulate sys.stdout so as to redirect all
1281 # debug messages to <STDERR>.
1282 stdout_fp = sys.stdout
1283 sys.stdout = sys.stderr
1284 did_some_work = False
1285 for uri_str in self.args[0:len(self.args)-1]:
1286 for uri in self.exp_handler.WildcardIterator(uri_str).IterUris():
1287 if not uri.names_object():
1288 raise CommandException('Destination Stream requires that '
1289 'source URI %s should represent an object!')
1290 did_some_work = True
1291 key = uri.get_key(False, self.headers)
1292 (elapsed_time, bytes_transferred) = self._PerformDownloadToStream(
1293 key, uri, stdout_fp, self.headers)
1294 self.total_elapsed_time += elapsed_time
1295 self.total_bytes_transferred += bytes_transferred
1296 if not did_some_work:
1297 raise CommandException('No URIs matched')
1298 if self.debug == 3:
1299 if self.total_bytes_transferred != 0:
1300 sys.stderr.write(
1301 'Total bytes copied=%d, total elapsed time=%5.3f secs (%sps)\n' %
1302 (self.total_bytes_transferred, self.total_elapsed_time,
1303 MakeHumanReadable(float(self.total_bytes_transferred) /
1304 float(self.total_elapsed_time))))
1305
1306 def _SrcDstSame(self, src_uri, dst_uri):
1307 """Checks if src_uri and dst_uri represent the same object or file.
1308
1309 We don't handle anything about hard or symbolic links.
1310
1311 Args:
1312 src_uri: Source StorageUri.
1313 dst_uri: Destination StorageUri.
1314
1315 Returns:
1316 Bool indicator.
1317 """
1318 if src_uri.is_file_uri() and dst_uri.is_file_uri():
1319 # Translate a/b/./c to a/b/c, so src=dst comparison below works.
1320 new_src_path = re.sub('%s+\.%s+' % (os.sep, os.sep), os.sep,
1321 src_uri.object_name)
1322 new_src_path = re.sub('^.%s+' % os.sep, '', new_src_path)
1323 new_dst_path = re.sub('%s+\.%s+' % (os.sep, os.sep), os.sep,
1324 dst_uri.object_name)
1325 new_dst_path = re.sub('^.%s+' % os.sep, '', new_dst_path)
1326 return (src_uri.clone_replace_name(new_src_path).uri ==
1327 dst_uri.clone_replace_name(new_dst_path).uri)
1328 else:
1329 # TODO: There are cases where copying from src to dst with the same
1330 # object makes sense, namely, for setting metadata on an object. At some
1331 # point if we offer a command to do so, add a parameter to the current
1332 # function to allow this check to be overridden. Note that we want this
1333 # check to prevent a user from blowing away data using the mv command,
1334 # with a command like:
1335 # gsutil mv -R gs://bucket/abc/* gs://bucket/abc
1336 return src_uri.uri == dst_uri.uri
1337
1338 def _ShouldTreatDstUriAsBucketSubDir(self, have_multiple_srcs, dst_uri):
1339 """
1340 Checks whether dst_uri should be treated as a bucket "sub-directory". The
1341 decision about whether something constitutes a bucket "sub-directory"
1342 depends on whether there are multiple sources in this request. For
1343 example, when running the command:
1344 gsutil cp file gs://bucket/abc
1345 gs://bucket/abc names an object; in contrast, when running the command:
1346 gsutil cp file1 file2 gs://bucket/abc
1347 gs://bucket/abc names a bucket "sub-directory".
1348
1349 Note that we don't disallow naming a bucket "sub-directory" where there's
1350 already an object at that URI. For example it's legitimate (albeit
1351 confusing) to have an object called gs://bucket/dir and
1352 then run the command
1353 gsutil cp file1 file2 gs://bucket/dir
1354 Doing so will end up with objects gs://bucket/dir, gs://bucket/dir/file1,
1355 and gs://bucket/dir/file2.
1356
1357 Args:
1358 have_multiple_srcs: Bool indicator of whether this is a multi-source
1359 operation.
1360 dst_uri: StorageUri to check.
1361
1362 Returns:
1363 bool indicator.
1364 """
1365 return (self.recursion_requested and have_multiple_srcs
1366 and dst_uri.is_cloud_uri())
1367
1368 def _ShouldTreatDstUriAsSingleton(self, have_multiple_srcs,
1369 have_existing_dest_subdir, dst_uri):
1370 """
1371 Checks that dst_uri names a singleton (file or object) after
1372 dir/wildcard expansion. The decision is more nuanced than simply
1373 dst_uri.names_singleton()) because of the possibility that an object path
1374 might name a bucket sub-directory.
1375
1376 Args:
1377 have_multiple_srcs: Bool indicator of whether this is a multi-source
1378 operation.
1379 have_existing_dest_subdir: bool indicator whether dest is an existing
1380 subdirectory.
1381 dst_uri: StorageUri to check.
1382
1383 Returns:
1384 bool indicator.
1385 """
1386 if have_multiple_srcs:
1387 # Only a file meets the criteria in this case.
1388 return dst_uri.names_file()
1389 return not have_existing_dest_subdir and dst_uri.names_singleton()
1390
1391
1392 def _GetPathBeforeFinalDir(uri):
1393 """
1394 Returns the part of the path before the final directory component for the
1395 given URI, handling cases for file system directories, bucket, and bucket
1396 subdirectories. Example: for gs://bucket/dir/ we'll return 'gs://bucket'.
1397
1398 Args:
1399 uri: StorageUri.
1400
1401 Returns:
1402 String name of above-described path, sans final path separator.
1403 """
1404 sep = uri.delim
1405 assert not uri.names_file()
1406 if uri.names_directory():
1407 return uri.uri.rstrip(sep).rpartition(sep)[0]
1408 if uri.names_bucket():
1409 return '%s://' % uri.scheme
1410 # Else it names a bucket subdir.
1411 return uri.uri.rstrip(sep).rpartition(sep)[0]
1412
1413 def _hash_filename(filename):
1414 """
1415 Apply a hash function (SHA1) to shorten the passed file name. In order
1416 to minimize the risk of collisions, we include the epoch time (with
1417 microsecond graularity). The complete spec for the hashed file name is
1418 as follows:
1419
1420 TRACKER_<hash>_<timestamp>_<trailing>
1421
1422 where hash is a SHA1 hash on the original file name, timestamp is a
1423 microsecond granularity current time stamp and trailing is the last
1424 16 chars from the original file name. Max file name lengths vary by
1425 operating system so the goal of this function is to ensure the hashed
1426 version takes less than 100 characters.
1427
1428 Args:
1429 filename: file name to be hashed.
1430
1431 Returns:
1432 shorter, hashed version of passed file name
1433 """
1434 m = hashlib.sha1(filename)
1435 hashed_name = ("TRACKER_" + m.hexdigest() + ('.%.6f' % time.time()) +
1436 '.' + filename[-16:])
1437 return hashed_name
OLDNEW
« no previous file with comments | « third_party/gsutil/gslib/commands/config.py ('k') | third_party/gsutil/gslib/commands/disablelogging.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698