/**
*
* Funf: Open Sensing Framework
* Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland.
* Acknowledgments: Alan Gardner
* Contact: nadav@media.mit.edu
*
* This file is part of Funf.
*
* Funf is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* Funf 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Funf. If not, see <http://www.gnu.org/licenses/>.
*
*/
package edu.mit.media.funf.storage;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.NetworkInfo.State;
import android.os.Binder;
import android.os.IBinder;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import edu.mit.media.funf.util.EqualsUtil;
import edu.mit.media.funf.util.HashCodeUtil;
import edu.mit.media.funf.util.LockUtil;
import edu.mit.media.funf.util.LogUtil;
public abstract class UploadService extends Service {
public static final int
MAX_REMOTE_ARCHIVE_RETRIES = 6,
MAX_FILE_RETRIES = 3;
public static final int
NETWORK_ANY = 0,
NETWORK_WIFI_ONLY = 1;
public static final String
ARCHIVE_ID = "archive_id",
REMOTE_ARCHIVE_ID = "remote_archive_id",
NETWORK = "network";
private ConnectivityManager connectivityManager;
private Map<String, Integer> fileFailures;
private Map<String, Integer> remoteArchiveFailures;
private Queue<ArchiveFile> filesToUpload;
private Thread uploadThread;
private WakeLock lock;
@Override
public void onCreate() {
Log.i(LogUtil.TAG, "Creating...");
connectivityManager =(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
lock = LockUtil.getWakeLock(this);
fileFailures = new HashMap<String, Integer>();
remoteArchiveFailures = new HashMap<String, Integer>();
filesToUpload = new ConcurrentLinkedQueue<ArchiveFile>();
// TODO: consider and add multiple upload threads
uploadThread = new Thread(new Runnable() {
@Override
public void run() {
while(Thread.currentThread().equals(uploadThread) && !filesToUpload.isEmpty()) {
ArchiveFile archiveFile = filesToUpload.poll();
runArchive(archiveFile.archive, archiveFile.remoteArchive, archiveFile.file, archiveFile.network);
}
uploadThread = null;
stopSelf();
}
});
}
@Override
public void onDestroy() {
if (uploadThread != null && uploadThread.isAlive()) {
uploadThread = null;
}
if (lock.isHeld()) {
lock.release();
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(LogUtil.TAG, "Starting...");
int network = intent.getIntExtra(NETWORK, NETWORK_ANY);
if (isOnline(network)) {
String archiveName = intent.getStringExtra(ARCHIVE_ID);
String remoteArchiveName = intent.getStringExtra(REMOTE_ARCHIVE_ID);
if (archiveName != null && remoteArchiveName != null) {
FileArchive archive = getArchive(archiveName);
RemoteFileArchive remoteArchive = getRemoteArchive(remoteArchiveName);
if (archive != null && remoteArchive != null) {
for (File file : archive.getAll()) {
archive(archive, remoteArchive, file, network);
}
}
}
}
// Start upload thread if necessary, even if no files to ensure stop
if (uploadThread != null && !uploadThread.isAlive()) {
uploadThread.start();
}
return Service.START_STICKY;
}
/**
* Returns the file archive that the upload service will use.
* @param databaseName
* @return
*/
public FileArchive getArchive(String name) {
return DefaultArchive.getArchive(this, name);
}
/**
* Get the remote archive with the specified name
* @param name
* @return
*/
protected abstract RemoteFileArchive getRemoteArchive(final String name);
public void archive(FileArchive archive, RemoteFileArchive remoteArchive, File file, int network) {
ArchiveFile archiveFile = new ArchiveFile(archive, remoteArchive, file, network);
if (!filesToUpload.contains(archiveFile)) {
Log.i(LogUtil.TAG, "Queuing " + file.getName());
filesToUpload.offer(archiveFile);
}
}
protected void runArchive(FileArchive archive, RemoteFileArchive remoteArchive, File file, int network) {
Integer numRemoteFailures = remoteArchiveFailures.get(remoteArchive.getId());
numRemoteFailures = (numRemoteFailures == null) ? 0 : numRemoteFailures;
Log.i(LogUtil.TAG, "numRemoteFailures:" + numRemoteFailures );
Log.i(LogUtil.TAG, "isOnline:" + isOnline(network) );
if (numRemoteFailures < MAX_REMOTE_ARCHIVE_RETRIES && isOnline(network)) {
Log.i(LogUtil.TAG, "Archiving..." + file.getName());
if(remoteArchive.add(file)) {
archive.remove(file);
} else {
Integer numFileFailures = fileFailures.get(file.getName());
numFileFailures = (numFileFailures == null) ? 1 : numFileFailures + 1;
numRemoteFailures += 1;
fileFailures.put(file.getName(), numFileFailures);
remoteArchiveFailures.put(remoteArchive.getId(), numRemoteFailures);
// 3 Attempts
if (numFileFailures < MAX_FILE_RETRIES) {
filesToUpload.offer(new ArchiveFile(archive, remoteArchive, file, network));
} else {
Log.i(LogUtil.TAG, "Failed to upload '" + file.getAbsolutePath() + "' after 3 attempts.");
}
}
} else {
Log.i(LogUtil.TAG, "Canceling upload. Remote archive '" + remoteArchive.getId() + "' is not currently available.");
}
}
/**
* Convenience class for pairing the database name with the db file
*/
protected class ArchiveFile {
public final FileArchive archive;
public final RemoteFileArchive remoteArchive;
public final File file;
public final int network;
public ArchiveFile(FileArchive archive, RemoteFileArchive remoteArchive, File file, int network) {
this.archive = archive;
this.remoteArchive = remoteArchive;
this.file = file;
this.network = network;
}
@Override
public boolean equals(Object o) {
return o != null && o instanceof ArchiveFile
&& EqualsUtil.areEqual(remoteArchive.getId(), ((ArchiveFile)o).remoteArchive.getId())
&& EqualsUtil.areEqual(file, ((ArchiveFile)o).file);
}
@Override
public int hashCode() {
return HashCodeUtil.hash(HashCodeUtil.hash(HashCodeUtil.SEED, file), remoteArchive.getId());
}
}
public boolean isOnline(int network) {
NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo();
if (network == NETWORK_ANY && netInfo != null && netInfo.isConnectedOrConnecting()) {
return true;
} else if (network == NETWORK_WIFI_ONLY ) {
Log.i(LogUtil.TAG,"we are in isOnline(): NETOWORK_WIFI_ONLY "+ network);
Log.i(LogUtil.TAG, "Prining out debugging info of connectivityStatus: ");
State wifiInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState();
Log.i(LogUtil.TAG, wifiInfo.toString());
if (State.CONNECTED.equals(wifiInfo) || State.CONNECTING.equals(wifiInfo)) {
return true;
}
}
return false;
}
/**
* Binder interface to the probe
*/
public class LocalBinder extends Binder {
public UploadService getService() {
return UploadService.this;
}
}
private final IBinder mBinder = new LocalBinder();
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}