2 ###############################################################################
4 # Pakfire - The IPFire package management system #
5 # Copyright (C) 2013 Pakfire development team #
7 # This program is free software: you can redistribute it and/or modify #
8 # it under the terms of the GNU General Public License as published by #
9 # the Free Software Foundation, either version 3 of the License, or #
10 # (at your option) any later version. #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
20 ###############################################################################
22 from __future__
import division
33 import pakfire
.downloader
36 from pakfire
.constants
import *
37 from pakfire
.i18n
import _
40 log
= logging
.getLogger("pakfire.transport")
43 class PakfireHubTransportUploader(object):
45 Handles the upload of a single file to the hub.
48 def __init__(self
, transport
, filename
):
49 self
.transport
= transport
50 self
.filename
= filename
52 def get_upload_id(self
):
54 Gets an upload from the pakfire hub.
56 # Calculate the SHA1 sum of the file to upload.
57 h
= hashlib
.new("sha1")
58 with
open(self
.filename
, "rb") as f
:
60 buf
= f
.read(CHUNK_SIZE
)
67 "filename" : os
.path
.basename(self
.filename
),
68 "filesize" : os
.path
.getsize(self
.filename
),
69 "hash" : h
.hexdigest(),
72 upload_id
= self
.transport
.get("/uploads/create", data
=data
)
73 log
.debug("Got upload id: %s" % upload_id
)
77 def send_file(self
, upload_id
, progress_callback
=None):
79 Sends the file content to the server.
81 The data is splitted into chunks, which are
82 sent one after an other.
84 with
open(self
.filename
, "rb") as f
:
86 chunk_size
= CHUNK_SIZE
88 # Count the already transmitted bytes.
92 chunk
= f
.read(chunk_size
)
96 log
.debug("Got chunk of %s bytes" % len(chunk
))
98 # Save the time when we started to send this bit.
99 time_started
= time
.time()
101 # Send the chunk to the server.
102 self
.send_chunk(upload_id
, chunk
)
104 # Save the duration.time after the chunk has been transmitted
105 # and adjust chunk size to send one chunk per second.
106 duration
= time
.time() - time_started
107 chunk_size
= int(chunk_size
/ duration
)
109 # Never let chunk_size drop under CHUNK_SIZE:
110 if chunk_size
< CHUNK_SIZE
:
111 chunk_size
= CHUNK_SIZE
113 # Add up the send amount of data.
114 transferred
+= len(chunk
)
115 if progress_callback
:
116 progress_callback(transferred
)
118 def send_chunk(self
, upload_id
, data
):
120 Sends a piece of the file to the server.
122 # Calculate checksum over the chunk data.
123 h
= hashlib
.new("sha512")
125 chksum
= h
.hexdigest()
127 # Encode data in base64.
128 data
= base64
.b64encode(data
)
130 # Send chunk data to the server.
131 self
.transport
.post("/uploads/%s/sendchunk" % upload_id
,
132 data
={ "chksum" : chksum
, "data" : data
})
134 def destroy_upload(self
, upload_id
):
136 Destroys the upload on the server.
138 self
.transport
.get("/uploads/%s/destroy" % upload_id
)
140 def finish_upload(self
, upload_id
):
142 Signals to the server, that the upload has finished.
144 self
.transport
.get("/uploads/%s/finished" % upload_id
)
149 # Create a progress bar.
150 progress
= pakfire
.util
.make_progress(
151 os
.path
.basename(self
.filename
), os
.path
.getsize(self
.filename
), speed
=True, eta
=True,
156 upload_id
= self
.get_upload_id()
158 # Send the file content.
160 self
.send_file(upload_id
, progress_callback
=progress
.update
)
162 self
.send_file(upload_id
)
168 # Remove broken upload from server.
170 self
.destroy_upload(upload_id
)
172 # XXX catch fatal errors
179 # If no exception was raised, the upload
181 self
.finish_upload(upload_id
)
183 # Return the upload id so some code can actually do something
184 # with the file on the server.
188 class PakfireHubTransport(object):
190 Connection to the pakfire hub.
193 def __init__(self
, config
):
196 # Create connection to the hub.
197 self
.grabber
= pakfire
.downloader
.PakfireGrabber(
198 self
.config
, prefix
=self
.url
,
204 Construct a right URL out of the given
205 server, username and password.
207 Basicly this just adds the credentials
211 server
, username
, password
= self
.config
.get_hub_credentials()
213 # Parse the given URL.
214 url
= urlparse
.urlparse(server
)
215 assert url
.scheme
in ("http", "https")
218 ret
= "%s://" % url
.scheme
220 # Add credentials if provided.
221 if username
and password
:
222 ret
+= "%s:%s@" % (username
, password
)
224 # Add path components.
229 def one_request(self
, url
, **kwargs
):
231 return self
.grabber
.urlread(url
, **kwargs
)
233 except urlgrabber
.grabber
.URLGrabError
, e
:
236 raise TransportConnectionTimeoutError
, e
238 # Handle common HTTP errors
242 raise TransportConnectionProxyError
, url
244 raise TransportConnectionDNSError
, url
246 raise TransportConnectionResetError
, url
248 raise TransportConnectionWriteError
, url
250 raise TransportConnectionReadError
, url
254 raise TransportSSLCertificateExpiredError
, url
258 raise TransportForbiddenError
, url
260 raise TransportNotFoundError
, url
262 raise TransportInternalServerError
, url
264 raise TransportBadGatewayError
, url
266 raise TransportConnectionTimeoutError
, url
268 # All other exceptions...
271 def request(self
, url
, tries
=None, **kwargs
):
272 # tries = None implies wait infinitely
274 while tries
or tries
is None:
279 return self
.one_request(url
, **kwargs
)
281 # 500 - Internal Server Error
282 except TransportInternalServerError
, e
:
283 log
.exception("%s" % e
.__class
__.__name
__)
285 # Wait a minute before trying again.
288 # Retry on connection problems.
289 except TransportConnectionError
, e
:
290 log
.exception("%s" % e
.__class
__.__name
__)
292 # Wait for 10 seconds.
295 raise TransportMaxTriesExceededError
297 def escape_args(self
, **kwargs
):
298 return urllib
.urlencode(kwargs
)
300 def get(self
, url
, data
={}, **kwargs
):
302 Sends a HTTP GET request to the given URL.
304 All given keyword arguments are considered as form data.
306 params
= self
.escape_args(**data
)
309 url
= "%s?%s" % (url
, params
)
311 return self
.request(url
, **kwargs
)
313 def post(self
, url
, data
={}, **kwargs
):
315 Sends a HTTP POST request to the given URL.
317 All keyword arguments are considered as form data.
319 params
= self
.escape_args(**data
)
325 return self
.request(url
, **kwargs
)
327 def upload_file(self
, filename
):
329 Uploads the given file to the server.
331 uploader
= PakfireHubTransportUploader(self
, filename
)
332 upload_id
= uploader
.run()
336 def get_json(self
, *args
, **kwargs
):
337 res
= self
.get(*args
, **kwargs
)
341 return json
.loads(res
)
347 No operation. Just to check if the connection is
348 working. Returns a random number.
350 return self
.get("/noop")
352 def test_code(self
, error_code
):
353 assert error_code
>= 100 and error_code
<= 999
355 self
.get("/error/test/%s" % error_code
)
359 def build_create(self
, filename
, build_type
, arches
=None, distro
=None):
361 Create a new build on the hub.
363 assert build_type
in ("scratch", "release")
365 # XXX Check for permission to actually create a build.
367 # Upload the source file to the server.
368 upload_id
= self
.upload_file(filename
)
371 "arches" : ",".join(arches
or []),
372 "build_type" : build_type
,
373 "distro" : distro
or "",
374 "upload_id" : upload_id
,
377 # Then create the build.
378 build_id
= self
.get("/builds/create", data
=data
)
380 return build_id
or None
382 def build_get(self
, build_uuid
):
383 return self
.get_json("/builds/%s" % build_uuid
)
387 def job_get(self
, job_uuid
):
388 return self
.get_json("/jobs/%s" % job_uuid
)
392 def package_get(self
, package_uuid
):
393 return self
.get_json("/packages/%s" % package_uuid
)