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.
- data/.gitignore +17 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +28 -0
- data/Rakefile +1 -0
- data/lib/s3_cmd_bin/version.rb +3 -0
- data/lib/s3_cmd_bin.rb +15 -0
- data/resources/ChangeLog +1462 -0
- data/resources/INSTALL +97 -0
- data/resources/LICENSE +339 -0
- data/resources/MANIFEST.in +2 -0
- data/resources/Makefile +4 -0
- data/resources/NEWS +234 -0
- data/resources/README +342 -0
- data/resources/S3/ACL.py +224 -0
- data/resources/S3/ACL.pyc +0 -0
- data/resources/S3/AccessLog.py +92 -0
- data/resources/S3/AccessLog.pyc +0 -0
- data/resources/S3/BidirMap.py +42 -0
- data/resources/S3/BidirMap.pyc +0 -0
- data/resources/S3/CloudFront.py +773 -0
- data/resources/S3/CloudFront.pyc +0 -0
- data/resources/S3/Config.py +294 -0
- data/resources/S3/Config.pyc +0 -0
- data/resources/S3/ConnMan.py +71 -0
- data/resources/S3/ConnMan.pyc +0 -0
- data/resources/S3/Exceptions.py +88 -0
- data/resources/S3/Exceptions.pyc +0 -0
- data/resources/S3/FileDict.py +53 -0
- data/resources/S3/FileDict.pyc +0 -0
- data/resources/S3/FileLists.py +517 -0
- data/resources/S3/FileLists.pyc +0 -0
- data/resources/S3/HashCache.py +53 -0
- data/resources/S3/HashCache.pyc +0 -0
- data/resources/S3/MultiPart.py +137 -0
- data/resources/S3/MultiPart.pyc +0 -0
- data/resources/S3/PkgInfo.py +14 -0
- data/resources/S3/PkgInfo.pyc +0 -0
- data/resources/S3/Progress.py +173 -0
- data/resources/S3/Progress.pyc +0 -0
- data/resources/S3/S3.py +979 -0
- data/resources/S3/S3.pyc +0 -0
- data/resources/S3/S3Uri.py +223 -0
- data/resources/S3/S3Uri.pyc +0 -0
- data/resources/S3/SimpleDB.py +178 -0
- data/resources/S3/SortedDict.py +66 -0
- data/resources/S3/SortedDict.pyc +0 -0
- data/resources/S3/Utils.py +462 -0
- data/resources/S3/Utils.pyc +0 -0
- data/resources/S3/__init__.py +0 -0
- data/resources/S3/__init__.pyc +0 -0
- data/resources/TODO +52 -0
- data/resources/artwork/AtomicClockRadio.ttf +0 -0
- data/resources/artwork/TypeRa.ttf +0 -0
- data/resources/artwork/site-top-full-size.xcf +0 -0
- data/resources/artwork/site-top-label-download.png +0 -0
- data/resources/artwork/site-top-label-s3cmd.png +0 -0
- data/resources/artwork/site-top-label-s3sync.png +0 -0
- data/resources/artwork/site-top-s3tools-logo.png +0 -0
- data/resources/artwork/site-top.jpg +0 -0
- data/resources/artwork/site-top.png +0 -0
- data/resources/artwork/site-top.xcf +0 -0
- data/resources/format-manpage.pl +196 -0
- data/resources/magic +63 -0
- data/resources/run-tests.py +537 -0
- data/resources/s3cmd +2116 -0
- data/resources/s3cmd.1 +435 -0
- data/resources/s3db +55 -0
- data/resources/setup.cfg +2 -0
- data/resources/setup.py +80 -0
- data/resources/testsuite.tar.gz +0 -0
- data/resources/upload-to-sf.sh +7 -0
- data/s3_cmd_bin.gemspec +23 -0
- 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
|