abraunegg-onedrive/src/onedrive.d

255 lines
7.3 KiB
D
Raw Normal View History

2015-09-01 20:45:34 +02:00
module onedrive;
2015-09-01 22:23:42 +02:00
import std.datetime, std.json, std.net.curl, std.path, std.string, std.uni, std.uri;
2015-09-01 20:45:34 +02:00
extern(C) void signal(int sig, void function(int));
private immutable {
string authUrl = "https://login.live.com/oauth20_authorize.srf";
string redirectUrl = "https://login.live.com/oauth20_desktop.srf";
string tokenUrl = "https://login.live.com/oauth20_token.srf";
string itemByIdUrl = "https://api.onedrive.com/v1.0/drive/items/";
string itemByPathUrl = "https://api.onedrive.com/v1.0/drive/root:/";
}
class OneDriveException: Exception
{
@nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
{
super(msg, file, line, next);
}
@nogc @safe pure nothrow this(string msg, Throwable next, string file = __FILE__, size_t line = __LINE__)
{
super(msg, file, line, next);
}
}
final class OneDriveApi
{
private string clientId, clientSecret;
private string refreshToken, accessToken;
2015-09-01 22:23:42 +02:00
private SysTime accessTokenExpiration;
2015-09-01 20:45:34 +02:00
private HTTP http;
void function(string) onRefreshToken; // called when a new refresh_token is received
this(string clientId, string clientSecret)
{
this.clientId = clientId;
this.clientSecret = clientSecret;
http = HTTP();
//debug http.verbose = true;
// HACK: prevent SIGPIPE
//import etc.c.curl;
//http.handle.set(CurlOption.nosignal, 0);
//signal(/*SIGPIPE*/ 13, /*SIG_IGN*/ cast(void function(int)) 1);
}
~this()
{
http.shutdown();
}
void authorize()
{
import std.stdio, std.regex;
string url = authUrl ~ "?client_id=" ~ clientId ~ "&scope=wl.offline_access onedrive.readwrite&response_type=code&redirect_url=" ~ redirectUrl;
writeln("Authorize this app visiting:");
writeln(url);
while (true) {
char[] response;
write("Enter the response url: ");
readln(response);
auto c = matchFirst(response, r"(?:code=)(([\w\d]+-){4}[\w\d]+)");
if (!c.empty) {
c.popFront(); // skip whole match
redeemToken(c.front);
break;
}
}
}
void setRefreshToken(string refreshToken)
{
this.refreshToken = refreshToken;
newToken();
}
string getItemPath(const(char)[] id)
{
2015-09-01 22:23:42 +02:00
checkAccessTokenExpired();
2015-09-01 20:45:34 +02:00
JSONValue response = get(itemByIdUrl ~ id ~ "/?select=name,parentReference");
string path;
try {
path = response["parentReference"].object["path"].str;
} catch (JSONException e) {
// root does not have parentReference
return "";
}
path = decodeComponent(path[path.indexOf(':') + 1 .. $]);
return buildNormalizedPath("." ~ path ~ "/" ~ response["name"].str);
}
string getItemId(const(char)[] path)
{
2015-09-01 22:23:42 +02:00
checkAccessTokenExpired();
2015-09-01 20:45:34 +02:00
JSONValue response = get(itemByPathUrl ~ encodeComponent(path) ~ ":/?select=id");
return response["id"].str;
}
// https://dev.onedrive.com/items/view_changes.htm
JSONValue viewChangesById(const(char)[] id, const(char)[] statusToken)
{
2015-09-01 22:23:42 +02:00
checkAccessTokenExpired();
2015-09-01 20:45:34 +02:00
char[] url = itemByIdUrl ~ id ~ "/view.changes";
if (statusToken) url ~= "?token=" ~ statusToken;
return get(url);
}
// https://dev.onedrive.com/items/view_changes.htm
JSONValue viewChangesByPath(const(char)[] path, const(char)[] statusToken)
{
2015-09-01 22:23:42 +02:00
checkAccessTokenExpired();
2015-09-01 20:45:34 +02:00
char[] url = itemByPathUrl ~ encodeComponent(path).dup ~ ":/view.changes";
url ~= "?select=id,name,eTag,cTag,deleted,file,folder,fileSystemInfo,parentReference";
if (statusToken) url ~= "&token=" ~ statusToken;
return get(url);
}
// https://dev.onedrive.com/items/download.htm
void downloadById(const(char)[] id, string saveToPath)
{
/*string downloadUrl;
// obtain the download url
http.url = itemByIdUrl ~ id ~ "/content";
http.method = HTTP.Method.get;
http.maxRedirects = 0;
http.onReceive = (ubyte[] data) { return data.length; };
http.onReceiveHeader = (in char[] key, in char[] value) {
if (sicmp(key, "location") == 0) {
http.onReceiveHeader = null;
downloadUrl = value.dup;
}
};
writeln("Obtaining the url ...");
http.perform();
2015-09-06 10:07:18 +02:00
checkHttpCode();
2015-09-01 20:45:34 +02:00
http.maxRedirects = 10;
if (downloadUrl) {
// try to download the file
try {
download(downloadUrl, saveToPath);
} catch (CurlException e) {
import std.file;
if (exists(saveToPath)) remove(saveToPath);
throw new OneDriveException("Download error", e);
}
} else {
throw new OneDriveException("Can't obtain the download url");
}*/
2015-09-01 22:23:42 +02:00
checkAccessTokenExpired();
2015-09-01 20:45:34 +02:00
char[] url = itemByIdUrl ~ id ~ "/content";
try {
download(url, saveToPath, http);
} catch (CurlException e) {
import std.file;
if (exists(saveToPath)) remove(saveToPath);
throw new OneDriveException("Download error", e);
}
}
// https://dev.onedrive.com/items/upload_put.htm
2015-09-05 21:23:43 +02:00
JSONValue simpleUpload(string localPath, const(char)[] remotePath, const(char)[] eTag = null)
2015-09-01 20:45:34 +02:00
{
2015-09-01 22:23:42 +02:00
checkAccessTokenExpired();
2015-09-01 20:45:34 +02:00
char[] url = itemByPathUrl ~ remotePath ~ ":/content";
ubyte[] content;
http.onReceive = (ubyte[] data) {
content ~= data;
return data.length;
};
if (eTag) http.addRequestHeader("If-Match", eTag);
upload(localPath, url, http);
// remove the if-match header
if (eTag) setAccessToken(accessToken);
2015-09-06 10:07:18 +02:00
checkHttpCode();
2015-09-01 20:45:34 +02:00
return parseJSON(content);
}
2015-09-05 21:23:43 +02:00
// https://dev.onedrive.com/items/update.htm
JSONValue updateById(const(char)[] id, JSONValue data, const(char)[] eTag = null)
{
checkAccessTokenExpired();
char[] url = itemByIdUrl ~ id;
if (eTag) http.addRequestHeader("If-Match", eTag);
http.addRequestHeader("Content-Type", "application/json");
auto result = patch(url, data.toString());
http.clearRequestHeaders();
// remove the headers
setAccessToken(accessToken);
return result;
}
2015-09-01 20:45:34 +02:00
private void redeemToken(const(char)[] authCode)
{
string postData = "client_id=" ~ clientId ~ "&redirect_url=" ~ redirectUrl ~ "&client_secret=" ~ clientSecret;
postData ~= "&code=" ~ authCode ~ "&grant_type=authorization_code";
acquireToken(postData);
}
private void newToken()
{
string postData = "client_id=" ~ clientId ~ "&redirect_url=" ~ redirectUrl ~ "&client_secret=" ~ clientSecret;
postData ~= "&refresh_token=" ~ refreshToken ~ "&grant_type=refresh_token";
acquireToken(postData);
}
private void acquireToken(const(char)[] postData)
{
JSONValue response = post(tokenUrl, postData);
setAccessToken(response["access_token"].str());
2015-09-02 11:20:42 +02:00
refreshToken = response["refresh_token"].str();
2015-09-01 22:23:42 +02:00
accessTokenExpiration = Clock.currTime() + dur!"seconds"(response["expires_in"].integer());
2015-09-01 20:45:34 +02:00
if (onRefreshToken) onRefreshToken(refreshToken);
}
private void setAccessToken(string accessToken)
{
http.clearRequestHeaders();
this.accessToken = accessToken;
http.addRequestHeader("Authorization", "bearer " ~ accessToken);
}
2015-09-01 22:23:42 +02:00
private void checkAccessTokenExpired()
{
if (Clock.currTime() >= accessTokenExpiration) {
writeln("Access token expired, requesting a new token...");
newToken();
}
}
2015-09-05 21:23:43 +02:00
2015-09-01 20:45:34 +02:00
private auto get(const(char)[] url)
{
return parseJSON(.get(url, http));
}
2015-09-05 21:23:43 +02:00
private auto patch(T)(const(char)[] url, const(T)[] patchData)
{
return parseJSON(.patch(url, patchData, http));
}
2015-09-01 20:45:34 +02:00
private auto post(T)(const(char)[] url, const(T)[] postData)
{
return parseJSON(.post(url, postData, http));
}
2015-09-06 10:07:18 +02:00
private void checkHttpCode()
2015-09-01 20:45:34 +02:00
{
if (http.statusLine.code / 100 != 2) {
throw new OneDriveException(format("HTTP request returned status code %d (%s)", http.statusLine.code, http.statusLine.reason));
}
}
}