From 02f3d8aab3d0110163470dc4cb151d2f472eb52f Mon Sep 17 00:00:00 2001
From: Aleksey Lim <alsroot@member.fsf.org>
Date: Sat, 19 Sep 2009 17:49:13 +0000
Subject: Cancel updating process in Update control panel component #1361
---
extensions/cpsection/updater/backends/aslo.py | 49 ++++++---------
extensions/cpsection/updater/model.py | 81 +++++++++++++++++++++----
extensions/cpsection/updater/view.py | 78 ++++++++++++++++++------
3 files changed, 147 insertions(+), 61 deletions(-)
diff --git a/extensions/cpsection/updater/backends/aslo.py b/extensions/cpsection/updater/backends/aslo.py
index 5f257f9..79162fc 100644
a
|
b
|
_FIND_SIZE = './/{http://www.mozilla.org/2004/em-rdf#}updateSize' |
64 | 64 | |
65 | 65 | _UPDATE_PATH = 'http://activities.sugarlabs.org/services/update-aslo.php' |
66 | 66 | |
67 | | _fetcher = None |
68 | 67 | |
69 | | |
70 | | class _UpdateFetcher(object): |
| 68 | class Checker(object): |
71 | 69 | |
72 | 70 | _CHUNK_SIZE = 10240 |
73 | 71 | |
… |
… |
class _UpdateFetcher(object): |
86 | 84 | self._stream = None |
87 | 85 | self._xml_data = '' |
88 | 86 | self._bundle = bundle |
| 87 | self.cancellable = gio.Cancellable() |
89 | 88 | |
90 | | self._file.read_async(self.__file_read_async_cb) |
| 89 | self._file.read_async( |
| 90 | callback=self.__file_read_async_cb, |
| 91 | cancellable=self.cancellable) |
91 | 92 | |
92 | 93 | def __file_read_async_cb(self, gfile, result): |
93 | 94 | try: |
94 | 95 | self._stream = self._file.read_finish(result) |
95 | 96 | except: |
96 | | global _fetcher |
97 | | _fetcher = None |
98 | 97 | self._completion_cb(None, None, None, None, traceback.format_exc()) |
99 | 98 | return |
100 | 99 | |
101 | | self._stream.read_async(self._CHUNK_SIZE, self.__stream_read_async_cb) |
| 100 | self._stream.read_async( |
| 101 | count=self._CHUNK_SIZE, |
| 102 | callback=self.__stream_read_async_cb, |
| 103 | cancellable=self.cancellable) |
102 | 104 | |
103 | 105 | def __stream_read_async_cb(self, stream, result): |
| 106 | if self.cancellable.is_cancelled(): |
| 107 | self._completion_cb(self._bundle, None, None, None, |
| 108 | 'Fetching was canceled') |
| 109 | return |
| 110 | |
104 | 111 | xml_data = self._stream.read_finish(result) |
105 | 112 | if xml_data is None: |
106 | | global _fetcher |
107 | | _fetcher = None |
108 | 113 | self._completion_cb(self._bundle, None, None, None, |
109 | 114 | 'Error reading update information for %s from ' |
110 | 115 | 'server.' % self._bundle.get_bundle_id()) |
… |
… |
class _UpdateFetcher(object): |
113 | 118 | self._process_result() |
114 | 119 | else: |
115 | 120 | self._xml_data += xml_data |
116 | | self._stream.read_async(self._CHUNK_SIZE, |
117 | | self.__stream_read_async_cb) |
| 121 | self._stream.read_async( |
| 122 | count=self._CHUNK_SIZE, |
| 123 | callback=self.__stream_read_async_cb, |
| 124 | cancellable=self.cancellable) |
118 | 125 | |
119 | 126 | def _process_result(self): |
120 | 127 | document = XML(self._xml_data) |
… |
… |
class _UpdateFetcher(object): |
140 | 147 | logging.error(traceback.format_exc()) |
141 | 148 | size = 0 |
142 | 149 | |
143 | | global _fetcher |
144 | | _fetcher = None |
145 | | self._completion_cb(self._bundle, version, link, size, None) |
146 | | |
147 | | |
148 | | def fetch_update_info(bundle, completion_cb): |
149 | | '''Queries the server for a newer version of the ActivityBundle. |
150 | | |
151 | | completion_cb receives bundle, version, link, size and possibly an error |
152 | | message: |
153 | | |
154 | | def completion_cb(bundle, version, link, size, error_message): |
155 | | ''' |
156 | | global _fetcher |
157 | | |
158 | | if _fetcher is not None: |
159 | | raise RuntimeError('Multiple simultaneous requests are not supported') |
160 | | |
161 | | _fetcher = _UpdateFetcher(bundle, completion_cb) |
| 150 | self._completion_cb(self._bundle, version, link, size, False) |
diff --git a/extensions/cpsection/updater/model.py b/extensions/cpsection/updater/model.py
index 102edea..b3b150b 100755
a
|
b
|
class UpdateModel(gobject.GObject): |
59 | 59 | self._bundles_to_check = None |
60 | 60 | self._bundles_to_update = None |
61 | 61 | self._total_bundles_to_update = 0 |
| 62 | self._bundles_updated = 0 |
62 | 63 | self._downloader = None |
| 64 | self._checker = None |
| 65 | |
| 66 | def cancel(self): |
| 67 | logging.debug('Cancel updating') |
| 68 | |
| 69 | if self._checker is not None: |
| 70 | self._checker.cancellable.cancel() |
| 71 | if self._downloader is not None: |
| 72 | self._downloader.cancellable.cancel() |
63 | 73 | |
64 | 74 | def check_updates(self): |
65 | 75 | self.updates = [] |
… |
… |
class UpdateModel(gobject.GObject): |
68 | 78 | self._check_next_update() |
69 | 79 | |
70 | 80 | def _check_next_update(self): |
| 81 | assert(self._checker is None) |
| 82 | |
71 | 83 | total = len(bundleregistry.get_registry()) |
72 | 84 | current = total - len(self._bundles_to_check) |
73 | 85 | |
… |
… |
class UpdateModel(gobject.GObject): |
75 | 87 | self.emit('progress', UpdateModel.ACTION_CHECKING, bundle.get_name(), |
76 | 88 | current, total) |
77 | 89 | |
78 | | aslo.fetch_update_info(bundle, self.__check_completed_cb) |
| 90 | self._checker = aslo.Checker(bundle, self.__check_completed_cb) |
79 | 91 | |
80 | 92 | def __check_completed_cb(self, bundle, version, link, size, error_message): |
81 | 93 | if error_message is not None: |
82 | 94 | logging.error('Error getting update information from server:\n' |
83 | 95 | '%s' % error_message) |
84 | 96 | |
| 97 | try : |
| 98 | if self._checker.cancellable.is_cancelled(): |
| 99 | self.emit('progress', UpdateModel.ACTION_CHECKING, None, 0, 0) |
| 100 | return |
| 101 | finally: |
| 102 | self._checker = None |
| 103 | |
85 | 104 | if version is not None and version > bundle.get_activity_version(): |
86 | 105 | self.updates.append(BundleUpdate(bundle, version, link, size)) |
87 | 106 | |
… |
… |
class UpdateModel(gobject.GObject): |
103 | 122 | self._bundles_to_update.append(bundle_update) |
104 | 123 | |
105 | 124 | self._total_bundles_to_update = len(self._bundles_to_update) |
| 125 | self._bundles_updated = 0 |
| 126 | |
106 | 127 | self._download_next_update() |
107 | 128 | |
108 | 129 | def _download_next_update(self): |
| 130 | assert(self._downloader is None) |
| 131 | |
109 | 132 | bundle_update = self._bundles_to_update.pop() |
110 | 133 | |
111 | 134 | total = self._total_bundles_to_update * 2 |
… |
… |
class UpdateModel(gobject.GObject): |
133 | 156 | self._downloader = None |
134 | 157 | |
135 | 158 | def __downloader_error_cb(self, downloader, error_message): |
| 159 | try : |
| 160 | if self._downloader.cancellable.is_cancelled(): |
| 161 | self.emit('progress', UpdateModel.ACTION_DOWNLOADING, |
| 162 | None, 0, 0) |
| 163 | return |
| 164 | finally: |
| 165 | if os.path.isfile(self._downloader.get_local_file_path()): |
| 166 | os.unlink(self._downloader.get_local_file_path()) |
| 167 | self._downloader = None |
| 168 | |
136 | 169 | logging.error('Error downloading update:\n%s', error_message) |
137 | 170 | |
138 | 171 | total = self._total_bundles_to_update * 2 |
… |
… |
class UpdateModel(gobject.GObject): |
145 | 178 | |
146 | 179 | def _install_update(self, bundle_update, local_file_path): |
147 | 180 | |
| 181 | self._bundles_updated += 1 |
148 | 182 | total = self._total_bundles_to_update * 2 |
149 | 183 | current = total - len(self._bundles_to_update) * 2 - 1 |
150 | 184 | |
… |
… |
class UpdateModel(gobject.GObject): |
175 | 209 | gobject.idle_add(self._download_next_update) |
176 | 210 | |
177 | 211 | def get_total_bundles_to_update(self): |
178 | | return self._total_bundles_to_update |
| 212 | return self._bundles_updated |
179 | 213 | |
180 | 214 | |
181 | 215 | class BundleUpdate(object): |
… |
… |
class _Downloader(gobject.GObject): |
208 | 242 | self._input_file = gio.File(bundle_update.link) |
209 | 243 | self._output_file = None |
210 | 244 | self._downloaded_size = 0 |
| 245 | self.cancellable = gio.Cancellable() |
211 | 246 | |
212 | | self._input_file.read_async(self.__file_read_async_cb) |
| 247 | self._input_file.read_async( |
| 248 | callback=self.__file_read_async_cb, |
| 249 | cancellable=self.cancellable) |
213 | 250 | |
214 | 251 | def __file_read_async_cb(self, gfile, result): |
215 | 252 | try: |
… |
… |
class _Downloader(gobject.GObject): |
217 | 254 | except: |
218 | 255 | self.emit('error', traceback.format_exc()) |
219 | 256 | return |
220 | | |
| 257 | |
221 | 258 | temp_file_path = self._get_temp_file_path(self.bundle_update.link) |
222 | 259 | self._output_file = gio.File(temp_file_path) |
223 | 260 | self._output_stream = self._output_file.create() |
224 | 261 | |
225 | | self._input_stream.read_async(self._CHUNK_SIZE, self.__read_async_cb, |
226 | | gobject.PRIORITY_LOW) |
| 262 | self._input_stream.read_async( |
| 263 | count=self._CHUNK_SIZE, |
| 264 | callback=self.__read_async_cb, |
| 265 | io_priority=gobject.PRIORITY_LOW, |
| 266 | cancellable=self.cancellable) |
227 | 267 | |
228 | 268 | def __read_async_cb(self, input_stream, result): |
| 269 | if self.cancellable.is_cancelled(): |
| 270 | self._input_stream.close() |
| 271 | self._output_stream.close() |
| 272 | self.emit('error', 'downloading canceled') |
| 273 | return |
| 274 | |
229 | 275 | data = input_stream.read_finish(result) |
230 | 276 | |
231 | 277 | if data is None: |
… |
… |
class _Downloader(gobject.GObject): |
237 | 283 | self._check_if_finished_writing() |
238 | 284 | else: |
239 | 285 | self._pending_buffers.append(data) |
240 | | self._input_stream.read_async(self._CHUNK_SIZE, |
241 | | self.__read_async_cb, |
242 | | gobject.PRIORITY_LOW) |
| 286 | self._input_stream.read_async( |
| 287 | count=self._CHUNK_SIZE, |
| 288 | callback=self.__read_async_cb, |
| 289 | io_priority=gobject.PRIORITY_LOW, |
| 290 | cancellable=self.cancellable) |
243 | 291 | |
244 | 292 | self._write_next_buffer() |
245 | 293 | |
246 | 294 | def __write_async_cb(self, output_stream, result, user_data): |
| 295 | if self.cancellable.is_cancelled(): |
| 296 | return |
| 297 | |
247 | 298 | count = output_stream.write_finish(result) |
248 | 299 | |
249 | 300 | self._downloaded_size += count |
… |
… |
class _Downloader(gobject.GObject): |
260 | 311 | data = self._pending_buffers.pop(0) |
261 | 312 | # TODO: we pass the buffer as user_data because of |
262 | 313 | # http://bugzilla.gnome.org/show_bug.cgi?id=564102 |
263 | | self._output_stream.write_async(data, self.__write_async_cb, |
264 | | gobject.PRIORITY_LOW, |
265 | | user_data=data) |
| 314 | self._output_stream.write_async( |
| 315 | buffer=data, |
| 316 | callback=self.__write_async_cb, |
| 317 | io_priority=gobject.PRIORITY_LOW, |
| 318 | cancellable=self.cancellable, |
| 319 | user_data=data) |
266 | 320 | |
267 | 321 | def _get_temp_file_path(self, uri): |
268 | 322 | # TODO: Should we use the HTTP headers for the file name? |
… |
… |
class _Downloader(gobject.GObject): |
270 | 324 | urlparse(uri) |
271 | 325 | path = os.path.basename(path) |
272 | 326 | |
| 327 | if not os.path.exists(env.get_user_activities_path()): |
| 328 | os.makedirs(env.get_user_activities_path()) |
| 329 | |
273 | 330 | base_name, extension_ = os.path.splitext(path) |
274 | 331 | fd, file_path = tempfile.mkstemp(dir=env.get_user_activities_path(), |
275 | 332 | prefix=base_name, suffix='.xo') |
diff --git a/extensions/cpsection/updater/view.py b/extensions/cpsection/updater/view.py
index 9a77743..5e6a33a 100644
a
|
b
|
class ActivityUpdater(SectionView): |
66 | 66 | |
67 | 67 | self._update_box = None |
68 | 68 | self._progress_pane = None |
| 69 | self._finish_box = None |
69 | 70 | |
70 | 71 | self._refresh() |
71 | 72 | |
72 | | def _switch_to_update_box(self): |
73 | | if self._update_box in self.get_children(): |
74 | | return |
| 73 | def undo(self): |
| 74 | self._model.cancel() |
75 | 75 | |
| 76 | def _clear_center(self): |
76 | 77 | if self._progress_pane in self.get_children(): |
77 | 78 | self.remove(self._progress_pane) |
78 | 79 | self._progress_pane = None |
79 | 80 | |
| 81 | if self._update_box in self.get_children(): |
| 82 | self.remove(self._update_box) |
| 83 | self._update_box = None |
| 84 | |
| 85 | if self._finish_box in self.get_children(): |
| 86 | self.remove(self._finish_box) |
| 87 | self._finish_box = None |
| 88 | |
| 89 | def _switch_to_update_box(self): |
| 90 | if self._update_box in self.get_children(): |
| 91 | return |
| 92 | |
| 93 | self._clear_center() |
| 94 | |
80 | 95 | if self._update_box is None: |
81 | 96 | self._update_box = UpdateBox(self._model) |
82 | 97 | self._update_box.refresh_button.connect('clicked', |
… |
… |
class ActivityUpdater(SectionView): |
91 | 106 | if self._progress_pane in self.get_children(): |
92 | 107 | return |
93 | 108 | |
94 | | if self._update_box in self.get_children(): |
95 | | self.remove(self._update_box) |
96 | | self._update_box = None |
| 109 | self._clear_center() |
97 | 110 | |
98 | 111 | if self._progress_pane is None: |
99 | 112 | self._progress_pane = ProgressPane() |
| 113 | self._progress_pane.cancel_button.connect('clicked', |
| 114 | self.__cancel_button_clicked_cb) |
100 | 115 | |
101 | 116 | self.pack_start(self._progress_pane, expand=True, fill=False) |
102 | 117 | self._progress_pane.show() |
103 | 118 | |
104 | | def _clear_center(self): |
105 | | if self._progress_pane in self.get_children(): |
106 | | self.remove(self._progress_pane) |
107 | | self._progress_pane = None |
| 119 | def __cancel_button_clicked_cb(self, button): |
| 120 | self._model.cancel() |
108 | 121 | |
109 | | if self._update_box in self.get_children(): |
110 | | self.remove(self._update_box) |
111 | | self._update_box = None |
| 122 | def _switch_to_finish_box(self): |
| 123 | if self._finish_box in self.get_children(): |
| 124 | return |
| 125 | |
| 126 | self._clear_center() |
| 127 | |
| 128 | if self._finish_box is None: |
| 129 | self._finish_box = FinishBox() |
| 130 | self._finish_box.refresh_button.connect('clicked', |
| 131 | self.__refresh_button_clicked_cb) |
| 132 | |
| 133 | self.pack_start(self._finish_box, expand=True, fill=True) |
| 134 | self._finish_box.show() |
112 | 135 | |
113 | 136 | def __progress_cb(self, model, action, bundle_name, current, total): |
114 | 137 | if current == total and action == UpdateModel.ACTION_CHECKING: |
… |
… |
class ActivityUpdater(SectionView): |
141 | 164 | top_message = gobject.markup_escape_text(top_message) |
142 | 165 | |
143 | 166 | self._top_label.set_markup('<big>%s</big>' % top_message) |
144 | | |
| 167 | |
145 | 168 | if not available_updates: |
146 | | self._clear_center() |
| 169 | self._switch_to_finish_box() |
147 | 170 | else: |
148 | 171 | self._switch_to_update_box() |
149 | 172 | self._update_box.refresh() |
… |
… |
class ActivityUpdater(SectionView): |
167 | 190 | top_message = top_message % installed_updates |
168 | 191 | top_message = gobject.markup_escape_text(top_message) |
169 | 192 | self._top_label.set_markup('<big>%s</big>' % top_message) |
170 | | self._clear_center() |
| 193 | self._switch_to_finish_box() |
171 | 194 | |
172 | 195 | |
173 | 196 | class ProgressPane(gtk.VBox): |
… |
… |
class ProgressPane(gtk.VBox): |
195 | 218 | self.pack_start(alignment_box) |
196 | 219 | alignment_box.show() |
197 | 220 | |
198 | | cancel_button = gtk.Button(stock=gtk.STOCK_CANCEL) |
199 | | alignment_box.add(cancel_button) |
200 | | cancel_button.show() |
| 221 | self.cancel_button = gtk.Button(stock=gtk.STOCK_CANCEL) |
| 222 | alignment_box.add(self.cancel_button) |
| 223 | self.cancel_button.show() |
201 | 224 | |
202 | 225 | def set_message(self, message): |
203 | 226 | self._label.set_text(message) |
… |
… |
class UpdateBox(gtk.VBox): |
280 | 303 | return bundles_to_update |
281 | 304 | |
282 | 305 | |
| 306 | class FinishBox(gtk.VBox): |
| 307 | |
| 308 | def __init__(self): |
| 309 | gtk.VBox.__init__(self) |
| 310 | |
| 311 | self.set_spacing(style.DEFAULT_PADDING) |
| 312 | |
| 313 | bottom_box = gtk.HBox() |
| 314 | bottom_box.set_spacing(style.DEFAULT_SPACING) |
| 315 | self.pack_end(bottom_box, expand=False) |
| 316 | bottom_box.show() |
| 317 | |
| 318 | self.refresh_button = gtk.Button(stock=gtk.STOCK_REFRESH) |
| 319 | bottom_box.pack_end(self.refresh_button, expand=False) |
| 320 | self.refresh_button.show() |
| 321 | |
| 322 | |
283 | 323 | class UpdateList(gtk.TreeView): |
284 | 324 | |
285 | 325 | def __init__(self, model): |