diff --git a/onedrive.conf b/onedrive.conf index ae7f2282..8b73f21b 100644 --- a/onedrive.conf +++ b/onedrive.conf @@ -1,4 +1,4 @@ -client_id = "000000004C15842F" +client_id = "22c49a0d-d21c-4792-aed1-8f163c982546" sync_dir = "~/OneDrive" skip_file = ".*|~*" skip_dir = ".*" diff --git a/src/onedrive.d b/src/onedrive.d index d75c1c2f..9daf2dff 100644 --- a/src/onedrive.d +++ b/src/onedrive.d @@ -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(); +} diff --git a/src/sync.d b/src/sync.d index b5f73b07..816d68a1 100644 --- a/src/sync.d +++ b/src/sync.d @@ -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,