Home | History | Annotate | Download | only in android_scripting
      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 (at) gmail.com)
     41  * @author Alexey Reznichenko (alexey.reznichenko (at) 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 {
     58     private ProgressReportingOutputStream(File f) throws FileNotFoundException {
     59       super(f);
     60     }
     61 
     62     @Override
     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 
     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
     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
    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
    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
    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
    149   protected void onCancelled() {
    150     if (mDialog != null) {
    151       mDialog.setTitle("Extraction cancelled.");
    152     }
    153   }
    154 
    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 
    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 
    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