import com.dropbox.client2.exception.DropboxException;
import com.dropbox.client2.session.Session;
import com.dropbox.client2.session.WebAuthSession;
import com.dropbox.client2.session.AppKeyPair;
import com.dropbox.client2.session.AccessTokenPair;
import com.dropbox.client2.DropboxAPI;
import com.dropbox.client2.jsonextract.*;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
public class CopyBetweenAccounts
{
public static final String STATE_FILE = "CopyBetweenAccounts.json";
public static void main(String[] args)
throws DropboxException
{
if (args.length == 0) {
printUsage(System.out);
throw die();
}
String command = args[0];
if (command.equals("reset")) {
doReset(args);
}
else if (command.equals("link")) {
doLink(args);
}
else if (command.equals("list")) {
doList(args);
}
else if (command.equals("copy")) {
doCopy(args);
}
else {
System.err.println("ERROR: Unknown command: \"" + command + "\"");
System.err.println("Run with no arguments for help.");
throw die();
}
}
// ------------------------------------------------------------------------
// Reset state
private static void doReset(String[] args)
throws DropboxException
{
if (args.length != 3) {
throw die("ERROR: \"reset\" takes exactly two arguments.");
}
AppKeyPair appKeyPair = new AppKeyPair(args[1], args[2]);
// Save state
State state = new State(appKeyPair);
state.save(STATE_FILE);
}
// ------------------------------------------------------------------------
// Link another account.
private static void doLink(String[] args)
throws DropboxException
{
if (args.length != 1) {
throw die("ERROR: \"link\" takes no arguments.");
}
// Load state.
State state = State.load(STATE_FILE);
WebAuthSession was = new WebAuthSession(state.appKey, Session.AccessType.APP_FOLDER);
// Make the user log in and authorize us.
WebAuthSession.WebAuthInfo info = was.getAuthInfo();
System.out.println("1. Go to: " + info.url);
System.out.println("2. Allow access to this app.");
System.out.println("3. Press ENTER.");
try {
while (System.in.read() != '\n') {}
}
catch (IOException ex) {
throw die("I/O error: " + ex.getMessage());
}
// This will fail if the user didn't visit the above URL and hit 'Allow'.
String uid = was.retrieveWebAccessToken(info.requestTokenPair);
AccessTokenPair accessToken = was.getAccessTokenPair();
System.out.println("Link successful.");
state.links.put(uid, accessToken);
state.save(STATE_FILE);
}
// ------------------------------------------------------------------------
// Link another account.
private static void doList(String[] args)
throws DropboxException
{
if (args.length != 1) {
throw die("ERROR: \"list\" takes no arguments.");
}
// Load state.
State state = State.load(STATE_FILE);
if (state.links.isEmpty()) {
System.out.println("No links.");
}
else {
System.out.println("[uid: access token]");
for (Map.Entry<String,AccessTokenPair> link : state.links.entrySet()) {
AccessTokenPair at = link.getValue();
System.out.println(link.getKey() + ": " + at.key + " " + at.secret);
}
}
}
// ------------------------------------------------------------------------
// Copy a file
private static void doCopy(String[] args)
throws DropboxException
{
if (args.length != 3) {
throw die("ERROR: \"copy\" takes exactly two arguments");
}
// Load cached state.
State state = State.load(STATE_FILE);
GlobalPath source, target;
try {
source = GlobalPath.parse(args[1]);
}
catch (GlobalPath.FormatException ex) {
throw die("ERROR: Bad <source>: " + ex.getMessage());
}
try {
target = GlobalPath.parse(args[2]);
}
catch (GlobalPath.FormatException ex) {
throw die("ERROR: Bad <source>: " + ex.getMessage());
}
AccessTokenPair sourceAccess = state.links.get(source.uid);
if (sourceAccess == null) {
throw die("ERROR: <source> refers to UID that isn't linked.");
}
AccessTokenPair targetAccess = state.links.get(target.uid);
if (targetAccess == null) {
throw die("ERROR: <target> refers to UID that isn't linked.");
}
// Connect to the <source> UID and create a copy-ref.
WebAuthSession sourceSession = new WebAuthSession(state.appKey, Session.AccessType.DROPBOX, sourceAccess);
DropboxAPI<?> sourceClient = new DropboxAPI<WebAuthSession>(sourceSession);
DropboxAPI.CreatedCopyRef cr = sourceClient.createCopyRef(source.path);
// Connect to the <target> UID and add the target file.
WebAuthSession targetSession = new WebAuthSession(state.appKey, Session.AccessType.DROPBOX, targetAccess);
DropboxAPI<?> targetClient = new DropboxAPI<WebAuthSession>(targetSession);
targetClient.addFromCopyRef(cr.copyRef, target.path);
System.out.println("Copied.");
}
private static final class GlobalPath
{
public final String uid;
public final String path;
private GlobalPath(String uid, String path)
{
this.uid = uid;
this.path = path;
}
/**
* Parse a 'global path' string of the form "[uid]:[path]" into UID and path
* parts.
*/
public static GlobalPath parse(String s)
throws FormatException
{
int colonPos = s.indexOf(':');
if (colonPos < 0) throw new FormatException("missing colon");
String uid = s.substring(0, colonPos);
String path = s.substring(colonPos+1);
if (uid.length() == 0) throw new FormatException("empty UID");
if (!path.startsWith("/")) throw new FormatException("path doesn't start with \"/\": \"" + path + "\"");
return new GlobalPath(uid, path);
}
public static final class FormatException extends Exception
{
public FormatException(String message) { super(message); }
}
}
// ------------------------------------------------------------------------
private static void printUsage(PrintStream out)
{
out.println("Usage:");
out.println(" ./run reset <app-key> <secret> Initialize the state with the given app key.");
out.println(" ./run link Link an account to this app.");
out.println(" ./run list List accounts that have been linked.");
out.println(" ./run copy <source> <target> Copy a file; paths are of the form <uid>:<path>.");
}
private static RuntimeException die(String message)
{
System.err.println(message);
return die();
}
private static RuntimeException die()
{
System.exit(1);
return new RuntimeException();
}
// ------------------------------------------------------------------------
// State model (load+save to JSON)
public static final class State
{
public final AppKeyPair appKey;
public final Map<String,AccessTokenPair> links = new HashMap<String,AccessTokenPair>();
public State(AppKeyPair appKey)
{
this.appKey = appKey;
}
public void save(String fileName)
{
JSONObject jstate = new JSONObject();
// Convert app key
JSONArray japp = new JSONArray();
japp.add(appKey.key);
japp.add(appKey.secret);
jstate.put("app_key", japp);
// Convert 'Link' objects (uid -> access token)
JSONObject jlinks = new JSONObject();
for (Map.Entry<String,AccessTokenPair> link : links.entrySet()) {
String uid = link.getKey();
AccessTokenPair access = link.getValue();
JSONArray jaccess = new JSONArray();
jaccess.add(access.key);
jaccess.add(access.secret);
jlinks.put(uid, jaccess);
}
jstate.put("links", jlinks);
try {
FileWriter fout = new FileWriter(fileName);
try {
jstate.writeJSONString(fout);
}
finally {
fout.close();
}
}
catch (IOException ex) {
throw die("ERROR: unable to save to state file \"" + fileName + "\": " + ex.getMessage());
}
}
public static State load(String fileName)
{
JsonThing j;
try {
FileReader fin = new FileReader(fileName);
try {
j = new JsonThing(new JSONParser().parse(fin));
} catch (ParseException ex) {
throw die("ERROR: State file \"" + fileName + "\" isn't valid JSON: " + ex.getMessage());
} finally {
fin.close();
}
}
catch (IOException ex) {
throw die("ERROR: unable to load state file \"" + fileName + "\": " + ex.getMessage());
}
try {
JsonMap jm = j.expectMap();
JsonList japp = jm.get("app_key").expectList();
AppKeyPair appKey = new AppKeyPair(japp.get(0).expectString(), japp.get(1).expectString());
State state = new State(appKey);
JsonMap jlinks = jm.get("links").expectMap();
for (Map.Entry<String,JsonThing> jlink : jlinks) {
JsonList jaccess = jlink.getValue().expectList();
AccessTokenPair access = new AccessTokenPair(jaccess.get(0).expectString(), jaccess.get(1).expectString());
state.links.put(jlink.getKey(), access);
}
return state;
}
catch (JsonExtractionException ex) {
throw die ("ERROR: State file has incorrect structure: " + ex.getMessage());
}
}
}
public static final class Link
{
public final String uid;
public final AccessTokenPair accessToken;
public Link(String uid, AccessTokenPair accessToken)
{
this.uid = uid;
this.accessToken = accessToken;
}
}
}