]>
Commit | Line | Data |
---|---|---|
96a8f2a4 MT |
1 | #!/usr/bin/python3 |
2 | ############################################################################### | |
3 | # # | |
4 | # Pakfire - The IPFire package management system # | |
5 | # Copyright (C) 2013 Pakfire development team # | |
6 | # # | |
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. # | |
11 | # # | |
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. # | |
16 | # # | |
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/>. # | |
19 | # # | |
20 | ############################################################################### | |
21 | ||
22 | import base64 | |
23 | import hashlib | |
24 | import logging | |
25 | import math | |
26 | import os.path | |
27 | import time | |
28 | ||
29 | from . import http | |
30 | ||
31 | from .constants import * | |
32 | ||
33 | log = logging.getLogger("pakfire.hub") | |
34 | log.propagate = 1 | |
35 | ||
36 | class Hub(object): | |
37 | def __init__(self, huburl, username, password): | |
38 | self.username = username | |
39 | self.password = password | |
96a8f2a4 MT |
40 | |
41 | # Initialise the HTTP client | |
42 | self.http = http.Client(baseurl=huburl) | |
43 | ||
44 | def _request(self, *args, **kwargs): | |
45 | """ | |
46 | Wrapper function around the HTTP Client request() | |
47 | function that adds authentication, etc. | |
48 | """ | |
49 | kwargs.update({ | |
50 | "auth" : (self.username, self.password), | |
51 | }) | |
52 | ||
53 | return self.http.request(*args, **kwargs) | |
54 | ||
55 | # Test functions | |
56 | ||
57 | def test(self): | |
58 | """ | |
59 | Tests the connection | |
60 | """ | |
7773b2ce | 61 | return self._request("/noop", decode="ascii") |
96a8f2a4 MT |
62 | |
63 | def test_error(self, code): | |
64 | assert code >= 100 and code <= 999 | |
65 | ||
66 | self._request("/error/test/%s" % code) | |
67 | ||
68 | # Build actions | |
69 | ||
70 | def get_build(self, uuid): | |
71 | return self._request("/builds/%s" % uuid, decode="json") | |
72 | ||
73 | def create_build(self, path, type, arches=None, distro=None): | |
74 | """ | |
75 | Create a new build on the hub | |
76 | """ | |
77 | assert type in ("scratch", "release") | |
78 | ||
79 | # XXX Check for permission to actually create a build | |
80 | ||
81 | # Upload the souce file to the server | |
82 | upload_id = self.upload_file(path) | |
83 | ||
84 | data = { | |
85 | "arches" : ",".join(arches or []), | |
86 | "build_type" : type, | |
87 | "distro" : distro or "", | |
88 | "upload_id" : upload_id, | |
89 | } | |
90 | ||
7773b2ce | 91 | return self._request("/builds/create", data=data, decode="ascii") |
96a8f2a4 MT |
92 | |
93 | # Job actions | |
94 | ||
95 | def get_job(self, uuid): | |
96 | return self._request("/jobs/%s" % uuid, decode="json") | |
97 | ||
98 | # Package actions | |
99 | ||
100 | def get_package(self, uuid): | |
101 | return self._request("/packages/%s" % uuid, decode="json") | |
102 | ||
103 | # File uploads | |
104 | ||
105 | def upload_file(self, path): | |
106 | uploader = FileUploader(self, path) | |
107 | ||
108 | return uploader.upload() | |
109 | ||
110 | ||
111 | class FileUploader(object): | |
112 | """ | |
113 | Handles file uploads to the Pakfire Hub | |
114 | """ | |
115 | def __init__(self, hub, path): | |
116 | self.hub = hub | |
117 | self.path = path | |
118 | ||
119 | @property | |
120 | def filename(self): | |
121 | """ | |
122 | Returns the basename of the uploaded file | |
123 | """ | |
124 | return os.path.basename(self.path) | |
125 | ||
126 | @property | |
127 | def filesize(self): | |
128 | """ | |
129 | The filesize of the uploaded file | |
130 | """ | |
131 | return os.path.getsize(self.path) | |
132 | ||
133 | @staticmethod | |
134 | def _make_checksum(algo, path): | |
135 | h = hashlib.new(algo) | |
136 | ||
137 | with open(path, "rb") as f: | |
138 | while True: | |
139 | buf = f.read(CHUNK_SIZE) | |
140 | if not buf: | |
141 | break | |
142 | ||
143 | h.update(buf) | |
144 | ||
145 | return h.hexdigest() | |
146 | ||
147 | def _get_upload_id(self): | |
148 | """ | |
149 | Sends some basic information to the hub | |
150 | and requests an upload id. | |
151 | """ | |
152 | data = { | |
153 | "filename" : self.filename, | |
154 | "filesize" : self.filesize, | |
155 | "hash" : self._make_checksum("sha1", self.path), | |
156 | } | |
157 | ||
7773b2ce | 158 | return self.hub._request("/uploads/create", method="GET", decode="ascii", data=data) |
96a8f2a4 MT |
159 | |
160 | def _send_chunk(self, upload_id, chunk): | |
161 | """ | |
162 | Sends a chunk at a time | |
163 | """ | |
164 | # Compute the SHA512 checksum of this chunk | |
165 | h = hashlib.new("sha512") | |
166 | h.update(chunk) | |
167 | ||
168 | # Encode data in base64 | |
169 | data = base64.b64encode(chunk) | |
170 | ||
171 | # Send chunk to the server | |
172 | self.hub._request("/uploads/%s/sendchunk" % upload_id, method="POST", | |
173 | data={ "chksum" : h.hexdigest(), "data" : data }) | |
174 | ||
175 | return len(chunk) | |
176 | ||
177 | def upload(self): | |
178 | """ | |
179 | Main function which runs the upload | |
180 | """ | |
181 | # Borrow progressbar from downloader | |
182 | p = self.hub.http._make_progressbar(message=self.filename, value_max=self.filesize) | |
183 | ||
184 | with p: | |
185 | # Request an upload ID | |
186 | upload_id = self._get_upload_id() | |
187 | assert upload_id | |
188 | ||
189 | log.debug("Starting upload with id %s" % upload_id) | |
190 | ||
191 | # Initial chunk size | |
192 | chunk_size = CHUNK_SIZE | |
193 | ||
194 | try: | |
195 | with open(self.path, "rb") as f: | |
196 | while True: | |
197 | chunk = f.read(chunk_size) | |
198 | if not chunk: | |
199 | break | |
200 | ||
201 | # Save the time when we started to send this bit | |
202 | time_started = time.time() | |
203 | ||
204 | # Send the chunk to the server | |
205 | self._send_chunk(upload_id, chunk) | |
206 | ||
207 | # Determine the size of the next chunk | |
208 | duration = time_started - time.time() | |
209 | chunk_size = math.ceil(chunk_size / duration) | |
210 | ||
211 | # Never let chunk_size drop under CHUNK_SIZE | |
212 | if chunk_size < CHUNK_SIZE: | |
213 | chunk_size = CHUNK_SIZE | |
214 | ||
215 | # Update progressbar | |
914d9ab0 | 216 | p.increment(len(chunk)) |
96a8f2a4 MT |
217 | |
218 | # Catch any unhandled exception here, tell the hub to delete the | |
219 | # file and raise the original exception | |
220 | except Exception as e: | |
221 | self.hub._request("/uploads/%s/destroy" % upload_id) | |
222 | ||
223 | raise | |
224 | ||
225 | # If all went well, we finish the upload | |
226 | else: | |
227 | self.hub._request("/uploads/%s/finished" % upload_id) | |
228 | ||
229 | # Return the upload ID if the upload was successful | |
230 | return upload_id |