switch to Microsoft Graph API

This commit is contained in:
skilion 2017-03-11 11:40:19 +01:00
parent 203062fc6b
commit 1beadf2577
3 changed files with 77 additions and 27 deletions

View file

@ -1,4 +1,4 @@
client_id = "000000004C15842F"
client_id = "22c49a0d-d21c-4792-aed1-8f163c982546"
sync_dir = "~/OneDrive"
skip_file = ".*|~*"
skip_dir = ".*"

View file

@ -6,12 +6,12 @@ static import log;
private immutable {
string authUrl = "https://login.live.com/oauth20_authorize.srf";
string redirectUrl = "https://login.live.com/oauth20_desktop.srf"; // "urn:ietf:wg:oauth:2.0:oob";
string tokenUrl = "https://login.live.com/oauth20_token.srf";
string driveUrl = "https://api.onedrive.com/v1.0/drive";
string itemByIdUrl = "https://api.onedrive.com/v1.0/drive/items/";
string itemByPathUrl = "https://api.onedrive.com/v1.0/drive/root:/";
string authUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
string redirectUrl = "https://login.microsoftonline.com/common/oauth2/nativeclient";
string tokenUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
string driveUrl = "https://graph.microsoft.com/v1.0/me/drive";
string itemByIdUrl = "https://graph.microsoft.com/v1.0/me/drive/items/";
string itemByPathUrl = "https://graph.microsoft.com/v1.0/me/drive/root:/";
}
class OneDriveException: Exception
@ -79,7 +79,7 @@ final class OneDriveApi
{
import std.stdio, std.regex;
char[] response;
string url = authUrl ~ "?client_id=" ~ clientId ~ "&scope=onedrive.readwrite%20offline_access&response_type=code&redirect_uri=" ~ redirectUrl;
string url = authUrl ~ "?client_id=" ~ clientId ~ "&scope=files.readwrite.all%20offline_access&response_type=code&redirect_uri=" ~ redirectUrl;
log.log("Authorize this app visiting:\n");
write(url, "\n\n", "Enter the response uri: ");
readln(response);
@ -105,9 +105,9 @@ final class OneDriveApi
JSONValue viewChangesById(const(char)[] id, const(char)[] statusToken)
{
checkAccessTokenExpired();
const(char)[] url = itemByIdUrl ~ id ~ "/view.delta";
const(char)[] url = itemByIdUrl ~ id ~ "/delta";
url ~= "?select=id,name,eTag,cTag,deleted,file,folder,fileSystemInfo,remoteItem,parentReference";
if (statusToken) url ~= "?token=" ~ statusToken;
if (statusToken) url ~= "&token=" ~ statusToken;
return get(url);
}
@ -115,7 +115,9 @@ final class OneDriveApi
JSONValue viewChangesByPath(const(char)[] path, const(char)[] statusToken)
{
checkAccessTokenExpired();
string url = itemByPathUrl ~ encodeComponent(path) ~ ":/view.delta";
//string url = itemByPathUrl ~ encodeComponent(path) ~ ":/delta";
// HACK: item by path seems to no be working
string url = "https://graph.microsoft.com/v1.0/me/drive/root/delta";
url ~= "?select=id,name,eTag,cTag,deleted,file,folder,fileSystemInfo,remoteItem,parentReference";
if (statusToken) url ~= "&token=" ~ statusToken;
return get(url);
@ -138,7 +140,6 @@ final class OneDriveApi
{
checkAccessTokenExpired();
string url = itemByPathUrl ~ encodeComponent(remotePath) ~ ":/content";
http.addRequestHeader("Content-Type", "application/octet-stream");
if (eTag) http.addRequestHeader("If-Match", eTag);
else url ~= "?@name.conflictBehavior=fail";
return upload(localPath, url);
@ -177,7 +178,7 @@ final class OneDriveApi
JSONValue createUploadSession(const(char)[] path, const(char)[] eTag = null)
{
checkAccessTokenExpired();
string url = itemByPathUrl ~ encodeComponent(path) ~ ":/upload.createSession";
string url = itemByPathUrl ~ encodeComponent(path) ~ ":/createUploadSession";
if (eTag) http.addRequestHeader("If-Match", eTag);
return post(url, null);
}
@ -192,7 +193,8 @@ final class OneDriveApi
}
http.method = HTTP.Method.put;
http.url = uploadUrl;
addAccessTokenHeader();
// when using microsoft graph the auth code is different
//addAccessTokenHeader();
import std.conv;
string contentRange = "bytes " ~ to!string(offset) ~ "-" ~ to!string(offset + offsetSize - 1) ~ "/" ~ to!string(fileSize);
http.addRequestHeader("Content-Range", contentRange);
@ -210,7 +212,8 @@ final class OneDriveApi
JSONValue requestUploadStatus(const(char)[] uploadUrl)
{
checkAccessTokenExpired();
return get(uploadUrl);
// when using microsoft graph the auth code is different
return get(uploadUrl, true);
}
private void redeemToken(const(char)[] authCode)
@ -254,12 +257,12 @@ final class OneDriveApi
http.addRequestHeader("Authorization", accessToken);
}
private JSONValue get(const(char)[] url)
private JSONValue get(const(char)[] url, bool skipToken = false)
{
scope(exit) http.clearRequestHeaders();
http.method = HTTP.Method.get;
http.url = url;
addAccessTokenHeader();
if (!skipToken) addAccessTokenHeader(); // HACK: requestUploadStatus
auto response = perform();
checkHttpCode(response);
return response;
@ -366,7 +369,15 @@ final class OneDriveApi
} catch (CurlException e) {
throw new OneDriveException(e.msg, e);
}
return content.parseJSON();
JSONValue json;
try {
json = content.parseJSON();
} catch (JSONException e) {
e.msg ~= "\n";
e.msg ~= content;
throw e;
}
return json;
}
private void checkHttpCode()
@ -383,3 +394,37 @@ final class OneDriveApi
}
}
}
unittest
{
string configDirName = expandTilde("~/.config/onedrive");
auto cfg = new config.Config(configDirName);
cfg.init();
OneDriveApi onedrive = new OneDriveApi(cfg);
onedrive.init();
std.file.write("/tmp/test", "test");
// simpleUpload
auto item = onedrive.simpleUpload("/tmp/test", "/test");
try {
item = onedrive.simpleUpload("/tmp/test", "/test");
} catch (OneDriveException e) {
assert(e.httpStatusCode == 409);
}
try {
item = onedrive.simpleUpload("/tmp/test", "/test", "123");
} catch (OneDriveException e) {
assert(e.httpStatusCode == 412);
}
item = onedrive.simpleUpload("/tmp/test", "/test", item["eTag"].str);
// deleteById
try {
onedrive.deleteById(item["id"].str, "123");
} catch (OneDriveException e) {
assert(e.httpStatusCode == 412);
}
onedrive.deleteById(item["id"].str, item["eTag"].str);
onedrive.http.shutdown();
}

View file

@ -5,21 +5,22 @@ import config, itemdb, onedrive, upload, util;
static import log;
// threshold after which files will be uploaded using an upload session
private long thresholdFileSize = 10 * 2^^20; // 10 MiB
private long thresholdFileSize = 4 * 2^^20; // 4 MiB
private bool isItemFolder(const ref JSONValue item)
{
return (("folder" in item.object) !is null);
return ("folder" in item) != null;
}
private bool isItemFile(const ref JSONValue item)
{
return (("file" in item.object) !is null);
return ("file" in item) != null;
}
private bool isItemDeleted(const ref JSONValue item)
{
return (("deleted" in item.object) !is null);
// HACK: fix for https://github.com/skilion/onedrive/issues/157
return ("deleted" in item) || ("fileSystemInfo" !in item);
}
private bool testCrc32(string path, const(char)[] crc32)
@ -107,9 +108,15 @@ final class SyncEngine
foreach (item; changes["value"].array) {
applyDifference(item);
}
statusToken = changes["@delta.token"].str;
// hack to reuse old code
string url;
if ("@odata.nextLink" in changes) url = changes["@odata.nextLink"].str;
if ("@odata.deltaLink" in changes) url = changes["@odata.deltaLink"].str;
auto c = matchFirst(url, r"(?:token=)([\w\d]+)");
c.popFront(); // skip the whole match
statusToken = c.front;
std.file.write(cfg.statusTokenFilePath, statusToken);
} while (!((changes.type == JSON_TYPE.OBJECT) && (("@odata.nextLink" in changes) is null)));
} while ("@odata.nextLink" in changes);
} catch (ErrnoException e) {
throw new SyncException(e.msg, e);
} catch (FileException e) {
@ -477,7 +484,6 @@ final class SyncEngine
} else {
response = session.upload(path, path);
}
saveItem(response);
string id = response["id"].str;
string cTag = response["cTag"].str;
SysTime mtime = timeLastModified(path).toUTC();
@ -512,7 +518,6 @@ final class SyncEngine
private void saveItem(JSONValue jsonItem)
{
string id = jsonItem["id"].str;
ItemType type;
if (isItemFile(jsonItem)) {
type = ItemType.file;
@ -522,7 +527,7 @@ final class SyncEngine
assert(0);
}
Item item = {
id: id,
id: jsonItem["id"].str,
name: jsonItem["name"].str,
type: type,
eTag: jsonItem["eTag"].str,