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