Resolve 'view.delta can only be called on the root'

* Resolve the error 'view.delta can only be called on the root' when
using OneDrive Business Account & --single-directory switch
* Change checkDatabaseForOneDriveRoot so that it is performed at init()
and not needed to be called a number of other times throughout the code
- leads to less HTTPS calls being made
* Cleanup comments that are no longer relevant due to
checkDatabaseForOneDriveRoot being run at init()
This commit is contained in:
abraunegg 2018-04-23 10:58:47 +10:00
parent d3f406459c
commit dff245d29b
2 changed files with 67 additions and 78 deletions

View file

@ -114,19 +114,6 @@ final class OneDriveApi
return get(url);
}
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delta
JSONValue viewChangesByPath(const(char)[] path, const(char)[] deltaLink)
{
checkAccessTokenExpired();
const(char)[] url = deltaLink;
if (url == null) {
if (path == ".") url = driveUrl ~ "/root/delta";
else url = itemByPathUrl ~ encodeComponent(path) ~ ":/delta";
url ~= "?select=id,name,eTag,cTag,deleted,file,folder,root,fileSystemInfo,remoteItem,parentReference";
}
return get(url);
}
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get_content
void downloadById(const(char)[] driveId, const(char)[] id, string saveToPath)
{
@ -200,6 +187,19 @@ final class OneDriveApi
return get(url);
}
// Return the details of the specified id
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get
JSONValue getPathDetailsById(const(char)[] id)
{
checkAccessTokenExpired();
const(char)[] url;
// string itemByIdUrl = "https://graph.microsoft.com/v1.0/me/drive/items/";
url = itemByIdUrl ~ id;
url ~= "?select=id,name,eTag,cTag,deleted,file,folder,root,fileSystemInfo,remoteItem,parentReference";
return get(url);
}
// https://dev.onedrive.com/items/move.htm
JSONValue moveByPath(const(char)[] sourcePath, JSONValue moveData)
{

View file

@ -1,4 +1,4 @@
import std.algorithm: find;
import std.algorithm;
import std.array: array;
import std.datetime;
import std.exception: enforce;
@ -36,6 +36,11 @@ private bool isItemRemote(const ref JSONValue item)
return ("remoteItem" in item) != null;
}
private bool changeHasParentReferenceId(const ref JSONValue item)
{
return ("id" in item["parentReference"]) != null;
}
// construct an Item struct from a JSON driveItem
private Item makeItem(const ref JSONValue driveItem)
{
@ -159,6 +164,9 @@ final class SyncEngine
log.vlog("Default Root ID: ", defaultRootId);
log.vlog("Remaining Free Space: ", remainingFreeSpace);
// Check the local database to ensure the OneDrive Root details are in the database
checkDatabaseForOneDriveRoot();
// check if there is an interrupted upload session
if (session.restore()) {
log.log("Continuing the upload session ...");
@ -195,13 +203,10 @@ final class SyncEngine
return;
}
}
// OK - it should exist, get the driveId and rootId for this folder
log.vlog("Checking for differences from OneDrive ...");
// OK - the path on OneDrive should exist, get the driveId and rootId for this folder
log.vlog("Getting path details from OneDrive ...");
JSONValue onedrivePathDetails = onedrive.getPathDetails(path); // Returns a JSON String for the OneDrive Path
// If the OneDrive Root is not in the local database, creating a remote folder will fail
checkDatabaseForOneDriveRoot();
// Use the global's as initialised via init() rather than performing unnecessary additional HTTPS calls
string driveId = defaultDriveId;
string folderId = onedrivePathDetails["id"].str; // Should give something like 12345ABCDE1234A1!101
@ -227,6 +232,7 @@ final class SyncEngine
if (!itemdb.selectById(driveId, rootId, rootPathItem)) {
log.vlog("OneDrive Root does not exist in the database. We need to add it.");
applyDifference(rootPathDetails, driveId, true);
log.vlog("Added OneDrive Root to the local database");
} else {
log.vlog("OneDrive Root exists in the database");
}
@ -238,9 +244,6 @@ final class SyncEngine
// Attempt to create the requested path within OneDrive without performing a sync
log.vlog("Attempting to create the requested path within OneDrive");
// If the OneDrive Root is not in the local database, creating a remote folder will fail
checkDatabaseForOneDriveRoot();
// Handle the remote folder creation and updating of the local database without performing a sync
uploadCreateDir(path);
}
@ -300,16 +303,25 @@ final class SyncEngine
// id is the root of the drive or a shared folder
private void applyDifferences(string driveId, const(char)[] id)
{
JSONValue changes;
string deltaLink = itemdb.getDeltaLink(driveId, id);
log.vlog("Applying changes of Path ID: " ~ id);
JSONValue changes;
string syncFolderName;
string deltaLink = itemdb.getDeltaLink(driveId, id);
JSONValue idDetails = onedrive.getPathDetailsById(id);
// Use the global's as initialised via init() rather than performing unnecessary additional HTTPS calls
string oneDriveRootId = defaultRootId;
// Set the name of this folder
if ((idDetails["id"].str == id) && (isItemFolder(idDetails))){
syncFolderName = idDetails["name"].str;
}
for (;;) {
try {
changes = onedrive.viewChangesById(driveId, id, deltaLink);
// Due to differences in OneDrive API's between personal and business we need to get changes only from defaultRootId
// If we used the 'id' passed in & when using --single-directory with a business account we get:
// 'HTTP request returned status code 501 (Not Implemented): view.delta can only be called on the root.'
// To view changes correctly, we need to use 'defaultRootId'
changes = onedrive.viewChangesById(driveId, defaultRootId, deltaLink);
} catch (OneDriveException e) {
if (e.httpStatusCode == 410) {
log.vlog("Delta link expired, resyncing...");
@ -319,22 +331,35 @@ final class SyncEngine
throw e;
}
}
foreach (item; changes["value"].array) {
// Test is this is the OneDrive Root - not say a single folder root sync
foreach (item; changes["value"].array) {
bool isRoot = false;
string thisItemPath;
// Deleted items returned from onedrive.viewChangesById (/delta) do not have a 'name' attribute
// Thus we cannot name check for 'root' below on deleted items
if(!isItemDeleted(item)){
// This is not a deleted item
if ((id == oneDriveRootId) && (item["name"].str == "root")) { // fix for https://github.com/skilion/onedrive/issues/269
// Test is this is the OneDrive Root?
// Use the global's as initialised via init() rather than performing unnecessary additional HTTPS calls
if ((id == defaultRootId) && (item["name"].str == "root")) { // fix for https://github.com/skilion/onedrive/issues/269
// This IS the OneDrive Root
isRoot = true;
}
}
// Apply the change
applyDifference(item, driveId, isRoot);
// How do we handle this change?
if (isRoot || !changeHasParentReferenceId(item) || isItemDeleted(item)){
// Is a root item, has no id in parentReference or is a OneDrive deleted item
applyDifference(item, driveId, isRoot);
} else {
// What is this item's path?
thisItemPath = item["parentReference"]["path"].str;
// Check this item's path to see if this is a change on the path we want
if ( (item["id"].str == id) || (item["parentReference"]["id"].str == id) || (canFind(thisItemPath, syncFolderName)) ){
// This is a change we want to apply
applyDifference(item, driveId, isRoot);
}
}
}
// the response may contain either @odata.deltaLink or @odata.nextLink
@ -358,13 +383,8 @@ final class SyncEngine
//log.vlog("Processing item to apply differences");
if (isItemRoot(driveItem) || !item.parentId || isRoot) {
log.vlog("Adding OneDrive Root to the local database");
item.parentId = null; // ensures that it has no parent
item.driveId = driveId; // HACK: makeItem() cannot set the driveId propery of the root
// What parent.driveId and parent.id are we using?
//log.vlog("Parent Drive ID: ", item.driveId);
//log.vlog("Parent ID: ", item.parentId);
itemdb.upsert(item);
return;
}
@ -458,7 +478,7 @@ final class SyncEngine
{
if (exists(path)) {
if (isItemSynced(item, path)) {
log.vlog("The item is already present");
//log.vlog("The item is already present");
return;
} else {
// TODO: force remote sync by deleting local item
@ -599,9 +619,6 @@ final class SyncEngine
// scan the given directory for differences and new items
void scanForDifferences(string path)
{
// Make sure the OneDrive Root is in the database
checkDatabaseForOneDriveRoot();
// scan for changes
log.vlog("Uploading differences of ", path);
Item item;
@ -804,49 +821,28 @@ final class SyncEngine
private void uploadCreateDir(const(string) path)
{
log.vlog("OneDrive Client requested to create remote path: ", path);
JSONValue onedrivePathDetails;
Item parent;
// Was the path entered the root path?
if (path == "."){
// We cant create this directory, as this would essentially equal the users OneDrive root:/
checkDatabaseForOneDriveRoot();
} else {
if (path != "."){
// If this is null or empty - we cant query the database properly
if ((parent.driveId == "") && (parent.id == "")){
// These are both empty .. not good
//log.vlog("WHOOPS: Well this is odd - parent.driveId & parent.id are empty - we have to query OneDrive for some values for the parent");
// What path to use?
string parentPath = dirName(path); // will be either . or something else
//log.vlog("WHOOPS FIX: Query OneDrive path details for parent: ", parentPath);
if (parentPath == "."){
// We cant create this directory, as this would essentially equal the users OneDrive root:/
checkDatabaseForOneDriveRoot();
}
try {
onedrive.getPathDetails(parentPath);
onedrivePathDetails = onedrive.getPathDetails(parentPath);
} catch (OneDriveException e) {
if (e.httpStatusCode == 404) {
// Parent does not exist ... need to create parent
uploadCreateDir(parentPath);
}
}
// Get the Parent Path Details
JSONValue onedrivePathDetails = onedrive.getPathDetails(parentPath); // Returns a JSON String for the OneDrive Path
// JSON Response
//log.vlog("WHOOPS JSON Response: ", onedrivePathDetails);
// configure the data
parent.driveId = onedrivePathDetails["parentReference"]["driveId"].str; // Should give something like 12345abcde1234a1
parent.id = onedrivePathDetails["id"].str; // This item's ID. Should give something like 12345ABCDE1234A1!101
// What parent.driveId and parent.id did we find?
//log.vlog("Using Parent DriveID: ", parent.driveId);
//log.vlog("Using Parent ID: ", parent.id);
}
// test if the path we are going to create already exists on OneDrive
@ -866,8 +862,6 @@ final class SyncEngine
// Submit the creation request
auto res = onedrive.createById(parent.driveId, parent.id, driveItem);
// What is returned?
//log.vlog("Create Folder Response JSON: ", res);
saveItem(res);
log.vlog("Sucessfully created the remote directory ", path, " on OneDrive");
return;
@ -972,7 +966,7 @@ final class SyncEngine
}
} else {
// Skip file - too large
log.log("Skipping uploading this new file as it exceeds the maximum allowed OneDrive size: ", path);
log.log("Skipping uploading this new file as it exceeds the maximum size allowed by OneDrive: ", path);
}
}
@ -983,20 +977,15 @@ final class SyncEngine
if ((item.driveId == "") && (item.id == "") && (item.eTag == "")){
// These are empty ... we cannot delete if this is empty ....
JSONValue onedrivePathDetails = onedrive.getPathDetails(path); // Returns a JSON String for the OneDrive Path
//log.vlog("WHOOPS JSON Response: ", onedrivePathDetails);
item.driveId = onedrivePathDetails["parentReference"]["driveId"].str; // Should give something like 12345abcde1234a1
item.id = onedrivePathDetails["id"].str; // This item's ID. Should give something like 12345ABCDE1234A1!101
item.eTag = onedrivePathDetails["eTag"].str; // Should be something like aNjM2NjJFRUVGQjY2NjJFMSE5MzUuMA
//log.vlog("item.driveId = ", item.driveId);
//log.vlog("item.id = ", item.id);
//log.vlog("item.eTag = ", item.eTag);
}
try {
onedrive.deleteById(item.driveId, item.id, item.eTag);
} catch (OneDriveException e) {
if (e.httpStatusCode == 404) log.log(e.msg);
if (e.httpStatusCode == 404) log.vlog("OneDrive reported: The resource could not be found.");
else throw e;
}
itemdb.deleteById(item.driveId, item.id);