/*
* Copyright (C) 2014 Arpit Khurana <arpitkh96@gmail.com>, Vishal Nehra <vishalmeham2@gmail.com>
*
* This file is part of Amaze File Manager.
*
* Amaze File Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.amaze.filemanager.services;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.text.format.Formatter;
import android.util.Log;
import com.amaze.filemanager.R;
import com.amaze.filemanager.activities.MainActivity;
import com.amaze.filemanager.filesystem.FileUtil;
import com.amaze.filemanager.utils.AppConfig;
import com.amaze.filemanager.utils.DataPackage;
import com.amaze.filemanager.utils.GenericCopyUtil;
import com.amaze.filemanager.utils.ProgressHandler;
import com.amaze.filemanager.utils.ServiceWatcherUtil;
import com.github.junrar.Archive;
import com.github.junrar.rarfile.FileHeader;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class ExtractService extends Service {
Context cd;
// list of data packages,// to initiate chart in process viewer fragment
private ArrayList<DataPackage> dataPackages = new ArrayList<>();
// total size of file, can change later
private long totalSize = 0L;
private NotificationManager mNotifyManager;
private NotificationCompat.Builder mBuilder;
// names of entries to be extracted
private ArrayList<String> entries = new ArrayList<>();
private String epath;
private ProgressHandler progressHandler;
public static final String KEY_PATH_ZIP = "zip";
public static final String KEY_ENTRIES_ZIP = "entries";
public static final String TAG_BROADCAST_EXTRACT_CANCEL = "excancel";
public static final String KEY_PATH_EXTRACT = "extractpath";
@Override
public void onCreate() {
registerReceiver(receiver1, new IntentFilter(TAG_BROADCAST_EXTRACT_CANCEL));
cd = getApplicationContext();
}
@Override
public int onStartCommand(Intent intent, int flags, final int startId) {
Bundle b = new Bundle();
String file = intent.getStringExtra(KEY_PATH_ZIP);
String extractPath = intent.getStringExtra(KEY_PATH_EXTRACT);
if (extractPath != null) {
// a custom dynamic path to extract files to
epath = extractPath;
} else {
epath = PreferenceManager.getDefaultSharedPreferences(this).getString(KEY_PATH_EXTRACT, file);
}
mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
entries = intent.getStringArrayListExtra(KEY_ENTRIES_ZIP);
b.putString(KEY_PATH_ZIP, file);
totalSize = getTotalSize(file);
progressHandler = new ProgressHandler(1, totalSize);
progressHandler.setProgressListener(new ProgressHandler.ProgressListener() {
@Override
public void onProgressed(String fileName, int sourceFiles, int sourceProgress, long totalSize,
long writtenSize, int speed) {
publishResults(startId, fileName, sourceFiles, sourceProgress, totalSize, writtenSize, speed, false);
}
});
Intent notificationIntent = new Intent(this, MainActivity.class);
notificationIntent.setAction(Intent.ACTION_MAIN);
notificationIntent.putExtra(MainActivity.KEY_INTENT_PROCESS_VIEWER, true);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
mBuilder = new NotificationCompat.Builder(cd);
mBuilder.setContentIntent(pendingIntent);
mBuilder.setContentTitle(getResources().getString(R.string.extracting))
.setContentText(new File(file).getName())
.setSmallIcon(R.drawable.ic_zip_box_grey600_36dp);
startForeground(Integer.parseInt("123" + startId), mBuilder.build());
new DoWork().execute(b);
return START_STICKY;
}
/**
* Method calculates zip file size to initiate progress
* Supporting local file extraction progress for now
*
* @param filePath
* @return
*/
private long getTotalSize(String filePath) {
return new File(filePath).length();
}
private final IBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder {
public ExtractService getService() {
// Return this instance of LocalService so clients can call public methods
return ExtractService.this;
}
}
public void setProgressListener(ProgressListener progressListener) {
this.progressListener = progressListener;
}
ProgressListener progressListener;
public interface ProgressListener {
void onUpdate(DataPackage dataPackage);
void refresh();
}
private void publishResults(int id, String fileName, int sourceFiles, int sourceProgress,
long total, long done, int speed, boolean isCompleted) {
if (!progressHandler.getCancelled()) {
mBuilder.setContentTitle(getResources().getString(R.string.extracting));
float progressPercent = ((float) done / total) * 100;
mBuilder.setProgress(100, Math.round(progressPercent), false);
mBuilder.setOngoing(true);
mBuilder.setContentText(fileName + " " + Formatter.formatFileSize(cd, done) + "/"
+ Formatter.formatFileSize(cd, total));
int id1 = Integer.parseInt("123" + id);
mNotifyManager.notify(id1, mBuilder.build());
if (progressPercent == 100 || total == 0) {
mBuilder.setContentTitle(getString(R.string.extract_complete));
mBuilder.setContentText(fileName + " " + Formatter.formatFileSize(cd, total));
mBuilder.setProgress(100, 100, false);
mBuilder.setOngoing(false);
mNotifyManager.notify(id1, mBuilder.build());
publishCompletedResult("", id1);
}
DataPackage intent = new DataPackage();
intent.setName(fileName);
intent.setSourceFiles(sourceFiles);
intent.setSourceProgress(sourceProgress);
intent.setTotal(total);
intent.setByteProgress(done);
intent.setSpeedRaw(speed);
intent.setMove(false);
intent.setCompleted(isCompleted);
putDataPackage(intent);
if (progressListener != null) {
progressListener.onUpdate(intent);
if (isCompleted) progressListener.refresh();
}
} else publishCompletedResult(fileName, Integer.parseInt("123" + id));
}
public void publishCompletedResult(String a, int id1) {
try {
mNotifyManager.cancel(id1);
} catch (Exception e) {
e.printStackTrace();
}
}
public class DoWork extends AsyncTask<Bundle, Void, Integer> {
long totalBytes = 0L;
private ServiceWatcherUtil watcherUtil;
private void createDir(File dir) {
FileUtil.mkdir(dir, cd);
}
/**
* Method extracts {@link ZipEntry} from {@link ZipFile}
*
* @param zipFile zip file from which entries are to be extracted
* @param entry zip entry that is to be extracted
* @param outputDir output directory
* @throws Exception
*/
private void unzipEntry(ZipFile zipFile, ZipEntry entry, String outputDir)
throws Exception {
if (entry.isDirectory()) {
// zip entry is a directory, return after creating new directory
createDir(new File(outputDir, entry.getName()));
return;
}
final File outputFile = new File(outputDir, entry.getName());
if (!outputFile.getParentFile().exists()) {
// creating directory if not already exists
createDir(outputFile.getParentFile());
}
BufferedInputStream inputStream = new BufferedInputStream(
zipFile.getInputStream(entry));
BufferedOutputStream outputStream = new BufferedOutputStream(
FileUtil.getOutputStream(outputFile, cd, 0));
try {
int len;
byte buf[] = new byte[GenericCopyUtil.DEFAULT_BUFFER_SIZE];
while ((len = inputStream.read(buf)) > 0) {
outputStream.write(buf, 0, len);
ServiceWatcherUtil.POSITION += len;
}
} finally {
outputStream.close();
inputStream.close();
}
}
private void unzipRAREntry(Archive zipFile, FileHeader entry, String outputDir)
throws Exception {
String name = entry.getFileNameString();
name = name.replaceAll("\\\\", "/");
if (entry.isDirectory()) {
createDir(new File(outputDir, name));
return;
}
File outputFile = new File(outputDir, name);
if (!outputFile.getParentFile().exists()) {
createDir(outputFile.getParentFile());
}
// Log.i("Amaze", "Extracting: " + entry);
BufferedInputStream inputStream = new BufferedInputStream(
zipFile.getInputStream(entry));
BufferedOutputStream outputStream = new BufferedOutputStream(
FileUtil.getOutputStream(outputFile, cd, entry.getFullUnpackSize()));
try {
int len;
byte buf[] = new byte[GenericCopyUtil.DEFAULT_BUFFER_SIZE];
while ((len = inputStream.read(buf)) > 0) {
outputStream.write(buf, 0, len);
ServiceWatcherUtil.POSITION += len;
}
} catch (Exception e) {
throw new Exception();
} finally {
outputStream.close();
inputStream.close();
}
}
private void unzipTAREntry(TarArchiveInputStream zipFileStream, TarArchiveEntry entry,
String outputDir) throws Exception {
String name = entry.getName();
if (entry.isDirectory()) {
createDir(new File(outputDir, name));
return;
}
File outputFile = new File(outputDir, name);
if (!outputFile.getParentFile().exists()) {
createDir(outputFile.getParentFile());
}
BufferedOutputStream outputStream = new BufferedOutputStream(
FileUtil.getOutputStream(outputFile, cd, entry.getRealSize()));
try {
int len;
byte buf[] = new byte[GenericCopyUtil.DEFAULT_BUFFER_SIZE];
while ((len = zipFileStream.read(buf)) > 0) {
outputStream.write(buf, 0, len);
ServiceWatcherUtil.POSITION += len;
}
} catch (Exception e) {
throw new Exception();
} finally {
outputStream.close();
}
}
/**
* Helper method to initiate extraction of zip/jar files.
*
* @param archive the file pointing to archive
* @param destinationPath the where to extract
* @param entryNamesList names of files to be extracted from the archive
* @return
*/
private boolean extract(File archive, String destinationPath,
ArrayList<String> entryNamesList) {
ArrayList<ZipEntry> entry1 = new ArrayList<>();
try {
ZipFile zipfile = new ZipFile(archive);
// iterating archive elements to find file names that are to be extracted
for (Enumeration e = zipfile.entries(); e.hasMoreElements(); ) {
ZipEntry zipEntry = (ZipEntry) e.nextElement();
for (String entry : entryNamesList) {
if (zipEntry.getName().contains(entry)) {
// header to be extracted is atleast the entry path (may be more, when it is a directory)
entry1.add(zipEntry);
}
}
}
// get the total size of elements to be extracted
for (ZipEntry entry : entry1) {
totalBytes += entry.getSize();
}
// setting total bytes calculated from zip entries
progressHandler.setTotalSize(totalBytes);
setInitDataPackage(totalBytes, entry1.get(0).getName(), entryNamesList.size());
watcherUtil = new ServiceWatcherUtil(progressHandler, totalBytes);
watcherUtil.watch();
int i = 0;
for (ZipEntry entry : entry1) {
if (!progressHandler.getCancelled()) {
progressHandler.setFileName(entry.getName());
unzipEntry(zipfile, entry, destinationPath);
progressHandler.setSourceFilesProcessed(++i);
}
}
return true;
} catch (Exception e) {
Log.e("amaze", "Error while extracting file " + archive, e);
AppConfig.toast(getApplicationContext(), getString(R.string.error));
return false;
}
}
private boolean extract(File archive, String destinationPath) {
try {
ArrayList<ZipEntry> arrayList = new ArrayList<>();
ZipFile zipfile = new ZipFile(archive);
for (Enumeration e = zipfile.entries(); e.hasMoreElements(); ) {
// adding all the elements to be extracted to an array list
ZipEntry entry = (ZipEntry) e.nextElement();
arrayList.add(entry);
}
for (ZipEntry entry : arrayList) {
// calculating size of compressed items
totalBytes += entry.getSize();
}
// setting total bytes calculated from zip entries
progressHandler.setTotalSize(totalBytes);
setInitDataPackage(totalBytes, arrayList.get(0).getName(), 1);
watcherUtil = new ServiceWatcherUtil(progressHandler, totalBytes);
watcherUtil.watch();
for (ZipEntry entry : arrayList) {
if (!progressHandler.getCancelled()) {
progressHandler.setFileName(entry.getName());
unzipEntry(zipfile, entry, destinationPath);
}
}
progressHandler.setSourceFilesProcessed(1);
return true;
} catch (Exception e) {
Log.e("amaze", "Error while extracting file " + archive, e);
AppConfig.toast(getApplicationContext(), getString(R.string.error));
return false;
}
}
private boolean extractTar(File archive, String destinationPath) {
try {
ArrayList<TarArchiveEntry> archiveEntries = new ArrayList<>();
TarArchiveInputStream inputStream;
if (archive.getName().endsWith(".tar"))
inputStream = new TarArchiveInputStream(new BufferedInputStream(new FileInputStream(archive)));
else
inputStream = new TarArchiveInputStream(new GZIPInputStream(new FileInputStream(archive)));
TarArchiveEntry tarArchiveEntry = inputStream.getNextTarEntry();
while (tarArchiveEntry != null) {
archiveEntries.add(tarArchiveEntry);
tarArchiveEntry = inputStream.getNextTarEntry();
}
for (TarArchiveEntry entry : archiveEntries) {
totalBytes += entry.getSize();
}
// setting total bytes calculated from zip entries
progressHandler.setTotalSize(totalBytes);
setInitDataPackage(totalBytes, archiveEntries.get(0).getName(), 1);
watcherUtil = new ServiceWatcherUtil(progressHandler, totalBytes);
watcherUtil.watch();
for (TarArchiveEntry entry : archiveEntries) {
if (!progressHandler.getCancelled()) {
progressHandler.setFileName(entry.getName());
unzipTAREntry(inputStream, entry, destinationPath);
}
}
progressHandler.setSourceFilesProcessed(1);
// operating finished
inputStream.close();
return true;
} catch (Exception e) {
Log.e("amaze", "Error while extracting file " + archive, e);
AppConfig.toast(getApplicationContext(), getString(R.string.error));
return false;
}
}
private boolean extractRar(File archive, String destinationPath) {
try {
ArrayList<FileHeader> arrayList = new ArrayList<>();
Archive zipFile = new Archive(archive);
FileHeader fh = zipFile.nextFileHeader();
while (fh != null) {
arrayList.add(fh);
fh = zipFile.nextFileHeader();
}
for (FileHeader header : arrayList) {
totalBytes += header.getFullUnpackSize();
}
// setting total bytes calculated from zip entries
progressHandler.setTotalSize(totalBytes);
setInitDataPackage(totalBytes, arrayList.get(0).getFileNameString(), 1);
watcherUtil = new ServiceWatcherUtil(progressHandler, totalBytes);
watcherUtil.watch();
for (FileHeader header : arrayList) {
if (!progressHandler.getCancelled()) {
progressHandler.setFileName(header.getFileNameString());
unzipRAREntry(zipFile, header, destinationPath);
}
}
progressHandler.setSourceFilesProcessed(1);
return true;
} catch (Exception e) {
Log.e("amaze", "Error while extracting file " + archive, e);
AppConfig.toast(getApplicationContext(), getString(R.string.error));
return false;
}
}
private boolean extractRar(File archive, String destinationPath, ArrayList<String> entries) {
try {
Archive rarFile = new Archive(archive);
ArrayList<FileHeader> arrayList = new ArrayList<>();
// iterating archive elements to find file names that are to be extracted
for (FileHeader header : rarFile.getFileHeaders()) {
for (String entry : entries) {
if (header.getFileNameString().contains(entry)) {
// header to be extracted is atleast the entry path (may be more, when it is a directory)
arrayList.add(header);
}
}
}
// get the total size of elements to be extracted
for (FileHeader entry : arrayList) {
totalBytes += entry.getFullUnpackSize();
}
// setting total bytes calculated from zip entries
progressHandler.setTotalSize(totalBytes);
setInitDataPackage(totalBytes, arrayList.get(0).getFileNameString(), arrayList.size());
watcherUtil = new ServiceWatcherUtil(progressHandler, totalBytes);
watcherUtil.watch();
int i = 0;
for (FileHeader entry : arrayList) {
if (!progressHandler.getCancelled()) {
progressHandler.setFileName(entry.getFileNameString());
unzipRAREntry(rarFile, entry, destinationPath);
progressHandler.setSourceFilesProcessed(++i);
}
}
return true;
} catch (Exception e) {
Log.e("amaze", "Error while extracting file " + archive, e);
AppConfig.toast(getApplicationContext(), getString(R.string.error));
return false;
}
}
protected Integer doInBackground(Bundle... p1) {
String file = p1[0].getString(KEY_PATH_ZIP);
File f = new File(file);
String path;
if (epath.equals(file)) {
// custom extraction path not set, extract at default path
path = f.getParent() + "/" + f.getName().substring(0, f.getName().lastIndexOf("."));
} else {
if (epath.endsWith("/")) {
path = epath + f.getName().substring(0, f.getName().lastIndexOf("."));
} else {
path = epath + "/" + f.getName().substring(0, f.getName().lastIndexOf("."));
}
}
if (entries != null && entries.size() != 0) {
if (f.getName().toLowerCase().endsWith(".zip") || f.getName().toLowerCase().endsWith(".jar") || f.getName().toLowerCase().endsWith(".apk"))
extract(f, path, entries);
else if (f.getName().toLowerCase().endsWith(".rar"))
extractRar(f, path, entries);
} else if (f.getName().toLowerCase().endsWith(".zip") || f.getName().toLowerCase().endsWith(".jar") || f.getName().toLowerCase().endsWith(".apk"))
extract(f, path);
else if (f.getName().toLowerCase().endsWith(".rar"))
extractRar(f, path);
else if (f.getName().toLowerCase().endsWith(".tar") || f.getName().toLowerCase().endsWith(".tar.gz"))
extractTar(f, path);
Log.i("Amaze", "Almost Completed");
// TODO: Implement this method
return p1[0].getInt("id");
}
@Override
public void onPostExecute(Integer b) {
// check whether watcherutil was initialized. It was not initialized when we got exception
// in extracting the file
if (watcherUtil != null) watcherUtil.stopWatch();
Intent intent = new Intent("loadlist");
sendBroadcast(intent);
stopSelf();
}
}
/**
* Setting initial package to initialize charts in process viewer properly
*
* @param totalSize
* @param fileName
*/
private void setInitDataPackage(long totalSize, String fileName, int sourceTotal) {
DataPackage intent1 = new DataPackage();
intent1.setName(fileName);
intent1.setSourceFiles(sourceTotal);
intent1.setSourceProgress(0);
intent1.setTotal(totalSize);
intent1.setByteProgress(0);
intent1.setSpeedRaw(0);
intent1.setMove(false);
intent1.setCompleted(false);
putDataPackage(intent1);
}
@Override
public void onDestroy() {
unregisterReceiver(receiver1);
}
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
private BroadcastReceiver receiver1 = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
progressHandler.setCancelled(true);
}
};
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return mBinder;
}
/**
* Returns the {@link #dataPackages} list which contains
* data to be transferred to {@link com.amaze.filemanager.fragments.ProcessViewer}
* Method call is synchronized so as to avoid modifying the list
* by {@link ServiceWatcherUtil#handlerThread} while {@link MainActivity#runOnUiThread(Runnable)}
* is executing the callbacks in {@link com.amaze.filemanager.fragments.ProcessViewer}
*
* @return
*/
public synchronized DataPackage getDataPackage(int index) {
return this.dataPackages.get(index);
}
public synchronized int getDataPackageSize() {
return this.dataPackages.size();
}
/**
* Puts a {@link DataPackage} into a list
* Method call is synchronized so as to avoid modifying the list
* by {@link ServiceWatcherUtil#handlerThread} while {@link MainActivity#runOnUiThread(Runnable)}
* is executing the callbacks in {@link com.amaze.filemanager.fragments.ProcessViewer}
*
* @param dataPackage
*/
private synchronized void putDataPackage(DataPackage dataPackage) {
this.dataPackages.add(dataPackage);
}
}