/*
*
* Panbox - encryption for cloud storage
* Copyright (C) 2014-2015 by Fraunhofer SIT and Sirrix AG
*
* This program 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/>.
*
* Additonally, third party code may be provided with notices and open source
* licenses from communities and third parties that govern the use of those
* portions, and any licenses granted hereunder do not alter any rights and
* obligations you may have under such open source licenses, however, the
* disclaimer of warranty and limitation of liability provisions of the GPLv3
* will apply to all the product.
*
*/
package org.panbox.desktop.common.vfs.backend.dropbox;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.swing.JOptionPane;
import org.apache.log4j.Logger;
import org.panbox.PanboxConstants;
import org.panbox.Settings;
import org.panbox.core.csp.CSPException;
import org.panbox.core.csp.ICSPAPIIntegration;
import org.panbox.core.csp.Revision;
import org.panbox.core.keymgmt.Volume;
import org.panbox.desktop.common.utils.DesktopApi;
import com.dropbox.core.DbxAccountInfo;
import com.dropbox.core.DbxAppInfo;
import com.dropbox.core.DbxAuthFinish;
import com.dropbox.core.DbxClient;
import com.dropbox.core.DbxEntry;
import com.dropbox.core.DbxException;
import com.dropbox.core.DbxRequestConfig;
import com.dropbox.core.DbxWebAuthNoRedirect;
import com.dropbox.core.DbxWriteMode;
/**
* Created by Timo Nolle on 01.09.14.
*/
public class DropboxAPIIntegration implements ICSPAPIIntegration {
private static final ResourceBundle bundle = ResourceBundle.getBundle(
"org/panbox/desktop/common/gui/Messages", Settings.getInstance()
.getLocale());
private final static Logger logger = Logger
.getLogger(DropboxAPIIntegration.class);
private static final String METADATA_PATH = "/"
+ PanboxConstants.PANBOX_SHARE_METADATA_DIRECTORY + "/"
+ Volume.DB_FILE;
public DbxClient client;
private static DropboxAPIIntegration instance = null;
private DbxRequestConfig dbxRConfig;
private DropboxAPIIntegration() {
this.dbxRConfig = new DbxRequestConfig("panbox.org", Settings
.getInstance().getLocale().toString());
String ACCESS_TOKEN = Settings.getInstance().getDropboxAccessToken();
try {
if ((ACCESS_TOKEN == null) || ACCESS_TOKEN.equals("")) {
ACCESS_TOKEN = authenticate();
}
client = new DbxClient(dbxRConfig, ACCESS_TOKEN);
Settings.getInstance().setDropboxAccessToken(ACCESS_TOKEN);
} catch (DropboxIntegrationException e) {
logger.error("No access token available!", e);
}
}
private DropboxAPIIntegration(String accessToken) {
this.dbxRConfig = new DbxRequestConfig("panbox.org", Settings
.getInstance().getLocale().toString());
client = new DbxClient(dbxRConfig, accessToken);
}
/**
* Get singleton instance of DropboxIntegration
*
* @return
* @throws DropboxIntegrationException
*/
public synchronized static DropboxAPIIntegration getInstance() {
if (instance == null) {
instance = new DropboxAPIIntegration();
}
return instance;
}
/**
* get the share metadata file for a given share
*
* @param shareName
* @return
* @throws Exception
*/
@Override
public File getShareMetadata(String shareName) throws CSPIOException,
CSPApiException {
try {
File tmp = File.createTempFile("panbox", "tmp");
OutputStream o = new FileOutputStream(tmp);
client.getFile("/" + shareName + METADATA_PATH, "", o);
o.flush();
o.close();
return tmp;
} catch (IOException e) {
throw new CSPIOException(e);
} catch (DbxException e) {
throw new CSPApiException(e);
}
}
@Override
public String getLatestShareMetadataVersion(String shareName)
throws CSPApiException {
try {
return client.getRevisions("/" + shareName + METADATA_PATH).get(0)
.asFile().rev;
} catch (DbxException e) {
throw new CSPApiException(e);
}
}
@Override
public String uploadFile(String serverPath, File f) throws CSPIOException,
CSPApiException {
if ((f == null) || !f.exists() || !f.canRead()) {
throw new CSPIOException("Could not access file " + f);
} else {
FileInputStream fis = null;
try {
fis = new FileInputStream(f);
DbxEntry.File uploadedFile = client.uploadFile(serverPath,
DbxWriteMode.force(), f.length(), fis);
return uploadedFile.path;
} catch (DbxException e) {
throw new CSPApiException("Error uploading file!", e);
} catch (IOException e) {
throw new CSPIOException(e);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
throw new CSPIOException(e);
}
}
}
}
}
@Override
public URI publishFile(File f) throws CSPIOException, CSPApiException {
String uploadPath = DropboxConstants.DB_PUBLIC_FOLDER
+ DropboxConstants.DB_SEPARATOR + f.getName();
return createPublicLink(uploadFile(uploadPath, f));
}
@Override
public URI createPublicLink(String path) throws CSPApiException {
try {
String res = client.createShareableUrl(path);
return URI.create(res.toString().replace("dl=0", "dl=1"));
} catch (DbxException e) {
throw new CSPApiException("Could not create public link.");
}
}
@Override
public void inviteUser(String shareName, String userIdentifier)
throws CSPException {
DesktopApi.browse(getShareConfigurationURL(shareName, false));
}
public void inviteUser(String shareName) throws CSPException {
this.inviteUser(shareName, "");
}
@Override
public void removeUser(String shareName, String userIdentifier)
throws CSPException {
DesktopApi.browse(getShareConfigurationURL(shareName, true));
}
public void removeUser(String shareName) throws CSPException {
this.removeUser(shareName, "");
}
public URI getShareConfigurationURL(String shareName,
boolean hasParticipants) throws CSPException {
try {
if (hasParticipants) {
return new URI(DropboxConstants.DB_MODE,
DropboxConstants.DB_URL,
DropboxConstants.DB_SHARE_PREFIX + shareName,
DropboxConstants.DB_PARAM_SHAREOPTIONS, null);
} else {
return new URI(DropboxConstants.DB_MODE,
DropboxConstants.DB_URL,
DropboxConstants.DB_SHARE_PREFIX + shareName,
DropboxConstants.DB_PARAM_SHARE, null);
}
} catch (URISyntaxException e) {
throw new CSPException(
"Share configuration URL could not be generated.");
}
}
/**
* Authenticates the client with the users dropbox and returns the access
* token which can then be used in the future.
*
* @return The access token for the user
* @throws DropboxIntegrationException
*/
public static String authenticate() throws DropboxIntegrationException {
DbxAppInfo appInfo = new DbxAppInfo(DropboxConstants.APP_KEY,
DropboxConstants.APP_SECRET);
DbxRequestConfig dbxRConfig = new DbxRequestConfig("panbox.org",
Settings.getInstance().getLocale().toString());
DbxWebAuthNoRedirect webAuth = new DbxWebAuthNoRedirect(dbxRConfig,
appInfo);
// open browser - user has to allow the app and then copy the access
// token
String authorizeUrl = webAuth.start();
DbxAuthFinish authFinish = null;
try {
JOptionPane.showMessageDialog(null, bundle
.getString("DropboxApiIntegration.copyAccessTokenMessage"),
bundle.getString("DropboxAPIIntegration.allowAccess"),
JOptionPane.INFORMATION_MESSAGE);
DesktopApi.browse(new URI(authorizeUrl));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// do nothing
}
String code = JOptionPane.showInputDialog(null,
bundle.getString("DropboxAPIIntegration.enterAccessToken"))
.trim();
// String code = new BufferedReader(new
// InputStreamReader(System.in))
// .readLine().trim();
authFinish = webAuth.finish(code);
} catch (URISyntaxException | DbxException e) {
throw new DropboxIntegrationException(e);
}
if (instance != null) {
// set new client in instance
instance.client = new DbxClient(dbxRConfig, authFinish.accessToken);
}
// and return the access token
logger.info("Received Dropbox authentication token "
+ authFinish.accessToken);
return authFinish.accessToken;
}
@Override
public Date getServerTime() throws CSPException {
return getServerTimeViaFile();
}
public Date getServerTimeViaHeader() throws DbxException, IOException,
ParseException {
URL url = new URL("https://api.dropbox.com");
URLConnection conn = url.openConnection();
String sDate = conn.getHeaderField("date");
Date date = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z")
.parse(sDate);
return date;
}
public Date getServerTimeViaFile() throws CSPException {
return getServerTimeViaFile("");
}
private Date getServerTimeViaFile(String path) throws CSPException {
try {
String filename = path + "/" + UUID.randomUUID().toString();
File tmp = File.createTempFile("panbox-tmp",
String.valueOf(System.currentTimeMillis()));
// FileWriter out = new FileWriter(tmp);
// out.write('1');
// out.close();
client.uploadFile(filename, DbxWriteMode.force(), tmp.length(),
new FileInputStream(tmp));
Date date = getLastModificationDate(filename);
client.delete(filename);
tmp.delete();
return date;
} catch (IOException e) {
throw new CSPIOException(e);
} catch (DbxException e) {
throw new CSPApiException(e);
}
}
@Override
public Date getLastModificationDate(String path)
throws CSPFileNotFoundException, CSPApiException {
try {
DbxEntry entry = client.getMetadata(path);
if (entry == null) {
throw new FileNotFoundException("File at location " + path
+ " could not be found!");
} else {
return entry.asFile().lastModified;
}
} catch (DbxException e) {
throw new CSPApiException(e);
} catch (FileNotFoundException e) {
throw new CSPFileNotFoundException(e);
}
}
@Override
public synchronized boolean createLock(String path) throws CSPIOException,
CSPApiException {
String filename = path + DropboxConstants.LOCK_SUFFIX;
File tmp = null;
try {
tmp = File.createTempFile(UUID.randomUUID().toString(), "");
} catch (IOException e) {
throw new CSPIOException(e);
}
if (!isLocked(path)) {
try {
client.uploadFile(filename, DbxWriteMode.force(), tmp.length(),
new FileInputStream(tmp));
return true;
} catch (DbxException | IOException e) {
try {
client.delete(filename);
if (e instanceof DbxException) {
throw new CSPApiException(e);
} else if (e instanceof IOException) {
throw new CSPIOException(e);
}
} catch (DbxException e1) {
logger.error("Could not remove lock file after failure!", e);
throw new CSPApiException(e1);
}
}
}
return false;
}
@Override
public synchronized boolean createTemporaryLock(String path)
throws CSPIOException, CSPApiException {
String filename = path + DropboxConstants.TEMP_LOCK_SUFFIX;
File tmp = null;
try {
tmp = File.createTempFile(UUID.randomUUID().toString(), "");
} catch (IOException e) {
throw new CSPIOException(e);
}
if (!isLocked(path)) {
try {
client.uploadFile(filename, DbxWriteMode.force(), tmp.length(),
new FileInputStream(tmp));
return true;
} catch (DbxException | IOException e) {
try {
client.delete(filename);
if (e instanceof DbxException) {
throw new CSPApiException(e);
} else if (e instanceof IOException) {
throw new CSPIOException(e);
}
} catch (DbxException e1) {
logger.error("Could not remove lock file after failure!", e);
throw new CSPApiException(e1);
}
}
}
return false;
}
@Override
public synchronized boolean isLocked(String path) throws CSPApiException {
try {
DbxEntry lockFile = client.getMetadata(path
+ DropboxConstants.LOCK_SUFFIX);
DbxEntry tempLockFile = client.getMetadata(path
+ DropboxConstants.TEMP_LOCK_SUFFIX);
if (lockFile != null) {
return true;
} else if (tempLockFile != null) {
if (new Date().compareTo(tempLockFile.asFile().lastModified) < DropboxConstants.TEMP_LOCK_DURATION) {
return true;
} else {
releaseTemporaryLock(path);
return false;
}
} else {
return false;
}
} catch (DbxException e) {
throw new CSPApiException(e);
}
}
@Override
public synchronized void releaseLock(String path) throws CSPApiException {
try {
client.delete(path + DropboxConstants.LOCK_SUFFIX);
} catch (DbxException e) {
throw new CSPApiException(e);
}
}
@Override
public synchronized void releaseTemporaryLock(String path)
throws CSPApiException {
try {
client.delete(path + DropboxConstants.TEMP_LOCK_SUFFIX);
} catch (DbxException e) {
throw new CSPApiException(e);
}
}
@Override
public synchronized boolean exists(String path) throws CSPApiException {
try {
return (client.getMetadata(path) != null);
} catch (DbxException e) {
throw new CSPApiException(e);
}
}
@Override
public List<Revision> getRevisions(String path) throws CSPApiException {
try {
List<DbxEntry.File> revs = client.getRevisions(path);
List<Revision> out = new ArrayList<>();
for (DbxEntry.File f : revs) {
Revision rev = new Revision(f.rev, f.lastModified, f.numBytes);
out.add(rev);
}
return out;
} catch (DbxException e) {
throw new CSPApiException(e);
}
}
@Override
public boolean restoreToRevision(String path, String rev) {
try {
return client.restoreFile(path, rev) != null;
} catch (DbxException e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean supportsMetadataFreshness() {
// FIXME: only if freshness checks with server-based timestamps are
// working
return true;
}
@Override
public boolean isOnline() {
try {
DbxAccountInfo accountInfo = client.getAccountInfo();
if (accountInfo != null) {
return true;
} else {
return false;
}
} catch (DbxException e) {
// first, check if second try succeeds
String ACCESS_TOKEN = Settings.getInstance()
.getDropboxAccessToken();
client = new DbxClient(dbxRConfig, ACCESS_TOKEN);
try {
DbxAccountInfo accountInfo = client.getAccountInfo();
if (accountInfo != null) {
return true;
} else {
return false;
}
} catch (DbxException e2) {
return false;
}
}
}
@Override
public void deleteFile(String serverPath) throws CSPApiException {
try {
client.delete(serverPath);
} catch (DbxException e) {
throw new CSPApiException(e);
}
}
@Override
public void downloadFile(String remotePath, String targetPath)
throws CSPException {
try {
if (client.getMetadata(remotePath).isFile()) {
File targetFile = new File(targetPath);
if (!targetFile.getParentFile().exists()) {
targetFile.getParentFile().mkdirs();
}
OutputStream o = new FileOutputStream(targetFile);
client.getFile(remotePath, "", o);
o.flush();
o.close();
}
} catch (FileNotFoundException e) {
throw new CSPFileNotFoundException(e);
} catch (DbxException e) {
throw new CSPApiException(e);
} catch (IOException e) {
throw new CSPIOException(e);
}
}
@Override
public void downloadFolder(String remotePath, String targetPath)
throws CSPException {
downloadFolder(remotePath, targetPath, 0);
}
public void downloadFolder(String remotePath, String targetPath, int depth)
throws CSPException {
try {
DbxEntry folder = client.getMetadata(remotePath);
if (folder.isFolder()) {
File remoteFolder = new File(remotePath);
File targetFolder = new File(targetPath + File.separator
+ remoteFolder.getName());
if (!targetFolder.exists()) {
targetFolder.mkdirs();
}
DbxEntry.WithChildren folderWithChildren = client
.getMetadataWithChildren(remotePath);
for (DbxEntry file : folderWithChildren.children) {
if (depth >= DropboxConstants.MAX_TREE_SEARCH_DEPTH) {
break;
}
if (file.isFile()) {
downloadFile(file.path, targetPath + "/" + folder.name
+ "/" + file.name);
} else if (file.isFolder()) {
downloadFolder(file.path, targetPath + "/"
+ folder.name, depth + 1);
}
}
}
} catch (DbxException e) {
e.printStackTrace();
}
}
}