/*
* 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 com.amaze.filemanager.R;
import com.amaze.filemanager.activities.MainActivity;
import com.amaze.filemanager.filesystem.BaseFile;
import com.amaze.filemanager.filesystem.FileUtil;
import com.amaze.filemanager.utils.DataPackage;
import com.amaze.filemanager.utils.GenericCopyUtil;
import com.amaze.filemanager.utils.PreferenceUtils;
import com.amaze.filemanager.utils.ProgressHandler;
import com.amaze.filemanager.utils.ServiceWatcherUtil;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZipTask extends Service {
NotificationManager mNotifyManager;
NotificationCompat.Builder mBuilder;
String mZipPath;
Context c;
ProgressListener progressListener;
long totalBytes = 0L;
private final IBinder mBinder = new LocalBinder();
private ProgressHandler progressHandler;
private ArrayList<DataPackage> dataPackages = new ArrayList<>();
public static final String KEY_COMPRESS_PATH = "zip_path";
public static final String KEY_COMPRESS_FILES = "zip_files";
public static final String KEY_COMPRESS_BROADCAST_CANCEL = "zip_cancel";
@Override
public void onCreate() {
c = getApplicationContext();
registerReceiver(receiver1, new IntentFilter(KEY_COMPRESS_BROADCAST_CANCEL));
}
@Override
public int onStartCommand(Intent intent, int flags, final int startId) {
Bundle b = new Bundle();
String path = intent.getStringExtra(KEY_COMPRESS_PATH);
ArrayList<BaseFile> baseFiles = intent.getParcelableArrayListExtra(KEY_COMPRESS_FILES);
File zipFile = new File(path);
mZipPath = PreferenceManager.getDefaultSharedPreferences(this)
.getString(PreferenceUtils.KEY_PATH_COMPRESS, path);
mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (!mZipPath.equals(path)) {
mZipPath.concat(mZipPath.endsWith("/") ? (zipFile.getName()) : ("/" + zipFile.getName()));
}
if (!zipFile.exists()) {
try {
zipFile.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
mBuilder = new NotificationCompat.Builder(this);
Intent notificationIntent = new Intent(this, MainActivity.class);
notificationIntent.putExtra(MainActivity.KEY_INTENT_PROCESS_VIEWER, true);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
mBuilder.setContentIntent(pendingIntent);
mBuilder.setContentTitle(getResources().getString(R.string.compressing))
.setSmallIcon(R.drawable.ic_zip_box_grey600_36dp);
startForeground(Integer.parseInt("789" + startId), mBuilder.build());
b.putInt("id", startId);
b.putParcelableArrayList(KEY_COMPRESS_FILES, baseFiles);
b.putString(KEY_COMPRESS_PATH, mZipPath);
new DoWork().execute(b);
// If we get killed, after returning from here, restart
return START_STICKY;
}
private long getTotalBytes(ArrayList<BaseFile> baseFiles, Context context) {
long totalBytes = 0L;
for (BaseFile f1 : baseFiles) {
if (f1.isDirectory(context)) {
totalBytes += f1.folderSize(context);
} else {
totalBytes += f1.length(context);
}
}
return totalBytes;
}
public class LocalBinder extends Binder {
public ZipTask getService() {
// Return this instance of LocalService so clients can call public methods
return ZipTask.this;
}
}
public void setProgressListener(ProgressListener progressListener) {
this.progressListener = progressListener;
}
public interface ProgressListener {
void onUpdate(DataPackage dataPackage);
void refresh();
}
public class DoWork extends AsyncTask<Bundle, Void, Integer> {
ZipOutputStream zos;
String zipPath;
ServiceWatcherUtil watcherUtil;
public DoWork() {
}
public ArrayList<File> toFileArray(ArrayList<BaseFile> a) {
ArrayList<File> b = new ArrayList<>();
for (int i = 0; i < a.size(); i++) {
b.add(new File(a.get(i).getPath()));
}
return b;
}
protected Integer doInBackground(Bundle... p1) {
final int id = p1[0].getInt("id");
ArrayList<BaseFile> baseFiles = p1[0].getParcelableArrayList(KEY_COMPRESS_FILES);
// setting up service watchers and initial data packages
// finding total size on background thread (this is necessary condition for SMB!)
totalBytes = getTotalBytes(baseFiles, c);
progressHandler = new ProgressHandler(baseFiles.size(), totalBytes);
progressHandler.setProgressListener(new ProgressHandler.ProgressListener() {
@Override
public void onProgressed(String fileName, int sourceFiles, int sourceProgress,
long totalSize, long writtenSize, int speed) {
publishResults(id, fileName, sourceFiles, sourceProgress,
totalSize, writtenSize, speed, false);
}
});
DataPackage intent1 = new DataPackage();
intent1.setName(baseFiles.get(0).getName());
intent1.setSourceFiles(baseFiles.size());
intent1.setSourceProgress(0);
intent1.setTotal(totalBytes);
intent1.setByteProgress(0);
intent1.setSpeedRaw(0);
intent1.setMove(false);
intent1.setCompleted(false);
putDataPackage(intent1);
zipPath = p1[0].getString(KEY_COMPRESS_PATH);
execute(toFileArray(baseFiles), zipPath);
return id;
}
@Override
public void onPostExecute(Integer b) {
watcherUtil.stopWatch();
Intent intent = new Intent("loadlist");
sendBroadcast(intent);
stopSelf();
}
public void execute(ArrayList<File> baseFiles, String zipPath) {
OutputStream out;
File zipDirectory = new File(zipPath);
watcherUtil = new ServiceWatcherUtil(progressHandler, totalBytes);
watcherUtil.watch();
try {
out = FileUtil.getOutputStream(zipDirectory, c, totalBytes);
zos = new ZipOutputStream(new BufferedOutputStream(out));
int fileProgress = 0;
for (File file : baseFiles) {
if (!progressHandler.getCancelled()) {
progressHandler.setFileName(file.getName());
progressHandler.setSourceFilesProcessed(++fileProgress);
compressFile(file, "");
} else return;
}
} catch (Exception e) {
} finally {
try {
zos.flush();
zos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void compressFile(File file, String path) throws IOException, NullPointerException {
if (!file.isDirectory()) {
if (progressHandler.getCancelled()) return;
byte[] buf = new byte[GenericCopyUtil.DEFAULT_BUFFER_SIZE];
int len;
BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
zos.putNextEntry(new ZipEntry(path + "/" + file.getName()));
while ((len = in.read(buf)) > 0) {
zos.write(buf, 0, len);
ServiceWatcherUtil.POSITION += len;
}
in.close();
return;
}
if (file.list() == null) {
return;
}
for (File currentFile : file.listFiles()) {
compressFile(currentFile, path + File.separator + file.getName());
}
}
}
private void publishResults(int id, String fileName, int sourceFiles, int sourceProgress,
long total, long done, int speed, boolean isCompleted) {
if (!progressHandler.getCancelled()) {
float progressPercent = ((float) done / total) * 100;
mBuilder.setProgress(100, Math.round(progressPercent), false);
mBuilder.setOngoing(true);
int title = R.string.compressing;
mBuilder.setContentTitle(c.getResources().getString(title));
mBuilder.setContentText(new File(fileName).getName() + " " +
Formatter.formatFileSize(c, done) + "/" + Formatter.formatFileSize(c, total));
int id1 = Integer.parseInt("789" + id);
mNotifyManager.notify(id1, mBuilder.build());
if (done == total || total == 0) {
mBuilder.setContentTitle(getString(R.string.compression_complete));
mBuilder.setContentText("");
mBuilder.setProgress(100, 100, false);
mBuilder.setOngoing(false);
mNotifyManager.notify(id1, mBuilder.build());
publishCompletedResult(id1);
isCompleted = true;
}
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(Integer.parseInt("789" + id));
}
}
public void publishCompletedResult(int id1) {
try {
mNotifyManager.cancel(id1);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 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;
}
@Override
public void onDestroy() {
this.unregisterReceiver(receiver1);
}
/**
* 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);
}
}