sync root folder

This commit is contained in:
skilion 2015-09-16 10:29:20 +02:00
parent c3c3a714e7
commit f887b29061
6 changed files with 143 additions and 373 deletions

View file

@ -1,244 +0,0 @@
module cache;
import std.datetime, std.path;
import sqlite;
enum ItemType
{
file,
dir
}
struct Item
{
string id;
string path;
string name;
ItemType type;
string eTag;
string cTag;
SysTime mtime;
string parentId;
string crc32;
}
struct ItemCache
{
Database db;
Statement insertItemStmt;
Statement selectItemByIdStmt;
Statement selectItemByParentIdStmt;
void init()
{
db = Database("cache.db");
db.exec("CREATE TABLE IF NOT EXISTS item (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
type TEXT NOT NULL,
eTag TEXT NOT NULL,
cTag TEXT NOT NULL,
mtime TEXT NOT NULL,
parentId TEXT NOT NULL,
crc32 TEXT
)");
db.exec("CREATE INDEX IF NOT EXISTS name_idx ON item (name)");
insertItemStmt = db.prepare("INSERT OR REPLACE INTO item (id, name, type, eTag, cTag, mtime, parentId, crc32) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
selectItemByIdStmt = db.prepare("SELECT id, name, type, eTag, cTag, mtime, parentId, crc32 FROM item WHERE id = ?");
selectItemByParentIdStmt = db.prepare("SELECT id FROM item WHERE parentId = ?");
}
void insert(const(char)[] id, const(char)[] name, ItemType type, const(char)[] eTag, const(char)[] cTag, const(char)[] mtime, const(char)[] parentId, const(char)[] crc32)
{
with (insertItemStmt) {
bind(1, id);
bind(2, name);
string typeStr = void;
final switch (type) {
case ItemType.file: typeStr = "file"; break;
case ItemType.dir: typeStr = "dir"; break;
}
bind(3, typeStr);
bind(4, eTag);
bind(5, cTag);
bind(6, mtime);
bind(7, parentId);
bind(8, crc32);
exec();
}
}
// returns a range that go trough all items, depth first
auto selectAll()
{
static struct ItemRange
{
ItemCache* itemCache;
string[] stack1, stack2;
private this(ItemCache* itemCache, string rootId)
{
this.itemCache = itemCache;
stack1.reserve(8);
stack2.reserve(8);
stack1 ~= rootId;
getChildren();
}
@property bool empty()
{
return stack2.length == 0;
}
@property Item front()
{
Item item;
bool res = itemCache.selectById(stack2[$ - 1], item);
assert(res);
return item;
}
void popFront()
{
stack2 = stack2[0 .. $ - 1];
assumeSafeAppend(stack2);
if (stack1.length > 0) getChildren();
}
private void getChildren()
{
while (true) {
itemCache.selectItemByParentIdStmt.bind(1, stack1[$ - 1]);
stack2 ~= stack1[$ - 1];
stack1 = stack1[0 .. $ - 1];
assumeSafeAppend(stack1);
auto res = itemCache.selectItemByParentIdStmt.exec();
if (res.empty) break;
else foreach (row; res) stack1 ~= row[0].dup;
}
}
}
auto s = db.prepare("SELECT a.id FROM item AS a LEFT JOIN item AS b ON a.parentId = b.id WHERE b.id IS NULL");
auto r = s.exec();
assert(!r.empty());
return ItemRange(&this, r.front[0].dup);
}
bool selectById(const(char)[] id, out Item item)
{
selectItemByIdStmt.bind(1, id);
auto r = selectItemByIdStmt.exec();
if (!r.empty) {
item = buildItem(r);
return true;
}
return false;
}
bool selectByPath(const(char)[] path, out Item item)
{
string[2][] candidates; // [id, parentId]
auto s = db.prepare("SELECT id, parentId FROM item WHERE name = ?");
s.bind(1, baseName(path));
auto r = s.exec();
foreach (row; r) candidates ~= [row[0].dup, row[1].dup];
if (candidates.length > 1) {
s = db.prepare("SELECT parentId FROM item WHERE id = ? AND name = ?");
do {
string[2][] newCandidates;
newCandidates.reserve(candidates.length);
path = dirName(path);
foreach (candidate; candidates) {
s.bind(1, candidate[1]);
s.bind(2, baseName(path));
r = s.exec();
if (!r.empty) {
string[2] c = [candidate[0], r.front[0].idup];
newCandidates ~= c;
}
}
candidates = newCandidates;
} while (candidates.length > 1);
}
if (candidates.length == 1) return selectById(candidates[0][0], item);
return false;
}
void deleteById(const(char)[] id)
{
auto s = db.prepare("DELETE FROM item WHERE id = ?");
s.bind(1, id);
s.exec();
}
// returns true if the item has the specified parent
bool hasParent(T)(const(char)[] itemId, T parentId)
if (is(T : const(char)[]) || is(T : const(char[])[]))
{
auto s = db.prepare("SELECT parentId FROM item WHERE id = ?");
while (true) {
s.bind(1, itemId);
auto r = s.exec();
if (r.empty) break;
auto currParentId = r.front[0];
static if (is(T : const(char)[])) {
if (currParentId == parentId) return true;
} else {
foreach (id; parentId) if (currParentId == id) return true;
}
itemId = currParentId.dup;
}
return false;
}
private Item buildItem(Statement.Result result)
{
assert(!result.empty && result.front.length == 8);
Item item = {
id: result.front[0].dup,
path: computePath(result.front[0]),
name: result.front[1].dup,
eTag: result.front[3].dup,
cTag: result.front[4].dup,
mtime: SysTime.fromISOExtString(result.front[5]),
parentId: result.front[6].dup,
crc32: result.front[7].dup
};
switch (result.front[2]) {
case "file": item.type = ItemType.file; break;
case "dir": item.type = ItemType.dir; break;
default: assert(0);
}
return item;
}
private string computePath(const(char)[] id)
{
auto s = db.prepare("SELECT name, parentId FROM item WHERE id = ?");
string path;
while (true) {
s.bind(1, id);
auto r = s.exec();
if (r.empty) break;
if (path) path = r.front[0].idup ~ "/" ~ path;
else path = r.front[0].dup;
id = r.front[1].dup;
}
return path;
}
private string computePath(const(char)[] name, const(char)[] parentId)
{
auto s = db.prepare("SELECT name, parentId FROM item WHERE id = ?");
string path = name.dup;
while (true) {
s.bind(1, parentId);
auto r = s.exec();
if (r.empty) break;
path = r.front[0].idup ~ "/" ~ path;
parentId = r.front[1].dup;
}
return path;
}
}

View file

@ -224,7 +224,7 @@ final class ItemDatabase
id = r.front[1].dup; id = r.front[1].dup;
} }
// HACK: skip "root/" // HACK: skip "root/"
if (path.length < 5) return null; if (path.length < 5) return ".";
return path[5 .. $]; return path[5 .. $];
} }

View file

@ -1,11 +1,11 @@
import std.getopt, std.file, std.process, std.stdio; import std.getopt, std.file, std.process, std.stdio;
import config, monitor, onedrive, sync; import config, itemdb, monitor, onedrive, sync;
string ver = "1.0"; string ver = "1.0";
void main(string[] args) void main(string[] args)
{ {
bool monitor, resync, resetLocal, resetRemote, verbose; bool monitor, resync, verbose;
try { try {
writeln("OneDrive Client for Linux v", ver); writeln("OneDrive Client for Linux v", ver);
auto opt = getopt( auto opt = getopt(
@ -29,10 +29,10 @@ void main(string[] args)
string configFilePath = configDirName ~ "/config"; string configFilePath = configDirName ~ "/config";
string refreshTokenFilePath = configDirName ~ "/refresh_token"; string refreshTokenFilePath = configDirName ~ "/refresh_token";
string statusTokenFilePath = configDirName ~ "/status_token"; string statusTokenFilePath = configDirName ~ "/status_token";
string databaseFilePath = configDirName ~ "/database"; string databaseFilePath = configDirName ~ "/items.db";
if (resync || resetLocal || resetRemote) { if (resync) {
if (verbose) writeln("Deleting the current status ..."); if (verbose) writeln("Deleting the saved status ...");
if (exists(databaseFilePath)) remove(databaseFilePath); if (exists(databaseFilePath)) remove(databaseFilePath);
if (exists(statusTokenFilePath)) remove(statusTokenFilePath); if (exists(statusTokenFilePath)) remove(statusTokenFilePath);
} }
@ -54,17 +54,33 @@ void main(string[] args)
} }
// TODO check if the token is valid // TODO check if the token is valid
if (verbose) writeln("Opening the item database ...");
auto itemdb = new ItemDatabase(databaseFilePath);
if (verbose) writeln("Initializing the Synchronization Engine ..."); if (verbose) writeln("Initializing the Synchronization Engine ...");
auto sync = new SyncEngine(cfg, onedrive); auto sync = new SyncEngine(cfg, onedrive, itemdb, verbose);
sync.onStatusToken = (string statusToken) {
std.file.write(statusTokenFilePath, statusToken);
};
try {
string statusToken = readText(statusTokenFilePath);
sync.setStatusToken(statusToken);
} catch (FileException e) {
// swallow exception
}
string syncDir = cfg.get("sync_dir");
chdir(syncDir);
sync.applyDifferences(); sync.applyDifferences();
sync.uploadDifferences(); sync.uploadDifferences();
return;
if (monitor) { if (monitor) {
if (verbose) writeln("Monitoring for changes ..."); if (verbose) writeln("Monitoring for changes ...");
Monitor m; Monitor m;
m.onDirCreated = delegate(string path) { m.onDirCreated = delegate(string path) {
if (verbose) writeln("[M] Directory created: ", path); if (verbose) writeln("[M] Directory created: ", path);
sync.createFolderItem(path); sync.uploadCreateDir(path);
sync.uploadDifferences(path); sync.uploadDifferences(path);
}; };
m.onFileChanged = delegate(string path) { m.onFileChanged = delegate(string path) {
@ -80,10 +96,10 @@ void main(string[] args)
sync.moveItem(from, to); sync.moveItem(from, to);
}; };
m.init(); m.init();
string syncDir = cfg.get("sync_dir");
chdir(syncDir);
m.addRecursive("test"); m.addRecursive("test");
while (true) m.update(); while (true) m.update();
// TODO download changes // TODO download changes
} }
destroy(sync);
} }

View file

@ -105,7 +105,7 @@ final class OneDriveApi
JSONValue viewChangesByPath(const(char)[] path, const(char)[] statusToken) JSONValue viewChangesByPath(const(char)[] path, const(char)[] statusToken)
{ {
checkAccessTokenExpired(); checkAccessTokenExpired();
char[] url = itemByPathUrl ~ encodeComponent(path).dup ~ ":/view.changes"; string url = itemByPathUrl ~ encodeComponent(path) ~ ":/view.changes";
url ~= "?select=id,name,eTag,cTag,deleted,file,folder,fileSystemInfo,parentReference"; url ~= "?select=id,name,eTag,cTag,deleted,file,folder,fileSystemInfo,parentReference";
if (statusToken) url ~= "&token=" ~ statusToken; if (statusToken) url ~= "&token=" ~ statusToken;
return get(url); return get(url);
@ -129,7 +129,7 @@ final class OneDriveApi
JSONValue simpleUpload(string localPath, const(char)[] remotePath, const(char)[] eTag = null) JSONValue simpleUpload(string localPath, const(char)[] remotePath, const(char)[] eTag = null)
{ {
checkAccessTokenExpired(); checkAccessTokenExpired();
char[] url = itemByPathUrl ~ remotePath ~ ":/content"; string url = itemByPathUrl ~ encodeComponent(remotePath) ~ ":/content";
if (!eTag) url ~= "?@name.conflictBehavior=fail"; if (!eTag) url ~= "?@name.conflictBehavior=fail";
ubyte[] content; ubyte[] content;
http.onReceive = (ubyte[] data) { http.onReceive = (ubyte[] data) {
@ -137,9 +137,10 @@ final class OneDriveApi
return data.length; return data.length;
}; };
if (eTag) http.addRequestHeader("If-Match", eTag); if (eTag) http.addRequestHeader("If-Match", eTag);
http.addRequestHeader("Content-Type", "application/octet-stream");
upload(localPath, url, http); upload(localPath, url, http);
// remove the if-match header // remove the headers
if (eTag) setAccessToken(accessToken); setAccessToken(accessToken);
checkHttpCode(); checkHttpCode();
return parseJSON(content); return parseJSON(content);
} }
@ -171,7 +172,7 @@ final class OneDriveApi
//https://dev.onedrive.com/items/create.htm //https://dev.onedrive.com/items/create.htm
JSONValue createByPath(const(char)[] parentPath, JSONValue item) JSONValue createByPath(const(char)[] parentPath, JSONValue item)
{ {
char[] url = itemByPathUrl ~ parentPath ~ ":/children"; string url = itemByPathUrl ~ encodeComponent(parentPath) ~ ":/children";
http.addRequestHeader("Content-Type", "application/json"); http.addRequestHeader("Content-Type", "application/json");
auto result = post(url, item.toString()); auto result = post(url, item.toString());
// remove the if-match header // remove the if-match header

View file

@ -1,5 +1,5 @@
import core.exception: RangeError; import core.exception: RangeError;
import std.datetime, std.file, std.json, std.path, std.stdio; import std.algorithm, std.datetime, std.file, std.json, std.path, std.stdio;
import config, itemdb, onedrive, util; import config, itemdb, onedrive, util;
private bool isItemFolder(const ref JSONValue item) private bool isItemFolder(const ref JSONValue item)
@ -51,6 +51,7 @@ final class SyncEngine
private ItemDatabase itemdb; private ItemDatabase itemdb;
private bool verbose; private bool verbose;
private string statusToken; private string statusToken;
private string[] skippedItems;
private string[] itemsToDelete; private string[] itemsToDelete;
void delegate(string) onStatusToken; void delegate(string) onStatusToken;
@ -83,6 +84,9 @@ final class SyncEngine
} while (changes["@changes.hasMoreChanges"].type == JSON_TYPE.TRUE); } while (changes["@changes.hasMoreChanges"].type == JSON_TYPE.TRUE);
// delete items in itemsToDelete // delete items in itemsToDelete
deleteItems(); deleteItems();
// empty the skipped items
skippedItems.length = 0;
assumeSafeAppend(skippedItems);
} }
private void applyDifference(JSONValue item) private void applyDifference(JSONValue item)
@ -102,9 +106,6 @@ final class SyncEngine
cached = false; cached = false;
} }
// skip items already downloaded
//if (cached && cachedItem.eTag == eTag) return;
ItemType type; ItemType type;
if (isItemDeleted(item)) { if (isItemDeleted(item)) {
if (verbose) writeln("The item is marked for deletion"); if (verbose) writeln("The item is marked for deletion");
@ -117,14 +118,20 @@ final class SyncEngine
if (verbose) writeln("The item is a directory"); if (verbose) writeln("The item is a directory");
type = ItemType.dir; type = ItemType.dir;
} else { } else {
writeln("The item is neither a file nor a directory"); if (verbose) writeln("The item is neither a file nor a directory");
//skippedFolders ~= id; skippedItems ~= id;
return;
}
string parentId = item["parentReference"].object["id"].str;
if (skippedItems.find(parentId).length != 0) {
if (verbose) writeln("The item is a children of a skipped item");
skippedItems ~= id;
return; return;
} }
string cTag = item["cTag"].str; string cTag = item["cTag"].str;
string mtime = item["fileSystemInfo"].object["lastModifiedDateTime"].str; string mtime = item["fileSystemInfo"].object["lastModifiedDateTime"].str;
string parentId = item["parentReference"].object["id"].str;
string crc32; string crc32;
if (type == ItemType.file) { if (type == ItemType.file) {
@ -154,38 +161,6 @@ final class SyncEngine
} }
} }
private void cacheItem(JSONValue item)
{
string id = item["id"].str;
ItemType type;
if (isItemDeleted(item)) {
itemdb.deleteById(id);
} else if (isItemFile(item)) {
type = ItemType.file;
} else if (isItemFolder(item)) {
type = ItemType.dir;
} else {
writeln("The item is neither a file nor a directory, skipping");
return;
}
string name = item["name"].str;
string eTag = item["eTag"].str;
string cTag = item["cTag"].str;
string mtime = item["fileSystemInfo"].object["lastModifiedDateTime"].str;
string parentId = item["parentReference"].object["id"].str;
string crc32;
if (type == ItemType.file) {
try {
crc32 = item["file"].object["hashes"].object["crc32Hash"].str;
} catch (JSONException e) {
writeln("The hash is not available");
} catch (RangeError e) {
writeln("The crc32 hash is not available");
}
}
itemdb.insert(id, name, type, eTag, cTag, mtime, parentId, crc32);
}
private void applyDeleteItem(Item item) private void applyDeleteItem(Item item)
{ {
itemsToDelete ~= item.path; itemsToDelete ~= item.path;
@ -311,17 +286,22 @@ final class SyncEngine
// scan the directory for unsynced files and upload them // scan the directory for unsynced files and upload them
public void uploadDifferences() public void uploadDifferences()
{ {
writeln("Uploading differences ..."); if (verbose) writeln("Uploading differences ...");
string currDir = getcwd();
string syncDir = cfg.get("sync_dir");
chdir(syncDir);
foreach (Item item; itemdb.selectAll()) { foreach (Item item; itemdb.selectAll()) {
uploadDifference(item); uploadDifference(item);
} }
foreach (DirEntry entry; dirEntries("test", SpanMode.breadth, false)) { // check for new files or directories
uploadDifference(entry.name/*[2 .. $]*/); foreach (DirEntry entry; dirEntries(".", SpanMode.breadth, false)) {
string path = entry.name[2 .. $]; // HACK: skip "./"
Item item;
if (!itemdb.selectByPath(path, item)) {
if (entry.isDir) {
uploadCreateDir(path);
} else {
uploadNewFile(path);
}
}
} }
chdir(currDir);
} }
public void uploadDifferences(string path) public void uploadDifferences(string path)
@ -332,123 +312,140 @@ final class SyncEngine
if (itemdb.selectByPath(entry.name, item)) { if (itemdb.selectByPath(entry.name, item)) {
uploadDifference(item); uploadDifference(item);
} else { } else {
uploadNewItem(entry.name); uploadNewFile(entry.name);
} }
} }
} }
private void uploadDifference(Item item) private void uploadDifference(Item item)
{ {
writeln(item.path); if (verbose) writeln(item.id, " ", item.name);
if (exists(item.path)) { if (exists(item.path)) {
final switch (item.type) { final switch (item.type) {
case ItemType.file: case ItemType.file:
if (isFile(item.path)) { if (isFile(item.path)) {
updateItem(item); uploadItemDifferences(item);
} else { } else {
deleteItem(item); if (verbose) writeln("The item was a file but now is a directory");
createFolderItem(item.path); uploadDeleteItem(item);
uploadCreateDir(item.path);
} }
break; break;
case ItemType.dir: case ItemType.dir:
if (isDir(item.path)) { if (!isDir(item.path)) {
updateItem(item); if (verbose) writeln("The item was a directory but now is a file");
uploadDeleteItem(item);
uploadNewFile(item.path);
} else { } else {
deleteItem(item); if (verbose) writeln("The item has not changed");
writeln("Uploading ...");
auto res = onedrive.simpleUpload(item.path, item.path);
cacheItem(res);
} }
break; break;
} }
} else { } else {
deleteItem(item); if (verbose) writeln("The item has been deleted");
uploadDeleteItem(item);
} }
} }
private void uploadDifference(const(char)[] path) // check if the item is changed and upload the differences
{ private void uploadItemDifferences(Item item)
Item item;
if (!itemdb.selectByPath(path, item)) {
writeln("New item ", path);
uploadNewItem(path);
}
}
// HACK
void uploadDifference2(const(char)[] path)
{
assert(isFile(path));
Item item;
if (itemdb.selectByPath(path, item)) {
uploadDifference(item);
} else {
uploadNewItem(path);
}
}
private void deleteItem(Item item)
{
writeln("Deleting ...");
onedrive.deleteById(item.id, item.eTag);
itemdb.deleteById(item.id);
}
private void updateItem(Item item)
{ {
SysTime localModifiedTime = timeLastModified(item.path); SysTime localModifiedTime = timeLastModified(item.path);
import core.time: Duration; import core.time: Duration;
item.mtime.fracSecs = Duration.zero; // HACK item.mtime.fracSecs = Duration.zero; // HACK
if (localModifiedTime != item.mtime) { if (localModifiedTime != item.mtime) {
if (verbose) writeln("The item last modified time has changed");
string id = item.id; string id = item.id;
string eTag = item.eTag; string eTag = item.eTag;
if (item.type == ItemType.file && !testCrc32(item.path, item.crc32)) { if (item.type == ItemType.file && !testCrc32(item.path, item.crc32)) {
assert(isFile(item.path)); if (verbose) writeln("The item content has changed");
writeln("Uploading ..."); writeln("Uploading: ", item.path);
JSONValue res = onedrive.simpleUpload(item.path, item.path, item.eTag); auto res = onedrive.simpleUpload(item.path, item.path, item.eTag);
cacheItem(res); saveItem(res);
id = res["id"].str; id = res["id"].str;
eTag = res["eTag"].str; eTag = res["eTag"].str;
} }
updateItemLastModifiedTime(id, eTag, localModifiedTime.toUTC()); uploadLastModifiedTime(id, eTag, localModifiedTime.toUTC());
} else { } else {
writeln("The item is not changed"); if (verbose) writeln("The item has not changed");
} }
} }
void createFolderItem(const(char)[] path) void uploadCreateDir(const(char)[] path)
{ {
writeln("Creating folder ..."); writeln("Creating remote directory: ", path);
JSONValue item = ["name": baseName(path).dup]; JSONValue item = ["name": baseName(path).idup];
item["folder"] = parseJSON("{}"); item["folder"] = parseJSON("{}");
auto res = onedrive.createByPath(dirName(path), item); auto res = onedrive.createByPath(dirName(path), item);
cacheItem(res); saveItem(res);
} }
private void updateItemLastModifiedTime(const(char)[] id, const(char)[] eTag, SysTime mtime) private void uploadNewFile(string path)
{
writeln("Uploading: ", path);
auto res = onedrive.simpleUpload(path, path);
saveItem(res);
string id = res["id"].str;
string eTag = res["eTag"].str;
uploadLastModifiedTime(id, eTag, timeLastModified(path).toUTC());
}
private void uploadDeleteItem(Item item)
{
writeln("Deleting remote: ", item.path);
onedrive.deleteById(item.id, item.eTag);
itemdb.deleteById(item.id);
}
private void uploadLastModifiedTime(const(char)[] id, const(char)[] eTag, SysTime mtime)
{ {
writeln("Updating last modified time ...");
JSONValue mtimeJson = [ JSONValue mtimeJson = [
"fileSystemInfo": JSONValue([ "fileSystemInfo": JSONValue([
"lastModifiedDateTime": mtime.toISOExtString() "lastModifiedDateTime": mtime.toISOExtString()
]) ])
]; ];
auto res = onedrive.updateById(id, mtimeJson, eTag); auto res = onedrive.updateById(id, mtimeJson, eTag);
cacheItem(res); saveItem(res);
} }
private void uploadNewItem(const(char)[] path) private void saveItem(JSONValue item)
{ {
assert(exists(path)); string id = item["id"].str;
if (isFile(path)) { ItemType type;
writeln("Uploading file ..."); if (isItemFile(item)) {
JSONValue res = onedrive.simpleUpload(path.dup, path); type = ItemType.file;
cacheItem(res); } else if (isItemFolder(item)) {
string id = res["id"].str; type = ItemType.dir;
string eTag = res["eTag"].str;
updateItemLastModifiedTime(id, eTag, timeLastModified(path).toUTC());
} else { } else {
createFolderItem(path); assert(0);
}
string name = item["name"].str;
string eTag = item["eTag"].str;
string cTag = item["cTag"].str;
string mtime = item["fileSystemInfo"].object["lastModifiedDateTime"].str;
string parentId = item["parentReference"].object["id"].str;
string crc32;
if (type == ItemType.file) {
try {
crc32 = item["file"].object["hashes"].object["crc32Hash"].str;
} catch (JSONException e) {
// swallow exception
} catch (RangeError e) {
// swallow exception
}
}
itemdb.insert(id, name, type, eTag, cTag, mtime, parentId, crc32);
}
// HACK
void uploadDifference2(string path)
{
assert(isFile(path));
Item item;
if (itemdb.selectByPath(path, item)) {
uploadDifference(item);
} else {
uploadNewFile(path);
} }
} }
@ -465,7 +462,7 @@ final class SyncEngine
]); ]);
writeln(diff.toPrettyString()); writeln(diff.toPrettyString());
auto res = onedrive.updateById(item.id, diff, item.eTag); auto res = onedrive.updateById(item.id, diff, item.eTag);
cacheItem(res); saveItem(res);
} }
void deleteByPath(const(char)[] path) void deleteByPath(const(char)[] path)
@ -475,6 +472,6 @@ final class SyncEngine
if (!itemdb.selectByPath(path, item)) { if (!itemdb.selectByPath(path, item)) {
throw new SyncException("Can't delete a non synced item"); throw new SyncException("Can't delete a non synced item");
} }
deleteItem(item); uploadDeleteItem(item);
} }
} }

View file

@ -1,10 +1,10 @@
import std.conv: to; import std.conv: to;
import std.digest.crc; import std.digest.crc;
import std.digest.digest; import std.digest.digest;
import std.stdio;
import std.string: chomp;
import std.file: exists, rename; import std.file: exists, rename;
import std.path: extension; import std.path: extension;
import std.stdio;
import std.string: chomp;
private string deviceName; private string deviceName;