mirror of
https://github.com/abraunegg/onedrive
synced 2024-05-15 20:26:53 +02:00
switch to Microsoft Graph API
This commit is contained in:
parent
203062fc6b
commit
1beadf2577
|
@ -1,4 +1,4 @@
|
|||
client_id = "000000004C15842F"
|
||||
client_id = "22c49a0d-d21c-4792-aed1-8f163c982546"
|
||||
sync_dir = "~/OneDrive"
|
||||
skip_file = ".*|~*"
|
||||
skip_dir = ".*"
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
23
src/sync.d
23
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,
|
||||
|
|
Loading…
Reference in a new issue