package com.junjunguo.pocketmaps.downloader; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.ArrayList; import java.util.zip.ZipOutputStream; import com.junjunguo.pocketmaps.model.MyMap; import com.junjunguo.pocketmaps.util.Variable; import android.annotation.TargetApi; import android.app.NotificationManager; import android.content.Context; import android.os.Build; import android.service.notification.StatusBarNotification; import android.util.Log; import com.junjunguo.pocketmaps.activities.ExportActivity; import com.junjunguo.pocketmaps.activities.GeocodeActivity; import java.io.OutputStream; import org.oscim.utils.IOUtils; /** * This file is part of PocketMaps * <p> * Created by GuoJunjun <junjunguo.com> on September 19, 2015. */ public class MapUnzip { public static final int ANDROID_API_MARSHMALLOW = Build.VERSION_CODES.LOLLIPOP + 2; public static final int BUFFER_SIZE = 8 * 1024; public void unzip(String zipFilePath, String mapName, ProgressPublisher pp) throws IOException { ZipInputStream zipIn = null; try{ File mapFolder = new File(Variable.getVariable().getMapsFolder(), mapName + "-gh"); File destDir = new File(mapFolder.getAbsolutePath()); if (destDir.exists()) { recursiveDelete(destDir); } if (!destDir.exists()) { destDir.mkdir(); } zipIn = new ZipInputStream(new FileInputStream(zipFilePath)); ZipEntry entry = zipIn.getNextEntry(); int up = 0; // iterates over entries in the zip file while (entry != null) { up++; long fSize = entry.getSize(); pp.updateText(false, "" + up + " Unzipping " + mapName, 0); String mapFileName = new File(entry.getName()).getName(); String filePath = mapFolder.getAbsolutePath() + File.separator + mapFileName; if (!entry.isDirectory()) { // if the entry is a file, extracts it extractFile(zipIn, filePath, pp, "" + up + " Unzipping " + mapName, fSize, null); } else { // if the entry is a directory, make the directory File dir = new File(filePath); dir.mkdir(); } zipIn.closeEntry(); entry = zipIn.getNextEntry(); } } finally { IOUtils.closeQuietly(zipIn); } } /** Unzip an exported-file (.pmz) and import it. **/ public boolean unzipImport(final String zipFilePath, final Context appContext) { ZipInputStream zipIn = null; try { zipIn = new ZipInputStream(new FileInputStream(zipFilePath)); ZipEntry entry = zipIn.getNextEntry(); if (entry==null) { return false; } if (ExportActivity.getFileType(entry.getName()) == ExportActivity.FileType.Map) { // Is a map file! String mapName = entry.getName().substring("/maps/".length()); int index = mapName.indexOf("-gh/"); if (index <= 0) { return false; } mapName = mapName.substring(0,index); final String mapNameFinal = mapName; //TODO: RefreshMapList, or use DownloadMapActivity.createStatusUpdater() --> BroadcastReceiver Thread t = new Thread(new Runnable(){ public void run() { // Because this may be a long running task, we dont use AsyncTask. ProgressPublisher pp = new ProgressPublisher(appContext); pp.updateText(false, "Unzipping " + mapNameFinal, 0); String finishTxt = "Finish: "; try { unzip(zipFilePath, mapNameFinal, pp); } catch (IOException e) { e.printStackTrace(); finishTxt = "Unzip error: "; } pp.updateTextFinal(finishTxt + mapNameFinal); MyMap myMap = new MyMap(mapNameFinal); Variable.getVariable().getRecentDownloadedMaps().add(myMap); myMap.setStatus(MyMap.DlStatus.Complete); }}); t.start(); return true; } boolean reSettings = false; boolean reFav = false; boolean reTrac = false; while (entry != null) { if (ExportActivity.getFileType(entry.getName()) == ExportActivity.FileType.Setting) { extractFile(zipIn, entry.getName(), null, "", 0, appContext); reSettings = true; } else if (ExportActivity.getFileType(entry.getName()) == ExportActivity.FileType.Favourites) { String favFolder = Variable.getVariable().getMapsFolder().getParent(); String favFile = new File(favFolder, "Favourites.properties").getPath(); extractFile(zipIn, favFile, null, "", 0, null); GeocodeActivity.resetFavourites(); reFav = true; } else if (ExportActivity.getFileType(entry.getName()) == ExportActivity.FileType.Tracking) { File tracFolder = Variable.getVariable().getTrackingFolder(); String tracFile = new File(tracFolder, new File(entry.getName()).getName()).getPath(); extractFile(zipIn, tracFile, null, "", 0, null); reTrac = true; } entry = zipIn.getNextEntry(); } String reSettingsS = "Loaded: "; if (reSettings) { for (Variable.VarType vt : Variable.VarType.values()) { Variable.getVariable().loadVariables(vt); } reSettingsS += "[Settings] "; } if (reFav) { reSettingsS += "[Favourites] "; } if (reTrac) { reSettingsS += "[Tracking-recs] "; } ProgressPublisher pp = new ProgressPublisher(appContext); pp.updateTextFinal(reSettingsS); } catch (IOException e) { e.printStackTrace(); return false; } finally { IOUtils.closeQuietly(zipIn); } return true; } /** Compress files. * @param context If any srcFile is internal, use this context. * @param pp ProgressPublisher may be null. **/ public boolean compressFiles(ArrayList<String> srcFiles, ArrayList<String> zipSubDirs, String tarZipFile, ProgressPublisher pp, Context context) { if (srcFiles.size()==0) { return true; } ZipOutputStream zout = null; FileInputStream fis = null; try { zout = new ZipOutputStream(new FileOutputStream(tarZipFile)); byte[] bytesIn = new byte[BUFFER_SIZE]; int fcount = 0; int flen = srcFiles.size(); for (int i=0; i<srcFiles.size(); i++) { String curFile = srcFiles.get(i); String zipSubDir = zipSubDirs.get(i); String zipName = new File(curFile).getName(); if (!zipSubDir.isEmpty()) { zipName = new File(zipSubDir, zipName).getPath(); } if (pp!=null) { fcount++; pp.updateText(false, "" + fcount + "/" + flen + " Export " + zipName, 0); } File f = new File(curFile); long bcount = 0; long bLen = 4000; if (f.exists()) { fis = new FileInputStream(new File(curFile)); bLen = f.length(); } else { fis = context.openFileInput(curFile); } ZipEntry zipEntry = new ZipEntry(zipName); zout.putNextEntry(zipEntry); while (true) { int count = fis.read(bytesIn); if (count < 0) { break; } if (pp!=null) { int per = (int)((bcount*100)/bLen); pp.updateText(true, "" + fcount + "/" + flen + " Export " + zipName, per); } zout.write(bytesIn, 0, count); } fis.close(); zout.closeEntry(); } if (pp!=null) { pp.updateTextFinal("Finish: Export " + new File(tarZipFile).getName()); } } catch (IOException e) { e.printStackTrace(); IOUtils.closeQuietly(fis); if (pp!=null) { pp.updateTextFinal("Error: Export " + new File(tarZipFile).getName()); } return false; } finally { IOUtils.closeQuietly(zout); } return true; } /** * Extracts a zip entry (file entry) * * @param zipIn The zip * @param filePath Target path, may be internal, when context not null * @param pp Show Progress, or may be null * @param mapName Map name * @param fSize The file size for progress or 0 for 50. * @param ppText Text for progress * @param appContextWhenInternal When filePath is internal, or null for regular file. * @throws IOException */ private void extractFile(ZipInputStream zipIn, String filePath, ProgressPublisher pp, String ppText, long fSize, Context appContextWhenInternal) throws IOException { BufferedOutputStream bos = null; try{ if (appContextWhenInternal==null) { bos = new BufferedOutputStream(new FileOutputStream(filePath)); } else { bos = new BufferedOutputStream(appContextWhenInternal.openFileOutput(filePath, Context.MODE_PRIVATE)); } byte[] bytesIn = new byte[BUFFER_SIZE]; int read = 0; long readCounter = 0; while ((read = zipIn.read(bytesIn)) != -1) { bos.write(bytesIn, 0, read); float percent = 50.0f; if (fSize>0) { readCounter += read; percent = ((float)readCounter) / ((float)fSize); percent = percent * 100.0f; } if (pp!=null) { pp.updateText(true, ppText, (int)percent); } } } finally { IOUtils.closeQuietly(bos); } } /** * delete a recursively delete a folder or file * * @param fileOrDirectory */ public void recursiveDelete(File fileOrDirectory) { if (fileOrDirectory.isDirectory()) for (File child : fileOrDirectory.listFiles()) recursiveDelete(child); try { fileOrDirectory.delete(); } catch (Exception e) { e.printStackTrace(); } } /** Check when status was updated from ProgressPublisher.java * Should update each second, otherwise the progress seems not alive. * @return False, when status was not updated for 30 sec. **/ public static boolean checkUnzipAlive(Context c, MyMap myMap) { if (Build.VERSION.SDK_INT < ANDROID_API_MARSHMALLOW) { return checkUnzipAliveOldVersion(c, myMap); } return checkUnzipAliveInternal(c, myMap); } private static boolean checkUnzipAliveOldVersion(Context c, MyMap myMap) { File mDir = MyMap.getMapFile(myMap, MyMap.MapFileType.MapFolder); if (timeCheck(mDir.lastModified())) { return true; } File list[] = mDir.listFiles(); if (list != null) { for (File f : list) { if (timeCheck(f.lastModified())) { return true; } } } return false; } @TargetApi(ANDROID_API_MARSHMALLOW) private static boolean checkUnzipAliveInternal(Context c, MyMap myMap) { String unzipKeyId = myMap.getMapName() + "-unzip"; NotificationManager notificationManager = (NotificationManager) c.getSystemService(Context.NOTIFICATION_SERVICE); for (StatusBarNotification n : notificationManager.getActiveNotifications()) { if (n.getId() == unzipKeyId.hashCode()) { long postTime = n.getPostTime(); return timeCheck(postTime); } } return false; } private static boolean timeCheck(long lastTime) { long curTime = System.currentTimeMillis(); long diffTime = curTime - lastTime; if (diffTime > 30000) { return false; } return true; } }