Merge lp:~humpolec-team/humpolec/UbuntuInstaller_resume_download into lp:humpolec

Proposed by Yuan-Chen Cheng
Status: Merged
Merged at revision: 31
Proposed branch: lp:~humpolec-team/humpolec/UbuntuInstaller_resume_download
Merge into: lp:humpolec
Diff against target: 458 lines (+191/-44)
3 files modified
src/com/canonical/ubuntu/installer/InstallActivity.java (+0/-2)
src/com/canonical/ubuntu/installer/UbuntuInstallService.java (+169/-39)
src/com/canonical/ubuntu/installer/VersionInfo.java (+22/-3)
To merge this branch: bzr merge lp:~humpolec-team/humpolec/UbuntuInstaller_resume_download
Reviewer Review Type Date Requested Status
Rex Tsai Approve
Yuan-Chen Cheng Needs Resubmitting
Review via email: mp+199993@code.launchpad.net

Description of the change

Resume download:
1. add a parameter to set whether it's a partial downloaded and
   last downloaded size (for storage size calculation)
2. don't delete download for cancel download or download failed.
   (due to IO error, mostly network error)
3. only reuse file with the same channel as revision.

To post a comment you must log in.
36. By Yuan-Chen Cheng

spell check and default downloaded size to 0 (full) for backword compatibility

Revision history for this message
Rex Tsai (chihchun) wrote :

* toDeleteOld flag is not reuseed, code can be cleaner without it.
* What will happen if network is blocked, and user pressed cancel button?

review: Needs Information
37. By Yuan-Chen Cheng

add braces to indicate that toDeleteOld is not used out of this block.

Revision history for this message
Yuan-Chen Cheng (ycheng-twn) wrote :

* add braces to indicate that toDeleteOld is not used out of this block.
* if network is blocked, we need to wait for it's time out and throw exception.

review: Needs Resubmitting
Revision history for this message
Rex Tsai (chihchun) wrote :

network timeout is nice to improved. :-)

review: Approve
Revision history for this message
Yuan-Chen Cheng (ycheng-twn) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/com/canonical/ubuntu/installer/InstallActivity.java'
2--- src/com/canonical/ubuntu/installer/InstallActivity.java 2013-12-20 08:52:08 +0000
3+++ src/com/canonical/ubuntu/installer/InstallActivity.java 2013-12-24 09:15:34 +0000
4@@ -409,8 +409,6 @@
5 }
6 Utils.showToast(context, reason);
7 updateInfoOnUiThread(reason);
8- // delete failed download
9- deleteDownload();
10 requestChannelList();
11 }
12 } else if (action.equals(UbuntuInstallService.VERSION_UPDATE)) {
13
14=== modified file 'src/com/canonical/ubuntu/installer/UbuntuInstallService.java'
15--- src/com/canonical/ubuntu/installer/UbuntuInstallService.java 2013-12-23 02:45:09 +0000
16+++ src/com/canonical/ubuntu/installer/UbuntuInstallService.java 2013-12-24 09:15:34 +0000
17@@ -196,13 +196,11 @@
18 };
19
20 class ECancelException extends Exception {
21- public ECancelException(){
22+ long mDownloadedSize;
23+
24+ public ECancelException(long downloadedSize){
25 super();
26- }
27-
28- public ECancelException(String string) {
29- // TODO Auto-generated constructor stub
30- super(string);
31+ mDownloadedSize = downloadedSize;
32 }
33 };
34
35@@ -269,8 +267,10 @@
36 updateInstallerState(InstallerState.DOWNLOADING);
37 result = doDownloadRelease(intent);
38 } else if (action.equals(CANCEL_DOWNLOAD)) {
39- // download should be already cancelled, now delete all the files
40- result = doRemoreDownload(intent);
41+ // download should be already cancelled, don't delete files for might resume latter
42+ // result = doRemoreDownload(intent);
43+ result = new Intent(SERVICE_STATE);
44+ result.putExtra(SERVICE_STATE_EXTRA_STATE, mInstallerState.ordinal());
45 } else if (action.equals(PAUSE_DOWNLOAD)) {
46 // TODO: handle download
47 } else if (action.equals(RESUME_DOWNLOAD)) {
48@@ -709,6 +709,7 @@
49 SharedPreferences.Editor editor = getSharedPreferences( SHARED_PREF, Context.MODE_PRIVATE).edit();
50
51 Intent result = new Intent(DOWNLOAD_RESULT);
52+ VersionInfo prevDownload = getDownloadVersion(this.getApplicationContext());
53 try {
54 File rootFolder = new File(mRootOfWorkPath);
55
56@@ -759,13 +760,25 @@
57 String.format("%s/%s",BASE_URL, URL_IMAGE_SIGNING),
58 };
59 String keyringsFilenames[] = new String[keyrings.length * 2];
60-
61+
62 // First delete old release if it exists
63- String s = deleteRelease();
64- if (s != null) {
65- // remove failed
66- return handleDownloadError(result, -1, s);
67+ {
68+ boolean toDeleteOld = true;
69+ if (prevDownload != null) {
70+ if (prevDownload.equals(jsonUrl, choosenRelease.version, releaseType) &&
71+ prevDownload.mDownloadedSize > 0) {
72+ toDeleteOld = false;
73+ }
74+ }
75+ if (toDeleteOld) {
76+ String s = deleteRelease();
77+ if (s != null) {
78+ // remove failed
79+ return handleDownloadError(result, -1, s);
80+ }
81+ }
82 }
83+
84 // make sure release folder exists
85 File release = new File(rootFolder,RELEASE_FOLDER);
86 release.mkdir();
87@@ -775,8 +788,11 @@
88 mProgress = 0;
89 broadcastProgress(mLastSignalledProgress, null);
90 mTotalSize = Utils.calculateDownloadSize(filesArray);
91-
92- boolean isStorageEnough = isStorageSpaceEnoughtBFDownload(mTotalSize);
93+ long neededSize = mTotalSize;
94+ if (prevDownload != null) {
95+ neededSize = mTotalSize - prevDownload.mDownloadedSize;
96+ }
97+ boolean isStorageEnough = isStorageSpaceEnoughBFDownload(neededSize);
98 if (! isStorageEnough) {
99 String msg = "Need more storage: ";
100 if (workPathInCache) {
101@@ -789,34 +805,63 @@
102 }
103
104 // mProgressSteps = mTotalDownloadSize / 100; // we want 1% steps
105+ long downloadedSize = 0;
106+ JsonChannelParser.File currentDownloadingFile = null;
107 try {
108 int i = 0;
109 for(String url : keyrings){
110- keyringsFilenames[i++] = doDownloadUrl(new URL(url),release);
111+ keyringsFilenames[i++] = doDownloadUrl(new URL(url), release);
112 // download signature
113- keyringsFilenames[i++] = doDownloadUrl(new URL(url+ASC_SUFFIX),release);
114+ keyringsFilenames[i++] = doDownloadUrl(new URL(url+ASC_SUFFIX), release);
115 }
116
117 // download all update images
118 i = 0;
119 for (JsonChannelParser.File file : filesArray){
120- updateFilenames[i] = doDownloadUrl(new URL(BASE_URL + file.path),release);
121+ URL url = new URL(BASE_URL + file.path);
122+ String fileName = URLUtil.guessFileName(url.toString(), null, null);
123+ File f = new File(release, fileName);
124+
125+ boolean fileNeedDownload = true;
126+ if (prevDownload != null) {
127+ long length = f.length();
128+ if (length == file.size) {
129+ fileNeedDownload = false;
130+ } else if (length > file.size) {
131+ f.delete();
132+ }
133+ }
134+ if (fileNeedDownload) {
135+ currentDownloadingFile = file;
136+ updateFilenames[i] = doDownloadFile(file, release);
137+ currentDownloadingFile = null;
138+ } else {
139+ updateFilenames[i] = fileName;
140+ mProgress += file.size;
141+ mLastSignalledProgress = (int) (mProgress * 100 / mTotalSize);
142+ broadcastProgress(mLastSignalledProgress, null);
143+ }
144
145 // check file size and check sum
146- File f = new File(release, updateFilenames[i]);
147 long length = f.length();
148 if (length != file.size) {
149 f.delete();
150 throw new ESumNotMatchException();
151 }
152+ broadcastProgress(mLastSignalledProgress, "Checksum Verifying: " + fileName);
153 String sha256sum = Utils.getSha256Sum(f);
154 if (! sha256sum.equals(file.checksum)) {
155+ broadcastProgress(mLastSignalledProgress, "Checksum Verify failed: " + fileName);
156 f.delete();
157 throw new ESumNotMatchException();
158 }
159+ broadcastProgress(mLastSignalledProgress, "Checksum Verified: " + fileName);
160+ downloadedSize += file.size;
161 i++;
162
163- updateFilenames[i++] = doDownloadUrl(new URL(BASE_URL + file.signature),release);
164+ // signature file size is not accounted for resume since it's quite small
165+ updateFilenames[i] = doDownloadFileSignature(file, release);
166+ i++;
167 }
168 } catch (MalformedURLException e) {
169 Log.e(TAG, "Failed to download release:", e);
170@@ -825,6 +870,26 @@
171 Log.e(TAG, "Failed to download release:", e);
172 return handleDownloadError(result, -1, "File not found");
173 } catch (IOException e){
174+ if (currentDownloadingFile != null) {
175+ try {
176+ URL url = new URL(BASE_URL + currentDownloadingFile.path);
177+ String fileName = URLUtil.guessFileName(url.toString(), null, null);
178+ File f = new File(release, fileName);
179+ downloadedSize += f.length();
180+ } catch (MalformedURLException e1) {
181+ // shouldn't happen for it should already happen.
182+ }
183+ }
184+
185+ if (downloadedSize > 0) {
186+ VersionInfo v = new VersionInfo(alias, jsonUrl, choosenRelease.description, choosenRelease.version,
187+ downloadedSize, releaseType);
188+ v.storeVersion(editor, PREF_KEY_DOWNLOADED_VERSION);
189+ } else {
190+ editor.putString(PREF_KEY_UPDATE_COMMAND, "");
191+ VersionInfo.storeEmptyVersion(editor, PREF_KEY_DOWNLOADED_VERSION);
192+ }
193+
194 Log.e(TAG, "Failed to download release:", e);
195 return handleDownloadError(result, -1, "IO Error");
196 } catch (ESumNotMatchException e) {
197@@ -832,6 +897,12 @@
198 return handleDownloadError(result, -1, "Download check sum error");
199 } catch (ECancelException e) {
200 // Download was cancelled by user
201+ downloadedSize += e.mDownloadedSize;
202+ if (downloadedSize > 0) {
203+ VersionInfo v = new VersionInfo(alias, jsonUrl, choosenRelease.description, choosenRelease.version,
204+ downloadedSize, releaseType);
205+ v.storeVersion(editor, PREF_KEY_DOWNLOADED_VERSION);
206+ }
207 return handleDownloadError(result, -2, "Download cancelled by user");
208 }
209
210@@ -899,13 +970,11 @@
211 }
212 }
213 // store update command
214- VersionInfo v = new VersionInfo(alias, jsonUrl, choosenRelease.description, choosenRelease.version, releaseType);
215+ VersionInfo v = new VersionInfo(alias, jsonUrl, choosenRelease.description, choosenRelease.version, 0, releaseType);
216 editor.putString(PREF_KEY_UPDATE_COMMAND, updateCommand.getAbsolutePath());
217 editor.putInt(PREF_KEY_ESTIMATED_CHECKPOINTS, estimatedCheckCount);
218 v.storeVersion(editor, PREF_KEY_DOWNLOADED_VERSION);
219 mProgress = 100;
220- editor.commit();
221-
222 } finally {
223 if (mWakeLock != null && mWakeLock.isHeld()) {
224 mWakeLock.release();
225@@ -918,39 +987,79 @@
226 private Intent handleDownloadError(Intent i, int res, String reason) {
227 i.putExtra(DOWNLOAD_RESULT_EXTRA_INT, res);
228 i.putExtra(DOWNLOAD_RESULT_EXTRA_STR, reason);
229- deleteRelease();
230 return i;
231 }
232
233- private String doDownloadUrl(URL url, File targerLocation) throws MalformedURLException,
234+ private String doDownloadFile(JsonChannelParser.File file, File targetLocation) throws MalformedURLException,
235+ FileNotFoundException, IOException, ECancelException {
236+ URL url = new URL(BASE_URL + file.path);
237+ return doDownloadUrl(url, targetLocation, true);
238+ }
239+
240+ private String doDownloadFileSignature(JsonChannelParser.File file, File targetLocation) throws MalformedURLException,
241+ FileNotFoundException, IOException, ECancelException {
242+ URL url = new URL(BASE_URL + file.signature);
243+ return doDownloadUrl(url, targetLocation);
244+ }
245+
246+ private String doDownloadUrl(URL url, File targertLocation) throws MalformedURLException,
247+ FileNotFoundException, IOException, ECancelException {
248+ return doDownloadUrl(url, targertLocation, false);
249+ }
250+
251+ private String doDownloadUrl(URL url, File targertLocation, boolean resume) throws MalformedURLException,
252 FileNotFoundException, IOException, ECancelException {
253 Log.v(TAG, "Downloading:" + url.toString());
254 URLConnection conn = url.openConnection();
255 String fileName = URLUtil.guessFileName(url.toString(), null, null);
256 // TODO: update progress accordingly
257- broadcastProgress(mLastSignalledProgress, "Downloading: " + fileName);
258- File file = new File(targerLocation, fileName);
259- if (file.exists() && file.isFile()) {
260+ broadcastProgress(mLastSignalledProgress, "Downloading: " + fileName);
261+ File file = new File(targertLocation, fileName);
262+ if ((! resume) && file.exists() && file.isFile()) {
263 file.delete();
264 }
265-
266- FileOutputStream output = new FileOutputStream(file);
267-
268+
269+ long resumePosition = 0;
270+ if (resume) {
271+ resumePosition = file.length();
272+ if (resumePosition > 0) {
273+ String rangeHeader = String.format(Locale.US, "bytes=%d-", resumePosition);
274+ conn.setRequestProperty("Range", rangeHeader);
275+ Log.i(TAG, String.format("Resuming download from %d bytes.", resumePosition));
276+ mProgress += resumePosition;
277+ }
278+ }
279+
280+ // resumePosition > 0 ==> append mode
281+ FileOutputStream output = new FileOutputStream(file, resumePosition > 0);
282+
283 InputStream input = conn.getInputStream();
284-
285+
286 byte[] buffer = new byte[1024];
287 int len = 0;
288-
289+
290 while ((len = input.read(buffer)) > 0) {
291 if (mIsCanceled) {
292 output.close();
293+ conn = null;
294 input.close();
295- throw new ECancelException("Cancelled");
296+ long flen = file.length();
297+ if (flen > 0) {
298+ try {
299+ file.delete();
300+ flen = 0;
301+ } catch (Exception e) {}
302+ }
303+ // don't account file that won't consider for resume !
304+ if (flen > 0 && resume) {
305+ throw new ECancelException(flen);
306+ }
307+ throw new ECancelException(0);
308 }
309 output.write(buffer, 0, len);
310 mProgress += len;
311 // shall we broadcast progress?
312- if ( mLastSignalledProgress < (mProgress * 100 / mTotalSize)) {
313+ if (mLastSignalledProgress < (mProgress * 100 / mTotalSize)) {
314 // update and signal new progress
315 mLastSignalledProgress = (int) (mProgress * 100 / mTotalSize);
316 broadcastProgress(mLastSignalledProgress, null);
317@@ -1023,7 +1132,7 @@
318 * @param downloadSize: download size from json. 0 means file already downloaded.
319 * @return true if stoarge size is ok to go.
320 */
321- private boolean isStorageSpaceEnoughtBFDownload(long downloadSize) {
322+ private boolean isStorageSpaceEnoughBFDownload(long downloadSize) {
323 long dataSizeRequired = INSTALL_SIZE_REQUIRED;
324
325 if (workPathInCache) {
326@@ -1052,9 +1161,26 @@
327 }
328 return null;
329 }
330+ /**
331+ * Internal helper function to get current DOWNLOAD_VERSION even download is partial
332+ * @param context
333+ * @return version info for download image.
334+ */
335+ private static VersionInfo getDownloadVersion(Context context) {
336+ return getVersionWithPrefKey(context, PREF_KEY_DOWNLOADED_VERSION);
337+ }
338
339- public static VersionInfo getDownloadedVersion(Context c) {
340- return getVersionWithPrefKey(c, PREF_KEY_DOWNLOADED_VERSION);
341+ /**
342+ * To get current DOWNLOAD_VERSION for completed download.
343+ * @param context
344+ * @return Version info for download-ed image.
345+ */
346+ public static VersionInfo getDownloadedVersion(Context context) {
347+ VersionInfo v = getVersionWithPrefKey(context, PREF_KEY_DOWNLOADED_VERSION);
348+ if (v != null) {
349+ if (v.mDownloadedSize == 0) return v;
350+ }
351+ return null;
352 }
353
354 public static VersionInfo getInstalledVersion(Context c) {
355@@ -1076,8 +1202,12 @@
356 */
357 public static boolean checkifReadyToInstall(Context context) {
358 SharedPreferences pref = context.getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE);
359+ VersionInfo versionInfo = getDownloadVersion(context);
360+ if (versionInfo == null) return false;
361+ if (versionInfo.getDownloadedSize() != 0) return false;
362+
363 String command = pref.getString(PREF_KEY_UPDATE_COMMAND, "");
364- if (!command.equals("")){
365+ if (!command.equals("")) {
366 File f = new File(command);
367 if (f.exists()) {
368 return true;
369
370=== modified file 'src/com/canonical/ubuntu/installer/VersionInfo.java'
371--- src/com/canonical/ubuntu/installer/VersionInfo.java 2013-12-16 12:19:31 +0000
372+++ src/com/canonical/ubuntu/installer/VersionInfo.java 2013-12-24 09:15:34 +0000
373@@ -13,6 +13,10 @@
374 private static final String JSON = "_json";
375 private static final String DESCRIPTION = "_description";
376 private static final String VERSION = "_version";
377+ // partial download: long
378+ // full-download: d_version is not empty and this value is 0
379+ // partial download: d_version is not empty and this value is > 0
380+ private static final String DOWNLOADED_SIZE = "_downloaded-size";
381 private static final String TYPE = "type";
382
383 public enum ReleaseType {
384@@ -44,6 +48,7 @@
385 public final String mChannelJson;
386 public final String mDescription;
387 public final int mVersion;
388+ public final long mDownloadedSize;
389 public final ReleaseType mReleaseType;
390
391 // empty constructor
392@@ -52,35 +57,44 @@
393 mChannelJson = "";
394 mDescription = "";
395 mVersion = -1;
396+ mDownloadedSize = 0;
397 mReleaseType = ReleaseType.UNKNOWN;
398 }
399
400 public VersionInfo(String channelAlias,
401 String channleJson,
402 String description,
403- int version,
404+ int version,
405+ long downloadedSize,
406 ReleaseType type) {
407 mChannelAlias = channelAlias;
408 mChannelJson = channleJson;
409 mDescription = description;
410 mVersion = version;
411+ mDownloadedSize = downloadedSize;
412 mReleaseType = type;
413 }
414
415- public VersionInfo( SharedPreferences sp, String set ) {
416+ public VersionInfo(SharedPreferences sp, String set ) {
417 mChannelAlias = sp.getString( set + ALIAS, "");
418 mChannelJson = sp.getString( set + JSON, "");
419 mDescription = sp.getString( set + DESCRIPTION, "");
420 mVersion = sp.getInt( set + VERSION, -1);
421+ mDownloadedSize = sp.getLong( set + DOWNLOADED_SIZE, 0);
422 mReleaseType = ReleaseType.fromValue(sp.getInt(TYPE, ReleaseType.FULL.getValue())); // default is FULL
423 }
424
425+ public boolean equals(String channelJson, int version, ReleaseType releaseType) {
426+ return true;
427+ }
428+
429 public void storeVersion(SharedPreferences.Editor e, String set) {
430 e.putString(set+ALIAS, mChannelAlias)
431 .putString(set + JSON, mChannelJson)
432 .putString(set + DESCRIPTION, mDescription)
433 .putInt(set + VERSION, mVersion)
434- .putInt(set + TYPE, mReleaseType.ordinal())
435+ .putLong(set + DOWNLOADED_SIZE, mDownloadedSize)
436+ .putInt(set + TYPE, mReleaseType.getValue())
437 .commit();
438 }
439
440@@ -89,6 +103,7 @@
441 .putString(set + JSON, "")
442 .putString(set + DESCRIPTION, "")
443 .putInt(set + VERSION, -1)
444+ .putInt(set + DOWNLOADED_SIZE, 0)
445 .putInt(set + TYPE, ReleaseType.UNKNOWN.ordinal())
446 .commit();
447 }
448@@ -115,6 +130,10 @@
449 public int getVersion() {
450 return mVersion;
451 }
452+
453+ public long getDownloadedSize() {
454+ return mDownloadedSize;
455+ }
456
457 public boolean isFullUpdate() {
458 return ReleaseType.FULL == mReleaseType;

Subscribers

People subscribed via source and target branches

to all changes: