s3_cmd_bin 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +28 -0
  5. data/Rakefile +1 -0
  6. data/lib/s3_cmd_bin/version.rb +3 -0
  7. data/lib/s3_cmd_bin.rb +15 -0
  8. data/resources/ChangeLog +1462 -0
  9. data/resources/INSTALL +97 -0
  10. data/resources/LICENSE +339 -0
  11. data/resources/MANIFEST.in +2 -0
  12. data/resources/Makefile +4 -0
  13. data/resources/NEWS +234 -0
  14. data/resources/README +342 -0
  15. data/resources/S3/ACL.py +224 -0
  16. data/resources/S3/ACL.pyc +0 -0
  17. data/resources/S3/AccessLog.py +92 -0
  18. data/resources/S3/AccessLog.pyc +0 -0
  19. data/resources/S3/BidirMap.py +42 -0
  20. data/resources/S3/BidirMap.pyc +0 -0
  21. data/resources/S3/CloudFront.py +773 -0
  22. data/resources/S3/CloudFront.pyc +0 -0
  23. data/resources/S3/Config.py +294 -0
  24. data/resources/S3/Config.pyc +0 -0
  25. data/resources/S3/ConnMan.py +71 -0
  26. data/resources/S3/ConnMan.pyc +0 -0
  27. data/resources/S3/Exceptions.py +88 -0
  28. data/resources/S3/Exceptions.pyc +0 -0
  29. data/resources/S3/FileDict.py +53 -0
  30. data/resources/S3/FileDict.pyc +0 -0
  31. data/resources/S3/FileLists.py +517 -0
  32. data/resources/S3/FileLists.pyc +0 -0
  33. data/resources/S3/HashCache.py +53 -0
  34. data/resources/S3/HashCache.pyc +0 -0
  35. data/resources/S3/MultiPart.py +137 -0
  36. data/resources/S3/MultiPart.pyc +0 -0
  37. data/resources/S3/PkgInfo.py +14 -0
  38. data/resources/S3/PkgInfo.pyc +0 -0
  39. data/resources/S3/Progress.py +173 -0
  40. data/resources/S3/Progress.pyc +0 -0
  41. data/resources/S3/S3.py +979 -0
  42. data/resources/S3/S3.pyc +0 -0
  43. data/resources/S3/S3Uri.py +223 -0
  44. data/resources/S3/S3Uri.pyc +0 -0
  45. data/resources/S3/SimpleDB.py +178 -0
  46. data/resources/S3/SortedDict.py +66 -0
  47. data/resources/S3/SortedDict.pyc +0 -0
  48. data/resources/S3/Utils.py +462 -0
  49. data/resources/S3/Utils.pyc +0 -0
  50. data/resources/S3/__init__.py +0 -0
  51. data/resources/S3/__init__.pyc +0 -0
  52. data/resources/TODO +52 -0
  53. data/resources/artwork/AtomicClockRadio.ttf +0 -0
  54. data/resources/artwork/TypeRa.ttf +0 -0
  55. data/resources/artwork/site-top-full-size.xcf +0 -0
  56. data/resources/artwork/site-top-label-download.png +0 -0
  57. data/resources/artwork/site-top-label-s3cmd.png +0 -0
  58. data/resources/artwork/site-top-label-s3sync.png +0 -0
  59. data/resources/artwork/site-top-s3tools-logo.png +0 -0
  60. data/resources/artwork/site-top.jpg +0 -0
  61. data/resources/artwork/site-top.png +0 -0
  62. data/resources/artwork/site-top.xcf +0 -0
  63. data/resources/format-manpage.pl +196 -0
  64. data/resources/magic +63 -0
  65. data/resources/run-tests.py +537 -0
  66. data/resources/s3cmd +2116 -0
  67. data/resources/s3cmd.1 +435 -0
  68. data/resources/s3db +55 -0
  69. data/resources/setup.cfg +2 -0
  70. data/resources/setup.py +80 -0
  71. data/resources/testsuite.tar.gz +0 -0
  72. data/resources/upload-to-sf.sh +7 -0
  73. data/s3_cmd_bin.gemspec +23 -0
  74. metadata +152 -0
@@ -0,0 +1,137 @@
1
+ ## Amazon S3 Multipart upload support
2
+ ## Author: Jerome Leclanche <jerome.leclanche@gmail.com>
3
+ ## License: GPL Version 2
4
+
5
+ import os
6
+ from stat import ST_SIZE
7
+ from logging import debug, info, warning, error
8
+ from Utils import getTextFromXml, formatSize, unicodise
9
+ from Exceptions import S3UploadError
10
+
11
+ class MultiPartUpload(object):
12
+
13
+ MIN_CHUNK_SIZE_MB = 5 # 5MB
14
+ MAX_CHUNK_SIZE_MB = 5120 # 5GB
15
+ MAX_FILE_SIZE = 42949672960 # 5TB
16
+
17
+ def __init__(self, s3, file, uri, headers_baseline = {}):
18
+ self.s3 = s3
19
+ self.file = file
20
+ self.uri = uri
21
+ self.parts = {}
22
+ self.headers_baseline = headers_baseline
23
+ self.upload_id = self.initiate_multipart_upload()
24
+
25
+ def initiate_multipart_upload(self):
26
+ """
27
+ Begin a multipart upload
28
+ http://docs.amazonwebservices.com/AmazonS3/latest/API/index.html?mpUploadInitiate.html
29
+ """
30
+ request = self.s3.create_request("OBJECT_POST", uri = self.uri, headers = self.headers_baseline, extra = "?uploads")
31
+ response = self.s3.send_request(request)
32
+ data = response["data"]
33
+ self.upload_id = getTextFromXml(data, "UploadId")
34
+ return self.upload_id
35
+
36
+ def upload_all_parts(self):
37
+ """
38
+ Execute a full multipart upload on a file
39
+ Returns the seq/etag dict
40
+ TODO use num_processes to thread it
41
+ """
42
+ if not self.upload_id:
43
+ raise RuntimeError("Attempting to use a multipart upload that has not been initiated.")
44
+
45
+ self.chunk_size = self.s3.config.multipart_chunk_size_mb * 1024 * 1024
46
+
47
+ if self.file.name != "<stdin>":
48
+ size_left = file_size = os.stat(self.file.name)[ST_SIZE]
49
+ nr_parts = file_size / self.chunk_size + (file_size % self.chunk_size and 1)
50
+ debug("MultiPart: Uploading %s in %d parts" % (self.file.name, nr_parts))
51
+ else:
52
+ debug("MultiPart: Uploading from %s" % (self.file.name))
53
+
54
+ seq = 1
55
+ if self.file.name != "<stdin>":
56
+ while size_left > 0:
57
+ offset = self.chunk_size * (seq - 1)
58
+ current_chunk_size = min(file_size - offset, self.chunk_size)
59
+ size_left -= current_chunk_size
60
+ labels = {
61
+ 'source' : unicodise(self.file.name),
62
+ 'destination' : unicodise(self.uri.uri()),
63
+ 'extra' : "[part %d of %d, %s]" % (seq, nr_parts, "%d%sB" % formatSize(current_chunk_size, human_readable = True))
64
+ }
65
+ try:
66
+ self.upload_part(seq, offset, current_chunk_size, labels)
67
+ except:
68
+ error(u"Upload of '%s' part %d failed. Aborting multipart upload." % (self.file.name, seq))
69
+ self.abort_upload()
70
+ raise
71
+ seq += 1
72
+ else:
73
+ while True:
74
+ buffer = self.file.read(self.chunk_size)
75
+ offset = self.chunk_size * (seq - 1)
76
+ current_chunk_size = len(buffer)
77
+ labels = {
78
+ 'source' : unicodise(self.file.name),
79
+ 'destination' : unicodise(self.uri.uri()),
80
+ 'extra' : "[part %d, %s]" % (seq, "%d%sB" % formatSize(current_chunk_size, human_readable = True))
81
+ }
82
+ if len(buffer) == 0: # EOF
83
+ break
84
+ try:
85
+ self.upload_part(seq, offset, current_chunk_size, labels, buffer)
86
+ except:
87
+ error(u"Upload of '%s' part %d failed. Aborting multipart upload." % (self.file.name, seq))
88
+ self.abort_upload()
89
+ raise
90
+ seq += 1
91
+
92
+ debug("MultiPart: Upload finished: %d parts", seq - 1)
93
+
94
+ def upload_part(self, seq, offset, chunk_size, labels, buffer = ''):
95
+ """
96
+ Upload a file chunk
97
+ http://docs.amazonwebservices.com/AmazonS3/latest/API/index.html?mpUploadUploadPart.html
98
+ """
99
+ # TODO implement Content-MD5
100
+ debug("Uploading part %i of %r (%s bytes)" % (seq, self.upload_id, chunk_size))
101
+ headers = { "content-length": chunk_size }
102
+ query_string = "?partNumber=%i&uploadId=%s" % (seq, self.upload_id)
103
+ request = self.s3.create_request("OBJECT_PUT", uri = self.uri, headers = headers, extra = query_string)
104
+ response = self.s3.send_file(request, self.file, labels, buffer, offset = offset, chunk_size = chunk_size)
105
+ self.parts[seq] = response["headers"]["etag"]
106
+ return response
107
+
108
+ def complete_multipart_upload(self):
109
+ """
110
+ Finish a multipart upload
111
+ http://docs.amazonwebservices.com/AmazonS3/latest/API/index.html?mpUploadComplete.html
112
+ """
113
+ debug("MultiPart: Completing upload: %s" % self.upload_id)
114
+
115
+ parts_xml = []
116
+ part_xml = "<Part><PartNumber>%i</PartNumber><ETag>%s</ETag></Part>"
117
+ for seq, etag in self.parts.items():
118
+ parts_xml.append(part_xml % (seq, etag))
119
+ body = "<CompleteMultipartUpload>%s</CompleteMultipartUpload>" % ("".join(parts_xml))
120
+
121
+ headers = { "content-length": len(body) }
122
+ request = self.s3.create_request("OBJECT_POST", uri = self.uri, headers = headers, extra = "?uploadId=%s" % (self.upload_id))
123
+ response = self.s3.send_request(request, body = body)
124
+
125
+ return response
126
+
127
+ def abort_upload(self):
128
+ """
129
+ Abort multipart upload
130
+ http://docs.amazonwebservices.com/AmazonS3/latest/API/index.html?mpUploadAbort.html
131
+ """
132
+ debug("MultiPart: Aborting upload: %s" % self.upload_id)
133
+ request = self.s3.create_request("OBJECT_DELETE", uri = self.uri, extra = "?uploadId=%s" % (self.upload_id))
134
+ response = self.s3.send_request(request)
135
+ return response
136
+
137
+ # vim:et:ts=4:sts=4:ai
Binary file
@@ -0,0 +1,14 @@
1
+ package = "s3cmd"
2
+ version = "1.5.0-alpha3"
3
+ url = "http://s3tools.org"
4
+ license = "GPL version 2"
5
+ short_description = "Command line tool for managing Amazon S3 and CloudFront services"
6
+ long_description = """
7
+ S3cmd lets you copy files from/to Amazon S3
8
+ (Simple Storage Service) using a simple to use
9
+ command line client. Supports rsync-like backup,
10
+ GPG encryption, and more. Also supports management
11
+ of Amazon's CloudFront content delivery network.
12
+ """
13
+
14
+ # vim:et:ts=4:sts=4:ai
Binary file
@@ -0,0 +1,173 @@
1
+ ## Amazon S3 manager
2
+ ## Author: Michal Ludvig <michal@logix.cz>
3
+ ## http://www.logix.cz/michal
4
+ ## License: GPL Version 2
5
+
6
+ import sys
7
+ import datetime
8
+ import time
9
+ import Utils
10
+
11
+ class Progress(object):
12
+ _stdout = sys.stdout
13
+ _last_display = 0
14
+
15
+ def __init__(self, labels, total_size):
16
+ self._stdout = sys.stdout
17
+ self.new_file(labels, total_size)
18
+
19
+ def new_file(self, labels, total_size):
20
+ self.labels = labels
21
+ self.total_size = total_size
22
+ # Set initial_position to something in the
23
+ # case we're not counting from 0. For instance
24
+ # when appending to a partially downloaded file.
25
+ # Setting initial_position will let the speed
26
+ # be computed right.
27
+ self.initial_position = 0
28
+ self.current_position = self.initial_position
29
+ self.time_start = datetime.datetime.now()
30
+ self.time_last = self.time_start
31
+ self.time_current = self.time_start
32
+
33
+ self.display(new_file = True)
34
+
35
+ def update(self, current_position = -1, delta_position = -1):
36
+ self.time_last = self.time_current
37
+ self.time_current = datetime.datetime.now()
38
+ if current_position > -1:
39
+ self.current_position = current_position
40
+ elif delta_position > -1:
41
+ self.current_position += delta_position
42
+ #else:
43
+ # no update, just call display()
44
+ self.display()
45
+
46
+ def done(self, message):
47
+ self.display(done_message = message)
48
+
49
+ def output_labels(self):
50
+ self._stdout.write(u"%(source)s -> %(destination)s %(extra)s\n" % self.labels)
51
+ self._stdout.flush()
52
+
53
+ def _display_needed(self):
54
+ # We only need to update the display every so often.
55
+ if time.time() - self._last_display > 1:
56
+ self._last_display = time.time()
57
+ return True
58
+ return False
59
+
60
+ def display(self, new_file = False, done_message = None):
61
+ """
62
+ display(new_file = False[/True], done = False[/True])
63
+
64
+ Override this method to provide a nicer output.
65
+ """
66
+ if new_file:
67
+ self.output_labels()
68
+ self.last_milestone = 0
69
+ return
70
+
71
+ if self.current_position == self.total_size:
72
+ print_size = Utils.formatSize(self.current_position, True)
73
+ if print_size[1] != "": print_size[1] += "B"
74
+ timedelta = self.time_current - self.time_start
75
+ sec_elapsed = timedelta.days * 86400 + timedelta.seconds + float(timedelta.microseconds)/1000000.0
76
+ print_speed = Utils.formatSize((self.current_position - self.initial_position) / sec_elapsed, True, True)
77
+ self._stdout.write("100%% %s%s in %.2fs (%.2f %sB/s)\n" %
78
+ (print_size[0], print_size[1], sec_elapsed, print_speed[0], print_speed[1]))
79
+ self._stdout.flush()
80
+ return
81
+
82
+ rel_position = selfself.current_position * 100 / self.total_size
83
+ if rel_position >= self.last_milestone:
84
+ self.last_milestone = (int(rel_position) / 5) * 5
85
+ self._stdout.write("%d%% ", self.last_milestone)
86
+ self._stdout.flush()
87
+ return
88
+
89
+ class ProgressANSI(Progress):
90
+ ## http://en.wikipedia.org/wiki/ANSI_escape_code
91
+ SCI = '\x1b['
92
+ ANSI_hide_cursor = SCI + "?25l"
93
+ ANSI_show_cursor = SCI + "?25h"
94
+ ANSI_save_cursor_pos = SCI + "s"
95
+ ANSI_restore_cursor_pos = SCI + "u"
96
+ ANSI_move_cursor_to_column = SCI + "%uG"
97
+ ANSI_erase_to_eol = SCI + "0K"
98
+ ANSI_erase_current_line = SCI + "2K"
99
+
100
+ def display(self, new_file = False, done_message = None):
101
+ """
102
+ display(new_file = False[/True], done_message = None)
103
+ """
104
+ if new_file:
105
+ self.output_labels()
106
+ self._stdout.write(self.ANSI_save_cursor_pos)
107
+ self._stdout.flush()
108
+ return
109
+
110
+ # Only display progress every so often
111
+ if not (new_file or done_message) and not self._display_needed():
112
+ return
113
+
114
+ timedelta = self.time_current - self.time_start
115
+ sec_elapsed = timedelta.days * 86400 + timedelta.seconds + float(timedelta.microseconds)/1000000.0
116
+ if (sec_elapsed > 0):
117
+ print_speed = Utils.formatSize((self.current_position - self.initial_position) / sec_elapsed, True, True)
118
+ else:
119
+ print_speed = (0, "")
120
+ self._stdout.write(self.ANSI_restore_cursor_pos)
121
+ self._stdout.write(self.ANSI_erase_to_eol)
122
+ self._stdout.write("%(current)s of %(total)s %(percent)3d%% in %(elapsed)ds %(speed).2f %(speed_coeff)sB/s" % {
123
+ "current" : str(self.current_position).rjust(len(str(self.total_size))),
124
+ "total" : self.total_size,
125
+ "percent" : self.total_size and (self.current_position * 100 / self.total_size) or 0,
126
+ "elapsed" : sec_elapsed,
127
+ "speed" : print_speed[0],
128
+ "speed_coeff" : print_speed[1]
129
+ })
130
+
131
+ if done_message:
132
+ self._stdout.write(" %s\n" % done_message)
133
+
134
+ self._stdout.flush()
135
+
136
+ class ProgressCR(Progress):
137
+ ## Uses CR char (Carriage Return) just like other progress bars do.
138
+ CR_char = chr(13)
139
+
140
+ def display(self, new_file = False, done_message = None):
141
+ """
142
+ display(new_file = False[/True], done_message = None)
143
+ """
144
+ if new_file:
145
+ self.output_labels()
146
+ return
147
+
148
+ # Only display progress every so often
149
+ if not (new_file or done_message) and not self._display_needed():
150
+ return
151
+
152
+ timedelta = self.time_current - self.time_start
153
+ sec_elapsed = timedelta.days * 86400 + timedelta.seconds + float(timedelta.microseconds)/1000000.0
154
+ if (sec_elapsed > 0):
155
+ print_speed = Utils.formatSize((self.current_position - self.initial_position) / sec_elapsed, True, True)
156
+ else:
157
+ print_speed = (0, "")
158
+ self._stdout.write(self.CR_char)
159
+ output = " %(current)s of %(total)s %(percent)3d%% in %(elapsed)4ds %(speed)7.2f %(speed_coeff)sB/s" % {
160
+ "current" : str(self.current_position).rjust(len(str(self.total_size))),
161
+ "total" : self.total_size,
162
+ "percent" : self.total_size and (self.current_position * 100 / self.total_size) or 0,
163
+ "elapsed" : sec_elapsed,
164
+ "speed" : print_speed[0],
165
+ "speed_coeff" : print_speed[1]
166
+ }
167
+ self._stdout.write(output)
168
+ if done_message:
169
+ self._stdout.write(" %s\n" % done_message)
170
+
171
+ self._stdout.flush()
172
+
173
+ # vim:et:ts=4:sts=4:ai
Binary file