/*
* Copyright (c) 2014 Jonas Kalderstam.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.nononsenseapps.notepad.data.remote.orgmodedropbox;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import com.dropbox.client2.DropboxAPI;
import com.dropbox.client2.android.AndroidAuthSession;
import com.dropbox.client2.exception.DropboxException;
import com.dropbox.client2.exception.DropboxServerException;
import com.nononsenseapps.notepad.data.local.orgmode.Monitor;
import com.nononsenseapps.notepad.data.local.orgmode.Synchronizer;
import com.nononsenseapps.notepad.data.local.orgmode.SynchronizerInterface;
import com.nononsenseapps.notepad.ui.settings.SyncPrefs;
import org.cowboyprogrammer.org.OrgFile;
import org.cowboyprogrammer.org.parser.OrgParser;
import org.cowboyprogrammer.org.parser.RegexParser;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.HashSet;
public class DropboxSynchronizer extends Synchronizer implements
SynchronizerInterface {
// Where files are kept. User changeable in preferences.
public static final String DEFAULT_DIR = "/NoNonsenseNotes/";
public static final String PREF_DIR = SyncPrefs.KEY_DROPBOX_DIR;
public static final String PREF_ENABLED = SyncPrefs.KEY_DROPBOX_ENABLE;
public final static String SERVICENAME = "DROPBOXORG";
protected final boolean enabled;
private final DropboxAPI<AndroidAuthSession> mDBApi;
private final String folderpath;
protected DropboxAPI.Entry DIR;
private DropboxAPI<AndroidAuthSession> dbApi = null;
public DropboxSynchronizer(final Context context) {
super(context);
final SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
enabled = prefs.getBoolean(PREF_ENABLED, false);
mDBApi = DropboxSyncHelper.getDBApi(context);
folderpath = prefs.getString(PREF_DIR, DEFAULT_DIR);
}
/**
* @return A unique name for this service. Should be descriptive, like
* DropboxOrg, SDOrg or SSHOrg.
*/
@Override
public String getServiceName() {
return SERVICENAME;
}
/**
* @return The username of the configured service. Likely an e-mail.
*/
@Override
public String getAccountName() {
try {
return mDBApi.accountInfo().email;
} catch (DropboxException e) {
return "ERROR";
}
}
/**
* Returns true if the synchronizer has been configured. This is called
* before synchronization. It will be true if the user has selected an
* account, folder etc...
*/
@Override
public boolean isConfigured() {
if (!enabled) return false;
// Need to ask dropbox if we are linked.
if (mDBApi.getSession().isLinked()) {
// Create default dir if necessary
try {
DIR = mDBApi.metadata(folderpath, 1, null, false, null);
return DIR.isDir;
} catch (DropboxServerException e) {
if (e.error == 404) {
try {
DIR = mDBApi.createFolder(folderpath);
return DIR.isDir;
} catch (DropboxException e1) {
Log.e(TAG, "isConfigured: " + e.reason);
}
} else {
Log.e(TAG, "isConfigured: " + e.reason);
}
} catch (DropboxException e) {
Log.e(TAG, "isConfigured catchall");
}
}
return false;
}
/**
* Returns an OrgFile object with a filename set that is guaranteed to
* not already exist. Use this method to avoid having multiple objects
* pointing to the same file.
*
* @param desiredName The name you'd want. If it exists,
* it will be used as the base in desiredName1,
* desiredName2, etc. Limited to 99.
* @return an OrgFile guaranteed not to exist.
* @throws java.io.IOException
* @throws IllegalArgumentException
*/
@Override
public OrgFile getNewFile(final String desiredName) throws IOException, IllegalArgumentException {
if (desiredName.contains("/")) {
throw new IOException("Filename can't contain /");
}
OrgParser orgParser = new RegexParser();
String filename;
try {
for (int i = 0; i < 100; i++) {
if (i == 0) {
filename = desiredName + ".org";
} else {
filename = desiredName + i + ".org";
}
try {
DropboxAPI.Entry entry = mDBApi.metadata(join(folderpath, filename), 1, null, false, null);
// If entry is returned, it exists, move to next iteration step
} catch (DropboxServerException e) {
if (404 == e.error) {
// No such file exists, great!
return new OrgFile(orgParser, filename);
} else {
throw e;
}
}
}
} catch (DropboxServerException e) {
throw new IOException("" + e.reason);
} catch (DropboxException e) {
throw new IOException();
}
throw new IllegalArgumentException("Filename not accessible");
}
private String join(String folderpath, String filename) {
if (folderpath == null || filename == null) {
throw new NullPointerException();
}
if (folderpath.endsWith("/")) {
if (filename.startsWith("/")) {
return folderpath + filename.substring(1);
} else {
return folderpath + filename;
}
} else {
if (filename.startsWith("/")) {
return folderpath + filename;
} else {
return folderpath + "/" + filename;
}
}
}
/**
* Replaces the file on the remote end with the given content.
*
* @param orgFile The file to save. Uses the filename stored in the object.
*/
@Override
public void putRemoteFile(final OrgFile orgFile) throws IOException {
try {
byte[] bytes = orgFile.treeToString().getBytes(Charset.forName("UTF-8"));
InputStream stream = new ByteArrayInputStream(orgFile.treeToString().getBytes(Charset.forName("UTF-8")));
mDBApi.putFileOverwrite(join(folderpath, orgFile.getFilename()), stream, bytes.length, null);
} catch (DropboxServerException e) {
throw new IOException("" + e.reason);
} catch (DropboxException e) {
throw new IOException("" + e.getMessage());
}
}
/**
* Delete the file on the remote end.
*
* @param orgFile The file to delete.
*/
@Override
public void deleteRemoteFile(final OrgFile orgFile) throws IOException {
if (orgFile == null || orgFile.getFilename() == null) {
// Nothing to do
return;
}
try {
mDBApi.delete(join(folderpath, orgFile.getFilename()));
} catch (DropboxServerException e) {
if (404 == e.error) {
// This is ok, already deleted
// ignore
} else {
throw new IOException("" + e.reason);
}
} catch (DropboxException e) {
throw new IOException("" + e.getMessage());
}
}
/**
* Rename the file on the remote end.
*
* @param oldName The name it is currently stored as on the remote end.
* @param orgFile
*/
@Override
public void renameRemoteFile(final String oldName, final OrgFile orgFile) throws IOException {
if (orgFile == null || orgFile.getFilename() == null) {
throw new NullPointerException("No new filename");
}
try {
mDBApi.move(join(folderpath, oldName), join(folderpath, orgFile.getFilename()));
} catch (DropboxServerException e) {
throw new IOException("" + e.reason);
} catch (DropboxException e) {
throw new IOException("" + e.getMessage());
}
}
/**
* Returns a BufferedReader to the remote file. Null if it doesn't exist.
*
* @param filename Name of the file, without path
*/
@Override
public BufferedReader getRemoteFile(final String filename) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
mDBApi.getFile(join(folderpath, filename), null, baos, null);
return new BufferedReader(new StringReader(baos.toString("UTF-8")));
} catch (DropboxServerException e) {
throw new IOException("" + e.reason);
} catch (DropboxException e) {
throw new IOException("" + e.getMessage());
} catch (UnsupportedEncodingException e) {
throw new IOException("" + e.getMessage());
}
}
/**
* @return a set of all remote files.
*/
@Override
public HashSet<String> getRemoteFilenames() throws IOException {
final HashSet<String> filenames = new HashSet<String>();
try {
DropboxAPI.Entry entry = mDBApi.metadata(folderpath, -1, null, true, null);
for (DropboxAPI.Entry file: entry.contents) {
if (!file.isDir && file.fileName().toLowerCase().endsWith(".org")) {
filenames.add(file.fileName());
}
}
} catch (DropboxServerException e) {
throw new IOException("" + e.reason);
} catch (DropboxException e) {
throw new IOException("" + e.getMessage());
}
return filenames;
}
/**
* Use this to disconnect from any services and cleanup.
*/
@Override
public void postSynchronize() {
}
/**
* @return a Monitor for this source. May be null.
*/
@Override
public Monitor getMonitor() {
return null;
}
}