/**
* Nextcloud Android client application
*
* @author Tobias Kaminsky
* @author Andy Scherzinger
* @author Mario Danic
* Copyright (C) 2016 Tobias Kaminsky, Andy Scherzinger
* Copyright (C) 2017 Mario Danic
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.owncloud.android.services.observer;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import com.owncloud.android.MainApp;
import com.owncloud.android.datamodel.SerializablePair;
import com.owncloud.android.datamodel.SyncedFolder;
import com.owncloud.android.datamodel.SyncedFolderProvider;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.services.FileAlterationMagicListener;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileEntry;
import java.io.EOFException;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.concurrent.CopyOnWriteArrayList;
public class SyncedFolderObserverService extends Service {
private static final String TAG = "SyncedFolderObserverService";
private SyncedFolderProvider mProvider;
private HashMap syncedFolderMap = new HashMap<>();
private final IBinder mBinder = new SyncedFolderObserverBinder();
private FileAlterationMonitor monitor;
private FileFilter fileFilter;
private CopyOnWriteArrayList> pairArrayList = new CopyOnWriteArrayList<>();
private File file;
@Override
public void onCreate() {
mProvider = new SyncedFolderProvider(MainApp.getAppContext().getContentResolver());
monitor = new FileAlterationMonitor();
fileFilter = new FileFilter() {
@Override
public boolean accept(File pathname) {
return !pathname.getName().startsWith(".") && !pathname.getName().endsWith(".tmp");
}
};
file = new File(MainApp.getAppContext().getExternalFilesDir(null).getAbsolutePath() + File.separator +
"nc_persistence");
boolean readPerstistanceEntries = false;
if (file.exists()) {
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
boolean cont = true;
while (cont) {
Object obj = ois.readObject();
if (obj != null)
pairArrayList.add((SerializablePair) obj);
else
cont = false;
}
readPerstistanceEntries = true;
} catch (FileNotFoundException e) {
Log_OC.d(TAG, "Failed with FileNotFound while reading persistence file");
} catch (EOFException e) {
Log_OC.d(TAG, "Failed with EOFException while reading persistence file");
readPerstistanceEntries = true;
} catch (IOException e) {
Log_OC.d(TAG, "Failed with IOException while reading persistence file");
} catch (ClassNotFoundException e) {
Log_OC.d(TAG, "Failed with ClassNotFound while reading persistence file");
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
Log_OC.d(TAG, "Failed with closing FIS");
}
}
}
Log_OC.d(TAG, "start");
if (pairArrayList.size() == 0) {
for (SyncedFolder syncedFolder : mProvider.getSyncedFolders()) {
if (syncedFolder.isEnabled() && !syncedFolderMap.containsKey(syncedFolder)) {
Log_OC.d(TAG, "start observer: " + syncedFolder.getLocalPath());
FileAlterationMagicObserver observer = new FileAlterationMagicObserver(new File(
syncedFolder.getLocalPath()), fileFilter);
try {
observer.init();
SerializablePair pair = new SerializablePair<>(syncedFolder,
observer.getRootEntry());
pairArrayList.add(pair);
} catch (Exception e) {
Log_OC.d(TAG, "Failed getting an observer to intialize");
}
observer.addListener(new FileAlterationMagicListener(syncedFolder));
monitor.addObserver(observer);
syncedFolderMap.put(syncedFolder, observer);
}
}
} else {
for (int i = 0; i < pairArrayList.size(); i++) {
SyncedFolder syncFolder = pairArrayList.get(i).getKey();
for (SyncedFolder syncedFolder : mProvider.getSyncedFolders()) {
if (syncFolder.getId() == syncedFolder.getId()) {
syncFolder = syncedFolder;
pairArrayList.set(i, new SerializablePair(syncFolder,
pairArrayList.get(i).getValue()));
break;
}
}
FileAlterationMagicObserver observer = new FileAlterationMagicObserver(new File(
syncFolder.getLocalPath()), fileFilter);
observer.setRootEntry(pairArrayList.get(i).getValue());
observer.addListener(new FileAlterationMagicListener(syncFolder));
monitor.addObserver(observer);
syncedFolderMap.put(syncFolder, observer);
}
}
writePersistenceEntries(readPerstistanceEntries, file);
try {
monitor.start();
} catch (Exception e) {
Log_OC.d(TAG, "Something went very wrong at onStartCommand");
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return Service.START_NOT_STICKY;
}
private void writePersistenceEntries(boolean readPerstistanceEntries, File file) {
FileOutputStream fos = null;
try {
if (pairArrayList.size() > 0 && !readPerstistanceEntries) {
File newFile = new File(file.getAbsolutePath());
if (!newFile.exists()) {
newFile.createNewFile();
}
fos = new FileOutputStream(new File(file.getAbsolutePath()), false);
ObjectOutputStream os = new ObjectOutputStream(fos);
for (int i = 0; i < pairArrayList.size(); i++) {
os.writeObject(pairArrayList.get(i));
}
os.close();
} else if (file.exists() && pairArrayList.size() == 0) {
FileUtils.deleteQuietly(file);
}
if (fos != null) {
fos.close();
}
} catch (FileNotFoundException e) {
Log_OC.d(TAG, "Failed writing to nc_sync_persistance file via FileNotFound");
} catch (IOException e) {
Log_OC.d(TAG, "Failed writing to nc_sync_persistance file via IOException");
}
}
public void syncToDisk(boolean destructive) {
for (SyncedFolder syncedFolder : syncedFolderMap.keySet()) {
FileAlterationMagicObserver obs = syncedFolderMap.get(syncedFolder);
for (int i = 0; i < pairArrayList.size(); i++) {
SyncedFolder pairSyncedFolder = pairArrayList.get(i).getKey();
if (pairSyncedFolder.equals(syncedFolder)) {
SerializablePair newPairEntry = new SerializablePair<>(syncedFolder,
obs.getRootEntry());
pairArrayList.set(i, newPairEntry);
break;
}
}
if (destructive) {
monitor.removeObserver(obs);
syncedFolderMap.remove(obs);
}
try {
obs.destroy();
} catch (Exception e) {
Log_OC.d(TAG, "Something went very wrong at onDestroy");
}
}
if (destructive) {
try {
monitor.stop();
} catch (Exception e) {
Log_OC.d(TAG, "Something went very wrong at onDestroy");
}
}
writePersistenceEntries(false, file);
}
@Override
public void onDestroy() {
syncToDisk(true);
}
/**
* Restart oberver if it is enabled
* If syncedFolder exists already, use it, otherwise create new observer
*
* @param syncedFolder
*/
public void restartObserver(SyncedFolder syncedFolder) {
FileAlterationMagicObserver fileAlterationObserver;
Log_OC.d(TAG, "stop observer: " + syncedFolder.getLocalPath());
SyncedFolder syncy = null;
for (SyncedFolder syncyFolder : syncedFolderMap.keySet()) {
if (syncyFolder.getId() == syncedFolder.getId()) {
syncy = syncyFolder;
break;
}
}
if (syncedFolder.isEnabled()) {
if (syncy != null) {
Log_OC.d(TAG, "start observer Restart: " + syncedFolder.getLocalPath() + " " + syncedFolder.getAccount());
fileAlterationObserver = syncedFolderMap.get(syncy);
monitor.removeObserver(fileAlterationObserver);
fileAlterationObserver.removeListener(null);
fileAlterationObserver.addListener(new FileAlterationMagicListener(syncedFolder));
syncedFolderMap.remove(syncy);
syncedFolderMap.put(syncedFolder, fileAlterationObserver);
monitor.addObserver(fileAlterationObserver);
// remove it from the paired array list
for (int i = 0; i < pairArrayList.size(); i++) {
if (syncy.getId() == pairArrayList.get(i).getKey().getId()) {
pairArrayList.remove(i);
break;
}
}
pairArrayList.add(new SerializablePair(syncedFolder,
fileAlterationObserver.getRootEntry()));
} else {
Log_OC.d(TAG, "start observer Restart noMap: " + syncedFolder.getLocalPath() + " " + syncedFolder.getAccount());
fileAlterationObserver = new FileAlterationMagicObserver(new File(syncedFolder.getLocalPath()),
fileFilter);
try {
fileAlterationObserver.init();
SerializablePair pair = new SerializablePair<>(syncedFolder,
fileAlterationObserver.getRootEntry());
pairArrayList.add(pair);
} catch (Exception e) {
Log_OC.d(TAG, "Failed getting an observer to intialize");
}
fileAlterationObserver.addListener(new FileAlterationMagicListener(syncedFolder));
monitor.addObserver(fileAlterationObserver);
try {
syncedFolderMap.put(syncedFolder, fileAlterationObserver);
} catch (Exception e) {
Log_OC.d(TAG, "Something went very wrong on RestartObserver");
}
}
} else {
fileAlterationObserver = syncedFolderMap.get(syncy);
monitor.removeObserver(fileAlterationObserver);
try {
fileAlterationObserver.destroy();
} catch (Exception e) {
Log_OC.d(TAG, "Something went very wrong at onDestroy");
}
// remove it from the paired array list
for (int i = 0; i < pairArrayList.size(); i++) {
if (syncy.getId() == pairArrayList.get(i).getKey().getId()) {
pairArrayList.remove(i);
break;
}
}
syncedFolderMap.remove(syncy);
}
writePersistenceEntries(false, file);
}
@Override
public IBinder onBind(Intent arg0) {
return mBinder;
}
public class SyncedFolderObserverBinder extends Binder {
public SyncedFolderObserverService getService() {
return SyncedFolderObserverService.this;
}
}
}