Home | History | Annotate | Download | only in android_scripting
      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 {
     56     private ProgressReportingOutputStream(File f) throws FileNotFoundException {
     57       super(f);
     58     }
     59 
     60     @Override
     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 
     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
     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
    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
    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
    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
    147   protected void onCancelled() {
    148     if (mDialog != null) {
    149       mDialog.setTitle("Extraction cancelled.");
    150     }
    151   }
    152 
    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 
    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 
    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