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