• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.googlecode.android_scripting;
18 
19 import android.app.AlertDialog;
20 import android.app.ProgressDialog;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.content.DialogInterface.OnCancelListener;
24 import android.os.AsyncTask;
25 
26 import com.googlecode.android_scripting.exception.Sl4aException;
27 import com.googlecode.android_scripting.future.FutureResult;
28 
29 import java.io.File;
30 import java.io.FileNotFoundException;
31 import java.io.FileOutputStream;
32 import java.io.IOException;
33 import java.util.Enumeration;
34 import java.util.zip.ZipEntry;
35 import java.util.zip.ZipFile;
36 
37 /**
38  * AsyncTask for extracting ZIP files.
39  *
40  * @author Damon Kohler (damonkohler@gmail.com)
41  * @author Alexey Reznichenko (alexey.reznichenko@gmail.com)
42  */
43 public class ZipExtractorTask extends AsyncTask<Void, Integer, Long> {
44 
45   private static enum Replace {
46     YES, NO, YESTOALL, SKIPALL
47   }
48 
49   private final File mInput;
50   private final File mOutput;
51   private final ProgressDialog mDialog;
52   private Throwable mException;
53   private int mProgress = 0;
54   private final Context mContext;
55   private boolean mReplaceAll;
56 
57   private final class ProgressReportingOutputStream extends FileOutputStream {
ProgressReportingOutputStream(File f)58     private ProgressReportingOutputStream(File f) throws FileNotFoundException {
59       super(f);
60     }
61 
62     @Override
write(byte[] buffer, int offset, int count)63     public void write(byte[] buffer, int offset, int count) throws IOException {
64       super.write(buffer, offset, count);
65       mProgress += count;
66       publishProgress(mProgress);
67     }
68   }
69 
ZipExtractorTask(String in, String out, Context context, boolean replaceAll)70   public ZipExtractorTask(String in, String out, Context context, boolean replaceAll)
71       throws Sl4aException {
72     super();
73     mInput = new File(in);
74     mOutput = new File(out);
75     if (!mOutput.exists()) {
76       if (!mOutput.mkdirs()) {
77         throw new Sl4aException("Failed to make directories: " + mOutput.getAbsolutePath());
78       }
79     }
80     if (context != null) {
81       mDialog = new ProgressDialog(context);
82     } else {
83       mDialog = null;
84     }
85 
86     mContext = context;
87     mReplaceAll = replaceAll;
88 
89   }
90 
91   @Override
onPreExecute()92   protected void onPreExecute() {
93     Log.v("Extracting " + mInput.getAbsolutePath() + " to " + mOutput.getAbsolutePath());
94     if (mDialog != null) {
95       mDialog.setTitle("Extracting");
96       mDialog.setMessage(mInput.getName());
97       mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
98       mDialog.setOnCancelListener(new OnCancelListener() {
99         @Override
100         public void onCancel(DialogInterface dialog) {
101           cancel(true);
102         }
103       });
104       mDialog.show();
105     }
106   }
107 
108   @Override
doInBackground(Void... params)109   protected Long doInBackground(Void... params) {
110     try {
111       return unzip();
112     } catch (Exception e) {
113       if (mInput.exists()) {
114         // Clean up bad zip file.
115         mInput.delete();
116       }
117       mException = e;
118       return null;
119     }
120   }
121 
122   @Override
onProgressUpdate(Integer... progress)123   protected void onProgressUpdate(Integer... progress) {
124     if (mDialog == null) {
125       return;
126     }
127     if (progress.length > 1) {
128       int max = progress[1];
129       mDialog.setMax(max);
130     } else {
131       mDialog.setProgress(progress[0].intValue());
132     }
133   }
134 
135   @Override
onPostExecute(Long result)136   protected void onPostExecute(Long result) {
137     if (mDialog != null && mDialog.isShowing()) {
138       mDialog.dismiss();
139     }
140     if (isCancelled()) {
141       return;
142     }
143     if (mException != null) {
144       Log.e("Zip extraction failed.", mException);
145     }
146   }
147 
148   @Override
onCancelled()149   protected void onCancelled() {
150     if (mDialog != null) {
151       mDialog.setTitle("Extraction cancelled.");
152     }
153   }
154 
unzip()155   private long unzip() throws Exception {
156     long extractedSize = 0l;
157     Enumeration<? extends ZipEntry> entries;
158     ZipFile zip = new ZipFile(mInput);
159     long uncompressedSize = getOriginalSize(zip);
160 
161     publishProgress(0, (int) uncompressedSize);
162 
163     entries = zip.entries();
164 
165     try {
166       while (entries.hasMoreElements()) {
167         ZipEntry entry = entries.nextElement();
168         if (entry.isDirectory()) {
169           // Not all zip files actually include separate directory entries.
170           // We'll just ignore them
171           // and create them as necessary for each actual entry.
172           continue;
173         }
174         File destination = new File(mOutput, entry.getName());
175         if (!destination.getParentFile().exists()) {
176           destination.getParentFile().mkdirs();
177         }
178         if (destination.exists() && mContext != null && !mReplaceAll) {
179           Replace answer = showDialog(entry.getName());
180           switch (answer) {
181           case YES:
182             break;
183           case NO:
184             continue;
185           case YESTOALL:
186             mReplaceAll = true;
187             break;
188           default:
189             return extractedSize;
190           }
191         }
192         ProgressReportingOutputStream outStream = new ProgressReportingOutputStream(destination);
193         extractedSize += IoUtils.copy(zip.getInputStream(entry), outStream);
194         outStream.close();
195       }
196     } finally {
197       try {
198         zip.close();
199       } catch (Exception e) {
200         // swallow this exception, we are only interested in the original one
201       }
202     }
203     Log.v("Extraction is complete.");
204     return extractedSize;
205   }
206 
getOriginalSize(ZipFile file)207   private long getOriginalSize(ZipFile file) {
208     Enumeration<? extends ZipEntry> entries = file.entries();
209     long originalSize = 0l;
210     while (entries.hasMoreElements()) {
211       ZipEntry entry = entries.nextElement();
212       if (entry.getSize() >= 0) {
213         originalSize += entry.getSize();
214       }
215     }
216     return originalSize;
217   }
218 
showDialog(final String name)219   private Replace showDialog(final String name) {
220     final FutureResult<Replace> mResult = new FutureResult<Replace>();
221 
222     MainThread.run(mContext, new Runnable() {
223       @Override
224       public void run() {
225         AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
226         builder.setTitle(String.format("Script \"%s\" already exist.", name));
227         builder.setMessage(String.format("Do you want to replace script \"%s\" ?", name));
228 
229         DialogInterface.OnClickListener buttonListener = new DialogInterface.OnClickListener() {
230           @Override
231           public void onClick(DialogInterface dialog, int which) {
232             Replace result = Replace.SKIPALL;
233             switch (which) {
234             case DialogInterface.BUTTON_POSITIVE:
235               result = Replace.YES;
236               break;
237             case DialogInterface.BUTTON_NEGATIVE:
238               result = Replace.NO;
239               break;
240             case DialogInterface.BUTTON_NEUTRAL:
241               result = Replace.YESTOALL;
242               break;
243             }
244             mResult.set(result);
245             dialog.dismiss();
246           }
247         };
248         builder.setNegativeButton("Skip", buttonListener);
249         builder.setPositiveButton("Replace", buttonListener);
250         builder.setNeutralButton("Replace All", buttonListener);
251 
252         builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
253           @Override
254           public void onCancel(DialogInterface dialog) {
255             mResult.set(Replace.SKIPALL);
256             dialog.dismiss();
257           }
258         });
259         builder.show();
260       }
261     });
262 
263     try {
264       return mResult.get();
265     } catch (InterruptedException e) {
266       Log.e(e);
267     }
268     return null;
269   }
270 }
271