s3_cmd_bin 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|