...
|
...
|
@@ -21,10 +21,6 @@ from optparse import OptionParser, Option, OptionValueError, IndentedHelpFormatt
|
21
|
21
|
from logging import debug, info, warning, error
|
22
|
22
|
from distutils.spawn import find_executable
|
23
|
23
|
|
24
|
|
-error("This s3cmd from SVN is broken!")
|
25
|
|
-error("Use revision 335 or s3cmd-0.9.9-pre4")
|
26
|
|
-sys.exit(1)
|
27
|
|
-
|
28
|
24
|
def output(message):
|
29
|
25
|
sys.stdout.write(message + "\n")
|
30
|
26
|
|
...
|
...
|
@@ -175,7 +171,7 @@ def cmd_bucket_delete(args):
|
175
|
175
|
|
176
|
176
|
def fetch_local_list(args, recursive = None):
|
177
|
177
|
local_uris = []
|
178
|
|
- local_list = {}
|
|
178
|
+ local_list = SortedDict()
|
179
|
179
|
|
180
|
180
|
if type(args) not in (list, tuple):
|
181
|
181
|
args = [args]
|
...
|
...
|
@@ -198,7 +194,7 @@ def fetch_local_list(args, recursive = None):
|
198
|
198
|
|
199
|
199
|
def fetch_remote_list(args, require_attribs = False, recursive = None):
|
200
|
200
|
remote_uris = []
|
201
|
|
- remote_list = {}
|
|
201
|
+ remote_list = SortedDict()
|
202
|
202
|
|
203
|
203
|
if type(args) not in (list, tuple):
|
204
|
204
|
args = [args]
|
...
|
...
|
@@ -531,27 +527,27 @@ def cmd_info(args):
|
531
|
531
|
def _get_filelist_local(local_uri):
|
532
|
532
|
info(u"Compiling list of local files...")
|
533
|
533
|
if local_uri.isdir():
|
534
|
|
- local_base = local_uri.basename()
|
|
534
|
+ local_base = deunicodise(local_uri.basename())
|
535
|
535
|
local_path = deunicodise(local_uri.path())
|
536
|
536
|
filelist = os.walk(local_path)
|
537
|
537
|
else:
|
538
|
538
|
local_base = ""
|
539
|
539
|
local_path = deunicodise(local_uri.dirname())
|
540
|
540
|
filelist = [( local_path, [], [deunicodise(local_uri.basename())] )]
|
541
|
|
- loc_list = {}
|
|
541
|
+ loc_list = SortedDict()
|
542
|
542
|
for root, dirs, files in filelist:
|
543
|
543
|
rel_root = root.replace(local_path, local_base, 1)
|
544
|
|
- ## TODO: implement explicit exclude
|
545
|
544
|
for f in files:
|
546
|
545
|
full_name = os.path.join(root, f)
|
547
|
|
- rel_name = os.path.join(rel_root, f)
|
548
|
546
|
if not os.path.isfile(full_name):
|
549
|
547
|
continue
|
550
|
548
|
if os.path.islink(full_name):
|
551
|
549
|
## Synchronize symlinks... one day
|
552
|
550
|
## for now skip over
|
553
|
551
|
continue
|
554
|
|
- relative_file = unicodise(rel_name)
|
|
552
|
+ relative_file = unicodise(os.path.join(rel_root, f))
|
|
553
|
+ if relative_file.startswith('./'):
|
|
554
|
+ relative_file = relative_file[2:]
|
555
|
555
|
sr = os.stat_result(os.lstat(full_name))
|
556
|
556
|
loc_list[relative_file] = {
|
557
|
557
|
'full_name_unicode' : unicodise(full_name),
|
...
|
...
|
@@ -589,7 +585,7 @@ def _get_filelist_remote(remote_uri, recursive = True):
|
589
|
589
|
rem_base = rem_base[:rem_base.rfind('/')+1]
|
590
|
590
|
remote_uri = S3Uri("s3://%s/%s" % (remote_uri.bucket(), rem_base))
|
591
|
591
|
rem_base_len = len(rem_base)
|
592
|
|
- rem_list = {}
|
|
592
|
+ rem_list = SortedDict()
|
593
|
593
|
break_now = False
|
594
|
594
|
for object in response['list']:
|
595
|
595
|
if object['Key'] == rem_base_original and object['Key'][-1] != os.path.sep:
|
...
|
...
|
@@ -616,7 +612,7 @@ def _get_filelist_remote(remote_uri, recursive = True):
|
616
|
616
|
def _filelist_filter_exclude_include(src_list):
|
617
|
617
|
info(u"Applying --exclude/--include")
|
618
|
618
|
cfg = Config()
|
619
|
|
- exclude_list = {}
|
|
619
|
+ exclude_list = SortedDict()
|
620
|
620
|
for file in src_list.keys():
|
621
|
621
|
debug(u"CHECK: %s" % file)
|
622
|
622
|
excluded = False
|
...
|
...
|
@@ -645,7 +641,7 @@ def _filelist_filter_exclude_include(src_list):
|
645
|
645
|
def _compare_filelists(src_list, dst_list, src_is_local_and_dst_is_remote):
|
646
|
646
|
info(u"Verifying attributes...")
|
647
|
647
|
cfg = Config()
|
648
|
|
- exists_list = {}
|
|
648
|
+ exists_list = SortedDict()
|
649
|
649
|
if cfg.debug_syncmatch:
|
650
|
650
|
logging.root.setLevel(logging.DEBUG)
|
651
|
651
|
|
...
|
...
|
@@ -735,23 +731,22 @@ def cmd_sync_remote2local(args):
|
735
|
735
|
|
736
|
736
|
info(u"Summary: %d remote files to download, %d local files to delete" % (remote_count, local_count))
|
737
|
737
|
|
738
|
|
- for file in local_list:
|
739
|
|
- if cfg.delete_removed:
|
740
|
|
- os.unlink(local_list[file]['full_name'])
|
741
|
|
- output(u"deleted: %s" % local_list[file]['full_name'])
|
742
|
|
- else:
|
743
|
|
- info(u"deleted: %s" % local_list[file]['full_name'])
|
744
|
|
-
|
745
|
|
- if cfg.verbosity == logging.DEBUG:
|
|
738
|
+ if cfg.dry_run:
|
746
|
739
|
for key in exclude_list:
|
747
|
|
- debug(u"excluded: %s" % unicodise(key))
|
|
740
|
+ output(u"excluded: %s" % unicodise(key))
|
|
741
|
+ for key in local_list:
|
|
742
|
+ output(u"delete: %s" % local_list[key]['full_name_unicode'])
|
748
|
743
|
for key in remote_list:
|
749
|
|
- debug(u"download: %s" % unicodise(key))
|
|
744
|
+ output(u"download: %s -> %s" % (remote_list[key]['object_uri_str'], remote_list[key]['local_filename']))
|
750
|
745
|
|
751
|
|
- if cfg.dry_run:
|
752
|
746
|
warning(u"Exitting now because of --dry-run")
|
753
|
747
|
return
|
754
|
748
|
|
|
749
|
+ if cfg.delete_removed:
|
|
750
|
+ for key in local_list:
|
|
751
|
+ os.unlink(local_list[key]['full_name'])
|
|
752
|
+ output(u"deleted: %s" % local_list[key]['full_name_unicode'])
|
|
753
|
+
|
755
|
754
|
total_size = 0
|
756
|
755
|
total_elapsed = 0.0
|
757
|
756
|
timestamp_start = time.time()
|
...
|
...
|
@@ -839,7 +834,7 @@ def cmd_sync_remote2local(args):
|
839
|
839
|
else:
|
840
|
840
|
info(outstr)
|
841
|
841
|
|
842
|
|
-def cmd_sync_local2remote(src, dst):
|
|
842
|
+def cmd_sync_local2remote(args):
|
843
|
843
|
def _build_attr_header(src):
|
844
|
844
|
import pwd, grp
|
845
|
845
|
attrs = {}
|
...
|
...
|
@@ -870,57 +865,74 @@ def cmd_sync_local2remote(src, dst):
|
870
|
870
|
s3 = S3(cfg)
|
871
|
871
|
|
872
|
872
|
if cfg.encrypt:
|
873
|
|
- error(u"S3cmd 'sync' doesn't support GPG encryption, sorry.")
|
|
873
|
+ error(u"S3cmd 'sync' doesn't yet support GPG encryption, sorry.")
|
874
|
874
|
error(u"Either use unconditional 's3cmd put --recursive'")
|
875
|
875
|
error(u"or disable encryption with --no-encrypt parameter.")
|
876
|
876
|
sys.exit(1)
|
877
|
877
|
|
|
878
|
+ destination_base = args[-1]
|
|
879
|
+ local_list = fetch_local_list(args[:-1], recursive = True)
|
|
880
|
+ remote_list = fetch_remote_list(destination_base, recursive = True, require_attribs = True)
|
878
|
881
|
|
879
|
|
- src_uri = S3Uri(src)
|
880
|
|
- dst_uri = S3Uri(dst)
|
|
882
|
+ local_count = len(local_list)
|
|
883
|
+ remote_count = len(remote_list)
|
881
|
884
|
|
882
|
|
- loc_list = _get_filelist_local(src_uri)
|
883
|
|
- loc_count = len(loc_list)
|
884
|
|
-
|
885
|
|
- rem_list = _get_filelist_remote(dst_uri)
|
886
|
|
- rem_count = len(rem_list)
|
|
885
|
+ info(u"Found %d local files, %d remote files" % (local_count, remote_count))
|
887
|
886
|
|
888
|
|
- info(u"Found %d local files, %d remote files" % (loc_count, rem_count))
|
|
887
|
+ local_list, exclude_list = _filelist_filter_exclude_include(local_list)
|
889
|
888
|
|
890
|
|
- _compare_filelists(loc_list, rem_list, True)
|
|
889
|
+ local_list, remote_list, existing_list = _compare_filelists(local_list, remote_list, True)
|
891
|
890
|
|
892
|
|
- info(u"Summary: %d local files to upload, %d remote files to delete" % (len(loc_list), len(rem_list)))
|
|
891
|
+ local_count = len(local_list)
|
|
892
|
+ remote_count = len(remote_list)
|
893
|
893
|
|
894
|
|
- for file in rem_list:
|
895
|
|
- uri = S3Uri("s3://" + dst_uri.bucket()+"/"+rem_list[file]['object_key'])
|
896
|
|
- if cfg.delete_removed:
|
897
|
|
- response = s3.object_delete(uri)
|
898
|
|
- output(u"deleted '%s'" % uri)
|
899
|
|
- else:
|
900
|
|
- output(u"not-deleted '%s'" % uri)
|
|
894
|
+ if not destination_base.endswith("/"):
|
|
895
|
+ if local_count > 1:
|
|
896
|
+ raise ParameterError("Destination S3 URI must end with '/' (ie must refer to a directory on the remote side).")
|
|
897
|
+ local_list[local_list.keys()[0]]['remote_uri'] = unicodise(destination_base)
|
|
898
|
+ else:
|
|
899
|
+ for key in local_list:
|
|
900
|
+ local_list[key]['remote_uri'] = unicodise(destination_base + key)
|
|
901
|
+
|
|
902
|
+ info(u"Summary: %d local files to upload, %d remote files to delete" % (local_count, remote_count))
|
|
903
|
+
|
|
904
|
+ if cfg.dry_run:
|
|
905
|
+ for key in exclude_list:
|
|
906
|
+ output(u"excluded: %s" % unicodise(key))
|
|
907
|
+ for key in remote_list:
|
|
908
|
+ output(u"deleted: %s" % remote_list[key]['object_uri_str'])
|
|
909
|
+ for key in local_list:
|
|
910
|
+ output(u"upload: %s -> %s" % (local_list[key]['full_name_unicode'], local_list[key]['remote_uri']))
|
|
911
|
+
|
|
912
|
+ warning(u"Exitting now because of --dry-run")
|
|
913
|
+ return
|
|
914
|
+
|
|
915
|
+ if cfg.delete_removed:
|
|
916
|
+ for key in remote_list:
|
|
917
|
+ uri = S3Uri(remote_list[key]['object_uri_str'])
|
|
918
|
+ s3.object_delete(uri)
|
|
919
|
+ output(u"deleted: '%s'" % uri)
|
901
|
920
|
|
902
|
921
|
total_size = 0
|
903
|
|
- total_count = len(loc_list)
|
904
|
922
|
total_elapsed = 0.0
|
905
|
923
|
timestamp_start = time.time()
|
906
|
924
|
seq = 0
|
907
|
|
- dst_base = dst_uri.uri()
|
908
|
|
- if not dst_base[-1] == "/": dst_base += "/"
|
909
|
|
- file_list = loc_list.keys()
|
|
925
|
+ file_list = local_list.keys()
|
910
|
926
|
file_list.sort()
|
911
|
927
|
for file in file_list:
|
912
|
928
|
seq += 1
|
913
|
|
- src = loc_list[file]
|
914
|
|
- uri = S3Uri(dst_base + file)
|
915
|
|
- seq_label = "[%d of %d]" % (seq, total_count)
|
|
929
|
+ item = local_list[file]
|
|
930
|
+ src = item['full_name']
|
|
931
|
+ uri = S3Uri(item['remote_uri'])
|
|
932
|
+ seq_label = "[%d of %d]" % (seq, local_count)
|
916
|
933
|
attr_header = None
|
917
|
934
|
if cfg.preserve_attrs:
|
918
|
|
- attr_header = _build_attr_header(src['full_name'])
|
|
935
|
+ attr_header = _build_attr_header(src)
|
919
|
936
|
debug(attr_header)
|
920
|
937
|
try:
|
921
|
|
- response = s3.object_put(src['full_name'], uri, attr_header, extra_label = seq_label)
|
|
938
|
+ response = s3.object_put(src, uri, attr_header, extra_label = seq_label)
|
922
|
939
|
except S3UploadError, e:
|
923
|
|
- error(u"%s: upload failed too many times. Skipping that file." % src['full_name_unicode'])
|
|
940
|
+ error(u"%s: upload failed too many times. Skipping that file." % item['full_name_unicode'])
|
924
|
941
|
continue
|
925
|
942
|
except InvalidFileError, e:
|
926
|
943
|
warning(u"File can not be uploaded: %s" % e)
|
...
|
...
|
@@ -928,8 +940,8 @@ def cmd_sync_local2remote(src, dst):
|
928
|
928
|
speed_fmt = formatSize(response["speed"], human_readable = True, floating_point = True)
|
929
|
929
|
if not cfg.progress_meter:
|
930
|
930
|
output(u"File '%s' stored as %s (%d bytes in %0.1f seconds, %0.2f %sB/s) %s" %
|
931
|
|
- (src, uri, response["size"], response["elapsed"], speed_fmt[0], speed_fmt[1],
|
932
|
|
- seq_label))
|
|
931
|
+ (item['full_name_unicode'], uri, response["size"], response["elapsed"],
|
|
932
|
+ speed_fmt[0], speed_fmt[1], seq_label))
|
933
|
933
|
total_size += response["size"]
|
934
|
934
|
|
935
|
935
|
total_elapsed = time.time() - timestamp_start
|
...
|
...
|
@@ -1239,7 +1251,7 @@ def main():
|
1239
|
1239
|
optparser.add_option("-c", "--config", dest="config", metavar="FILE", help="Config file name. Defaults to %default")
|
1240
|
1240
|
optparser.add_option( "--dump-config", dest="dump_config", action="store_true", help="Dump current configuration after parsing config files and command line options and exit.")
|
1241
|
1241
|
|
1242
|
|
- #optparser.add_option("-n", "--dry-run", dest="dry_run", action="store_true", help="Only show what should be uploaded or downloaded but don't actually do it. May still perform S3 requests to get bucket listings and other information though.")
|
|
1242
|
+ optparser.add_option("-n", "--dry-run", dest="dry_run", action="store_true", help="Only show what should be uploaded or downloaded but don't actually do it. May still perform S3 requests to get bucket listings and other information though (only for [sync] command)")
|
1243
|
1243
|
|
1244
|
1244
|
optparser.add_option("-e", "--encrypt", dest="encrypt", action="store_true", help="Encrypt files before uploading to S3.")
|
1245
|
1245
|
optparser.add_option( "--no-encrypt", dest="encrypt", action="store_false", help="Don't encrypt files.")
|