Implement OneDrive Business Shared Folders Support (Issue #459) (#473)

* Implement OneDrive Business Shared Folders Support
This commit is contained in:
abraunegg 2020-06-27 19:10:37 +10:00 committed by GitHub
parent 650cb97d3f
commit 9cc72c2396
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 1233 additions and 340 deletions

View file

@ -54,7 +54,7 @@ endif
system_unit_files = contrib/systemd/onedrive@.service system_unit_files = contrib/systemd/onedrive@.service
user_unit_files = contrib/systemd/onedrive.service user_unit_files = contrib/systemd/onedrive.service
DOCFILES = README.md config LICENSE CHANGELOG.md docs/Docker.md docs/INSTALL.md docs/Office365.md docs/USAGE.md DOCFILES = README.md config LICENSE CHANGELOG.md docs/Docker.md docs/INSTALL.md docs/Office365.md docs/USAGE.md docs/BusinessSharedFolders.md
ifneq ("$(wildcard /etc/redhat-release)","") ifneq ("$(wildcard /etc/redhat-release)","")
RHEL = $(shell cat /etc/redhat-release | grep -E "(Red Hat Enterprise Linux Server|CentOS)" | wc -l) RHEL = $(shell cat /etc/redhat-release | grep -E "(Red Hat Enterprise Linux Server|CentOS)" | wc -l)

View file

@ -17,8 +17,8 @@ This client is a 'fork' of the [skilion](https://github.com/skilion/onedrive) cl
* File upload / download validation to ensure data integrity * File upload / download validation to ensure data integrity
* Resumable uploads * Resumable uploads
* Support OneDrive for Business (part of Office 365) * Support OneDrive for Business (part of Office 365)
* Shared folders (OneDrive Personal) * Shared Folder support for OneDrive Personal and OneDrive Business accounts
* SharePoint / Office 365 Shared Libraries * SharePoint / Office365 Shared Libraries
* Desktop notifications via libnotify * Desktop notifications via libnotify
* Dry-run capability to test configuration changes * Dry-run capability to test configuration changes
* Prevent major OneDrive accidental data deletion after configuration change * Prevent major OneDrive accidental data deletion after configuration change
@ -37,6 +37,9 @@ See [docs/USAGE.md](https://github.com/abraunegg/onedrive/blob/master/docs/USAGE
## Docker support ## Docker support
See [docs/Docker.md](https://github.com/abraunegg/onedrive/blob/master/docs/Docker.md) See [docs/Docker.md](https://github.com/abraunegg/onedrive/blob/master/docs/Docker.md)
## OneDrive Business Shared Folders
See [docs/BusinessSharedFolders.md](https://github.com/abraunegg/onedrive/blob/master/docs/docs/BusinessSharedFolders.md)
## SharePoint / Office 365 Shared Libraries (Business or Education) ## SharePoint / Office 365 Shared Libraries (Business or Education)
See [docs/Office365.md](https://github.com/abraunegg/onedrive/blob/master/docs/Office365.md) See [docs/Office365.md](https://github.com/abraunegg/onedrive/blob/master/docs/Office365.md)

2
config
View file

@ -37,3 +37,5 @@
# application_id = "" # application_id = ""
# resync = "false" # resync = "false"
# bypass_data_preservation = "false" # bypass_data_preservation = "false"
# azure_ad_endpoint = ""
# sync_business_shared_folders = "false"

View file

@ -0,0 +1,187 @@
# How to configure OneDrive Business Shared Folder Sync
Syncing OneDrive Business Shared Folders requires additional configuration for your 'onedrive' client:
1. List available shared folders to determine which folder you wish to sync & to validate that you have access to that folder
2. Create a new file called 'business_shared_folders' in your config directory which contains a list of the shared folders you wish to sync
3. Perform a sync
## Listing available OneDrive Business Shared Folders
List the available OneDrive Business Shared folders with the following command:
```text
onedrive --list-shared-folders
```
This will return a listing of all OneDrive Business Shared folders which have been shared with you and by whom. This is important for conflict resolution:
```text
Initializing the Synchronization Engine ...
Listing available OneDrive Business Shared Folders:
---------------------------------------
Shared Folder: SharedFolder0
Shared By: Firstname Lastname
---------------------------------------
Shared Folder: SharedFolder1
Shared By: Firstname Lastname
---------------------------------------
Shared Folder: SharedFolder2
Shared By: Firstname Lastname
---------------------------------------
Shared Folder: SharedFolder0
Shared By: Firstname Lastname (user@domain)
---------------------------------------
Shared Folder: SharedFolder1
Shared By: Firstname Lastname (user@domain)
---------------------------------------
Shared Folder: SharedFolder2
Shared By: Firstname Lastname (user@domain)
...
```
## Configuring OneDrive Business Shared Folders
1. Create a new file called 'business_shared_folders' in your config directory
2. On each new line, list the OneDrive Business Shared Folder you wish to sync
```text
[alex@centos7full onedrive]$ cat ~/.config/onedrive/business_shared_folders
# comment
Child Shared Folder
# Another comment
Top Level to Share
[alex@centos7full onedrive]$
```
3. Validate your configuration with `onedrive --display-config`:
```text
Configuration file successfully loaded
onedrive version = v2.4.3
Config path = /home/alex/.config/onedrive-business/
Config file found in config path = true
Config option 'check_nosync' = false
Config option 'sync_dir' = /home/alex/OneDriveBusiness
Config option 'skip_dir' =
Config option 'skip_file' = ~*|.~*|*.tmp
Config option 'skip_dotfiles' = false
Config option 'skip_symlinks' = false
Config option 'monitor_interval' = 300
Config option 'min_notify_changes' = 5
Config option 'log_dir' = /var/log/onedrive/
Config option 'classify_as_big_delete' = 1000
Config option 'sync_root_files' = false
Selective sync 'sync_list' configured = false
Business Shared Folders configured = true
business_shared_folders contents:
# comment
Child Shared Folder
# Another comment
Top Level to Share
```
## Performing a sync of OneDrive Business Shared Folders
Perform a standalone sync using the following command: `onedrive --synchronize --sync-shared-folders --verbose`:
```text
onedrive --synchronize --sync-shared-folders --verbose
Using 'user' Config Dir: /home/alex/.config/onedrive-business/
Using 'system' Config Dir:
Configuration file successfully loaded
Initializing the OneDrive API ...
Configuring Global Azure AD Endpoints
Opening the item database ...
All operations will be performed in: /home/alex/OneDriveBusiness
Application version: v2.4.3
Account Type: business
Default Drive ID: b!bO8V7s9SSk6r7mWHpIjURotN33W1W2tEv3OXV_oFIdQimEdOHR-1So7CqeT1MfHA
Default Root ID: 01WIXGO5V6Y2GOVW7725BZO354PWSELRRZ
Remaining Free Space: 1098316220277
Fetching details for OneDrive Root
OneDrive Root exists in the database
Initializing the Synchronization Engine ...
Syncing changes from OneDrive ...
Applying changes of Path ID: 01WIXGO5V6Y2GOVW7725BZO354PWSELRRZ
Number of items from OneDrive to process: 0
Attempting to sync OneDrive Business Shared Folders
Syncing this OneDrive Business Shared Folder: Child Shared Folder
OneDrive Business Shared Folder - Shared By: test user
Applying changes of Path ID: 01JRXHEZMREEB3EJVHNVHKNN454Q7DFXPR
Adding OneDrive root details for processing
Adding OneDrive folder details for processing
Adding 4 OneDrive items for processing from OneDrive folder
Adding 2 OneDrive items for processing from /Child Shared Folder/Cisco VDI Whitepaper
Adding 2 OneDrive items for processing from /Child Shared Folder/SMPP_Shared
Processing 11 OneDrive items to ensure consistent local state
Syncing this OneDrive Business Shared Folder: Top Level to Share
OneDrive Business Shared Folder - Shared By: test user (testuser@mynasau3.onmicrosoft.com)
Applying changes of Path ID: 01JRXHEZLRMXHKBYZNOBF3TQOPBXS3VZMA
Adding OneDrive root details for processing
Adding OneDrive folder details for processing
Adding 4 OneDrive items for processing from OneDrive folder
Adding 3 OneDrive items for processing from /Top Level to Share/10-Files
Adding 2 OneDrive items for processing from /Top Level to Share/10-Files/Cisco VDI Whitepaper
Adding 2 OneDrive items for processing from /Top Level to Share/10-Files/Images
Adding 8 OneDrive items for processing from /Top Level to Share/10-Files/Images/JPG
Adding 8 OneDrive items for processing from /Top Level to Share/10-Files/Images/PNG
Adding 2 OneDrive items for processing from /Top Level to Share/10-Files/SMPP
Processing 31 OneDrive items to ensure consistent local state
Uploading differences of ~/OneDriveBusiness
Processing root
The directory has not changed
Processing SMPP_Local
The directory has not changed
Processing SMPP-IF-SPEC_v3_3-24858.pdf
The file has not changed
Processing SMPP_v3_4_Issue1_2-24857.pdf
The file has not changed
Processing new_local_file.txt
The file has not changed
Processing root
The directory has not changed
...
The directory has not changed
Processing week02-03-Combinational_Logic-v1.pptx
The file has not changed
Uploading new items of ~/OneDriveBusiness
Applying changes of Path ID: 01WIXGO5V6Y2GOVW7725BZO354PWSELRRZ
Number of items from OneDrive to process: 0
Attempting to sync OneDrive Business Shared Folders
Syncing this OneDrive Business Shared Folder: Child Shared Folder
OneDrive Business Shared Folder - Shared By: test user
Applying changes of Path ID: 01JRXHEZMREEB3EJVHNVHKNN454Q7DFXPR
Adding OneDrive root details for processing
Adding OneDrive folder details for processing
Adding 4 OneDrive items for processing from OneDrive folder
Adding 2 OneDrive items for processing from /Child Shared Folder/Cisco VDI Whitepaper
Adding 2 OneDrive items for processing from /Child Shared Folder/SMPP_Shared
Processing 11 OneDrive items to ensure consistent local state
Syncing this OneDrive Business Shared Folder: Top Level to Share
OneDrive Business Shared Folder - Shared By: test user (testuser@mynasau3.onmicrosoft.com)
Applying changes of Path ID: 01JRXHEZLRMXHKBYZNOBF3TQOPBXS3VZMA
Adding OneDrive root details for processing
Adding OneDrive folder details for processing
Adding 4 OneDrive items for processing from OneDrive folder
Adding 3 OneDrive items for processing from /Top Level to Share/10-Files
Adding 2 OneDrive items for processing from /Top Level to Share/10-Files/Cisco VDI Whitepaper
Adding 2 OneDrive items for processing from /Top Level to Share/10-Files/Images
Adding 8 OneDrive items for processing from /Top Level to Share/10-Files/Images/JPG
Adding 8 OneDrive items for processing from /Top Level to Share/10-Files/Images/PNG
Adding 2 OneDrive items for processing from /Top Level to Share/10-Files/SMPP
Processing 31 OneDrive items to ensure consistent local state
```
**Note:** Whenever you modify the `business_shared_folders` file you must perform a `--resync` of your database to clean up stale entries due to changes in your configuration.
## Enable / Disable syncing of OneDrive Business Shared Folders
Performing a sync of the configured OneDrive Business Shared Folders can be enabled / disabled via adding the following to your configuration file.
### Enable syncing of OneDrive Business Shared Folders via config file
```text
sync_business_shared_folders = "true"
```
### Disable syncing of OneDrive Business Shared Folders via config file
```text
sync_business_shared_folders = "false"
```
## Known Issues
Shared folders, shared with you from people outside of your 'organisation' are unable to be synced. This is due to the Microsoft Graph API not presenting these folders.
Shared folders that match this scenario, when you view 'Shared' via OneDrive online, will have a 'world' symbol as per below:
![shared_with_me](./images/shared_with_me.jpg)
This issue is being tracked by: [#966](https://github.com/abraunegg/onedrive/issues/966)

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View file

@ -23,6 +23,7 @@ final class Config
public string configFileSyncDir = ""; public string configFileSyncDir = "";
public string configFileSkipFile = ""; public string configFileSkipFile = "";
public string configFileSkipDir = ""; public string configFileSkipDir = "";
public string businessSharedFolderFilePath = "";
private string userConfigFilePath = ""; private string userConfigFilePath = "";
private string systemConfigFilePath = ""; private string systemConfigFilePath = "";
// was the application just authorised - paste of response uri // was the application just authorised - paste of response uri
@ -33,8 +34,9 @@ final class Config
private string[string] stringValues; private string[string] stringValues;
private bool[string] boolValues; private bool[string] boolValues;
private long[string] longValues; private long[string] longValues;
// Compile time regex - this does not change
public auto configRegex = ctRegex!(`^(\w+)\s*=\s*"(.*)"\s*$`); public auto configRegex = ctRegex!(`^(\w+)\s*=\s*"(.*)"\s*$`);
this(string confdirOption) this(string confdirOption)
{ {
// default configuration - entries in config file ~/.config/onedrive/config // default configuration - entries in config file ~/.config/onedrive/config
@ -100,6 +102,8 @@ final class Config
// AD Endpoint: https://login.chinacloudapi.cn // AD Endpoint: https://login.chinacloudapi.cn
// Graph Endpoint: https://microsoftgraph.chinacloudapi.cn // Graph Endpoint: https://microsoftgraph.chinacloudapi.cn
stringValues["azure_ad_endpoint"] = ""; stringValues["azure_ad_endpoint"] = "";
// Allow enable / disable of the syncing of OneDrive Business Shared Folders via configuration file
boolValues["sync_business_shared_folders"] = false;
// DEVELOPER OPTIONS // DEVELOPER OPTIONS
// display_memory = true | false // display_memory = true | false
@ -189,6 +193,7 @@ final class Config
userConfigFilePath = buildNormalizedPath(configDirName ~ "/config"); userConfigFilePath = buildNormalizedPath(configDirName ~ "/config");
syncListFilePath = buildNormalizedPath(configDirName ~ "/sync_list"); syncListFilePath = buildNormalizedPath(configDirName ~ "/sync_list");
systemConfigFilePath = buildNormalizedPath(systemConfigDirName ~ "/config"); systemConfigFilePath = buildNormalizedPath(systemConfigDirName ~ "/config");
businessSharedFolderFilePath = buildNormalizedPath(configDirName ~ "/business_shared_folders");
// Debug Output for application set variables based on configDirName // Debug Output for application set variables based on configDirName
log.vdebug("refreshTokenFilePath = ", refreshTokenFilePath); log.vdebug("refreshTokenFilePath = ", refreshTokenFilePath);
@ -199,6 +204,7 @@ final class Config
log.vdebug("userConfigFilePath = ", userConfigFilePath); log.vdebug("userConfigFilePath = ", userConfigFilePath);
log.vdebug("syncListFilePath = ", syncListFilePath); log.vdebug("syncListFilePath = ", syncListFilePath);
log.vdebug("systemConfigFilePath = ", systemConfigFilePath); log.vdebug("systemConfigFilePath = ", systemConfigFilePath);
log.vdebug("businessSharedFolderFilePath = ", businessSharedFolderFilePath);
} }
bool initialize() bool initialize()
@ -259,13 +265,16 @@ final class Config
boolValues["force"] = false; boolValues["force"] = false;
boolValues["remove_source_files"] = false; boolValues["remove_source_files"] = false;
boolValues["skip_dir_strict_match"] = false; boolValues["skip_dir_strict_match"] = false;
boolValues["list_business_shared_folders"] = false;
// Application Startup option validation // Application Startup option validation
try { try {
string tmpStr; string tmpStr;
bool tmpBol; bool tmpBol;
long tmpVerb; long tmpVerb;
// duplicated from main.d to get full help output!
auto opt = getopt( auto opt = getopt(
args, args,
std.getopt.config.bundling, std.getopt.config.bundling,
std.getopt.config.caseSensitive, std.getopt.config.caseSensitive,
@ -404,7 +413,6 @@ final class Config
"user-agent", "user-agent",
"Specify a User Agent string to the http client", "Specify a User Agent string to the http client",
&stringValues["user_agent"], &stringValues["user_agent"],
// duplicated from main.d to get full help output!
"confdir", "confdir",
"Set the directory used to store the configuration files", "Set the directory used to store the configuration files",
&tmpStr, &tmpStr,
@ -413,7 +421,13 @@ final class Config
&tmpVerb, &tmpVerb,
"version", "version",
"Print the version and exit", "Print the version and exit",
&tmpBol &tmpBol,
"list-shared-folders",
"List OneDrive Business Shared Folders",
&boolValues["list_business_shared_folders"],
"sync-shared-folders",
"Sync OneDrive Business Shared Folders",
&boolValues["sync_business_shared_folders"]
); );
if (opt.helpWanted) { if (opt.helpWanted) {
outputLongHelp(opt.options); outputLongHelp(opt.options);

View file

@ -2,6 +2,8 @@ import std.datetime;
import std.exception; import std.exception;
import std.path; import std.path;
import std.string; import std.string;
import std.stdio;
import std.algorithm.searching;
import core.stdc.stdlib; import core.stdc.stdlib;
import sqlite; import sqlite;
static import log; static import log;
@ -368,9 +370,14 @@ final class ItemDatabase
if (r2.empty) { if (r2.empty) {
// root reached // root reached
assert(path.length >= 4); assert(path.length >= 4);
// remove "root" // remove "root/" from path string if it exists
if (path.length >= 5) path = path[5 .. $]; if (path.length >= 5) {
else path = path[4 .. $]; if (canFind(path, "root/")){
path = path[5 .. $];
}
} else {
path = path[4 .. $];
}
// special case of computing the path of the root itself // special case of computing the path of the root itself
if (path.length == 0) path = "."; if (path.length == 0) path = ".";
break; break;
@ -427,17 +434,39 @@ final class ItemDatabase
// As we query /children to get all children from OneDrive, update anything in the database // As we query /children to get all children from OneDrive, update anything in the database
// to be flagged as not-in-sync, thus, we can use that flag to determing what was previously // to be flagged as not-in-sync, thus, we can use that flag to determing what was previously
// in-sync, but now deleted on OneDrive // in-sync, but now deleted on OneDrive
void downgradeSyncStatusFlag() void downgradeSyncStatusFlag(const(char)[] driveId, const(char)[] id)
{ {
db.exec("UPDATE item SET syncStatus = 'N'"); assert(driveId);
auto stmt = db.prepare("UPDATE item SET syncStatus = 'N' WHERE driveId = ?1 AND id = ?2");
stmt.bind(1, driveId);
stmt.bind(2, id);
stmt.exec();
} }
// National Cloud Deployments (US and DE) do not support /delta as a query // National Cloud Deployments (US and DE) do not support /delta as a query
// Select items that have a out-of-sync flag set // Select items that have a out-of-sync flag set
Item[] selectOutOfSyncItems() Item[] selectOutOfSyncItems(const(char)[] driveId)
{ {
assert(driveId);
Item[] items; Item[] items;
auto stmt = db.prepare("SELECT * FROM item WHERE syncStatus = 'N'"); auto stmt = db.prepare("SELECT * FROM item WHERE syncStatus = 'N' AND driveId = ?1");
stmt.bind(1, driveId);
auto res = stmt.exec();
while (!res.empty) {
items ~= buildItem(res);
res.step();
}
return items;
}
// OneDrive Business Folders are stored in the database potentially without a root | parentRoot link
// Select items associated with the provided driveId
Item[] selectByDriveId(const(char)[] driveId)
{
assert(driveId);
Item[] items;
auto stmt = db.prepare("SELECT * FROM item WHERE driveId = ?1 AND parentId IS NULL");
stmt.bind(1, driveId);
auto res = stmt.exec(); auto res = stmt.exec();
while (!res.empty) { while (!res.empty) {
items ~= buildItem(res); items ~= buildItem(res);

View file

@ -26,6 +26,7 @@ int main(string[] args)
string configFilePath; string configFilePath;
string syncListFilePath; string syncListFilePath;
string databaseFilePath; string databaseFilePath;
string businessSharedFolderFilePath;
string currentConfigHash; string currentConfigHash;
string currentSyncListHash; string currentSyncListHash;
string previousConfigHash; string previousConfigHash;
@ -35,7 +36,11 @@ int main(string[] args)
string configBackupFile; string configBackupFile;
string syncDir; string syncDir;
string logOutputMessage; string logOutputMessage;
string currentBusinessSharedFoldersHash;
string previousBusinessSharedFoldersHash;
string businessSharedFoldersHashFile;
bool configOptionsDifferent = false; bool configOptionsDifferent = false;
bool businessSharedFoldersDifferent = false;
bool syncListConfigured = false; bool syncListConfigured = false;
bool syncListDifferent = false; bool syncListDifferent = false;
bool syncDirDifferent = false; bool syncDirDifferent = false;
@ -144,6 +149,7 @@ int main(string[] args)
configFilePath = buildNormalizedPath(cfg.configDirName ~ "/config"); configFilePath = buildNormalizedPath(cfg.configDirName ~ "/config");
syncListFilePath = buildNormalizedPath(cfg.configDirName ~ "/sync_list"); syncListFilePath = buildNormalizedPath(cfg.configDirName ~ "/sync_list");
databaseFilePath = buildNormalizedPath(cfg.configDirName ~ "/items.db"); databaseFilePath = buildNormalizedPath(cfg.configDirName ~ "/items.db");
businessSharedFolderFilePath = buildNormalizedPath(cfg.configDirName ~ "/business_shared_folders");
// Has any of our configuration that would require a --resync been changed? // Has any of our configuration that would require a --resync been changed?
// 1. sync_list file modification // 1. sync_list file modification
@ -152,6 +158,7 @@ int main(string[] args)
configHashFile = buildNormalizedPath(cfg.configDirName ~ "/.config.hash"); configHashFile = buildNormalizedPath(cfg.configDirName ~ "/.config.hash");
syncListHashFile = buildNormalizedPath(cfg.configDirName ~ "/.sync_list.hash"); syncListHashFile = buildNormalizedPath(cfg.configDirName ~ "/.sync_list.hash");
configBackupFile = buildNormalizedPath(cfg.configDirName ~ "/.config.backup"); configBackupFile = buildNormalizedPath(cfg.configDirName ~ "/.config.backup");
businessSharedFoldersHashFile = buildNormalizedPath(cfg.configDirName ~ "/.business_shared_folders.hash");
// Does a config file exist with a valid hash file // Does a config file exist with a valid hash file
if ((exists(configFilePath)) && (!exists(configHashFile))) { if ((exists(configFilePath)) && (!exists(configHashFile))) {
@ -165,6 +172,12 @@ int main(string[] args)
std.file.write(syncListHashFile, computeQuickXorHash(syncListFilePath)); std.file.write(syncListHashFile, computeQuickXorHash(syncListFilePath));
} }
// check if business_shared_folders & business_shared_folders hash exists
if ((exists(businessSharedFolderFilePath)) && (!exists(businessSharedFoldersHashFile))) {
// Hash of business_shared_folders file needs to be created
std.file.write(businessSharedFoldersHashFile, computeQuickXorHash(businessSharedFolderFilePath));
}
// If hash files exist, but config files do not ... remove the hash, but only if --resync was issued as now the application will use 'defaults' which 'may' be different // If hash files exist, but config files do not ... remove the hash, but only if --resync was issued as now the application will use 'defaults' which 'may' be different
if ((!exists(configFilePath)) && (exists(configHashFile))) { if ((!exists(configFilePath)) && (exists(configHashFile))) {
// if --resync safe remove config.hash and config.backup // if --resync safe remove config.hash and config.backup
@ -180,11 +193,18 @@ int main(string[] args)
if (cfg.getValueBool("resync")) safeRemove(syncListHashFile); if (cfg.getValueBool("resync")) safeRemove(syncListHashFile);
} }
if ((!exists(businessSharedFolderFilePath)) && (exists(businessSharedFoldersHashFile))) {
// if --resync safe remove business_shared_folders.hash
if (cfg.getValueBool("resync")) safeRemove(businessSharedFoldersHashFile);
}
// Read config hashes if they exist // Read config hashes if they exist
if (exists(configFilePath)) currentConfigHash = computeQuickXorHash(configFilePath); if (exists(configFilePath)) currentConfigHash = computeQuickXorHash(configFilePath);
if (exists(syncListFilePath)) currentSyncListHash = computeQuickXorHash(syncListFilePath); if (exists(syncListFilePath)) currentSyncListHash = computeQuickXorHash(syncListFilePath);
if (exists(businessSharedFolderFilePath)) currentBusinessSharedFoldersHash = computeQuickXorHash(businessSharedFolderFilePath);
if (exists(configHashFile)) previousConfigHash = readText(configHashFile); if (exists(configHashFile)) previousConfigHash = readText(configHashFile);
if (exists(syncListHashFile)) previousSyncListHash = readText(syncListHashFile); if (exists(syncListHashFile)) previousSyncListHash = readText(syncListHashFile);
if (exists(businessSharedFoldersHashFile)) previousBusinessSharedFoldersHash = readText(businessSharedFoldersHashFile);
// Was sync_list file updated? // Was sync_list file updated?
if (currentSyncListHash != previousSyncListHash) { if (currentSyncListHash != previousSyncListHash) {
@ -193,6 +213,13 @@ int main(string[] args)
syncListDifferent = true; syncListDifferent = true;
} }
// Was business_shared_folders updated?
if (currentBusinessSharedFoldersHash != previousBusinessSharedFoldersHash) {
// Debugging output to assist what changed
log.vdebug("business_shared_folders file has been updated, --resync needed");
businessSharedFoldersDifferent = true;
}
// Was config file updated between last execution ang this execution? // Was config file updated between last execution ang this execution?
if (currentConfigHash != previousConfigHash) { if (currentConfigHash != previousConfigHash) {
// config file was updated, however we only want to trigger a --resync requirement if sync_dir, skip_dir, skip_file or drive_id was modified // config file was updated, however we only want to trigger a --resync requirement if sync_dir, skip_dir, skip_file or drive_id was modified
@ -315,7 +342,7 @@ int main(string[] args)
} }
// Has anything triggered a --resync requirement? // Has anything triggered a --resync requirement?
if (configOptionsDifferent || syncListDifferent || syncDirDifferent || skipFileDifferent || skipDirDifferent) { if (configOptionsDifferent || syncListDifferent || syncDirDifferent || skipFileDifferent || skipDirDifferent || businessSharedFoldersDifferent) {
// --resync needed, is the user just testing configuration changes? // --resync needed, is the user just testing configuration changes?
if (!cfg.getValueBool("display_config")){ if (!cfg.getValueBool("display_config")){
// not testing configuration changes // not testing configuration changes
@ -340,6 +367,11 @@ int main(string[] args)
log.vdebug("updating sync_list hash as --resync issued"); log.vdebug("updating sync_list hash as --resync issued");
std.file.write(syncListHashFile, computeQuickXorHash(syncListFilePath)); std.file.write(syncListHashFile, computeQuickXorHash(syncListFilePath));
} }
if (exists(businessSharedFolderFilePath)) {
// update business_shared_folders hash
log.vdebug("updating business_shared_folders hash as --resync issued");
std.file.write(businessSharedFoldersHashFile, computeQuickXorHash(businessSharedFolderFilePath));
}
} }
} }
} }
@ -438,10 +470,8 @@ int main(string[] args)
if (cfg.getValueBool("display_config")){ if (cfg.getValueBool("display_config")){
// Display application version // Display application version
writeln("onedrive version = ", strip(import("version"))); writeln("onedrive version = ", strip(import("version")));
// Display all of the pertinent configuration options // Display all of the pertinent configuration options
writeln("Config path = ", cfg.configDirName); writeln("Config path = ", cfg.configDirName);
// Does a config file exist or are we using application defaults // Does a config file exist or are we using application defaults
writeln("Config file found in config path = ", exists(configFilePath)); writeln("Config file found in config path = ", exists(configFilePath));
@ -465,7 +495,7 @@ int main(string[] args)
// Is sync_list configured? // Is sync_list configured?
if (exists(syncListFilePath)){ if (exists(syncListFilePath)){
writeln("Config option 'sync_root_files' = ", cfg.getValueBool("sync_root_files")); writeln("Config option 'sync_root_files' = ", cfg.getValueBool("sync_root_files"));
writeln("Selective sync configured = true"); writeln("Selective sync 'sync_list' configured = true");
writeln("sync_list contents:"); writeln("sync_list contents:");
// Output the sync_list contents // Output the sync_list contents
auto syncListFile = File(syncListFilePath); auto syncListFile = File(syncListFilePath);
@ -476,10 +506,25 @@ int main(string[] args)
} }
} else { } else {
writeln("Config option 'sync_root_files' = ", cfg.getValueBool("sync_root_files")); writeln("Config option 'sync_root_files' = ", cfg.getValueBool("sync_root_files"));
writeln("Selective sync configured = false"); writeln("Selective sync 'sync_list' configured = false");
} }
// exit // Is business_shared_folders configured
if (exists(businessSharedFolderFilePath)){
writeln("Business Shared Folders configured = true");
writeln("business_shared_folders contents:");
// Output the business_shared_folders contents
auto businessSharedFolderFileList = File(businessSharedFolderFilePath);
auto range = businessSharedFolderFileList.byLine();
foreach (line; range)
{
writeln(line);
}
} else {
writeln("Business Shared Folders configured = false");
}
// Exit
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
@ -518,9 +563,9 @@ int main(string[] args)
performSyncOK = true; performSyncOK = true;
} }
// create-directory, remove-directory, source-directory, destination-directory // create-directory, remove-directory, source-directory, destination-directory
// these are activities that dont perform a sync, so to not generate an error message for these items either // these are activities that dont perform a sync, so to not generate an error message for these items either
if (((cfg.getValueString("create_directory") != "") || (cfg.getValueString("remove_directory") != "")) || ((cfg.getValueString("source_directory") != "") && (cfg.getValueString("destination_directory") != "")) || (cfg.getValueString("get_file_link") != "") || (cfg.getValueString("get_o365_drive_id") != "") || cfg.getValueBool("display_sync_status")) { if (((cfg.getValueString("create_directory") != "") || (cfg.getValueString("remove_directory") != "")) || ((cfg.getValueString("source_directory") != "") && (cfg.getValueString("destination_directory") != "")) || (cfg.getValueString("get_file_link") != "") || (cfg.getValueString("get_o365_drive_id") != "") || cfg.getValueBool("display_sync_status") || cfg.getValueBool("list_business_shared_folders")) {
performSyncOK = true; performSyncOK = true;
} }
@ -580,11 +625,13 @@ int main(string[] args)
// Configure selective sync by parsing and getting a regex for skip_file config component // Configure selective sync by parsing and getting a regex for skip_file config component
auto selectiveSync = new SelectiveSync(); auto selectiveSync = new SelectiveSync();
if (exists(cfg.syncListFilePath)){
// load sync_list if it exists
if (exists(syncListFilePath)){
log.vdebug("Loading user configured sync_list file ..."); log.vdebug("Loading user configured sync_list file ...");
syncListConfigured = true; syncListConfigured = true;
// list what will be synced // list what will be synced
auto syncListFile = File(cfg.syncListFilePath); auto syncListFile = File(syncListFilePath);
auto range = syncListFile.byLine(); auto range = syncListFile.byLine();
foreach (line; range) foreach (line; range)
{ {
@ -596,7 +643,20 @@ int main(string[] args)
syncListFile.close(); syncListFile.close();
} }
} }
selectiveSync.load(cfg.syncListFilePath); selectiveSync.load(syncListFilePath);
// load business_shared_folders if it exists
if (exists(businessSharedFolderFilePath)){
log.vdebug("Loading user configured business_shared_folders file ...");
// list what will be synced
auto businessSharedFolderFileList = File(businessSharedFolderFilePath);
auto range = businessSharedFolderFileList.byLine();
foreach (line; range)
{
log.vdebug("business_shared_folders: ", line);
}
}
selectiveSync.loadSharedFolders(businessSharedFolderFilePath);
// Configure skip_dir, skip_file, skip-dir-strict-match & skip_dotfiles from config entries // Configure skip_dir, skip_file, skip-dir-strict-match & skip_dotfiles from config entries
// Handle skip_dir configuration in config file // Handle skip_dir configuration in config file
@ -725,11 +785,42 @@ int main(string[] args)
// Are we obtaining the Office 365 Drive ID for a given Office 365 SharePoint Shared Library? // Are we obtaining the Office 365 Drive ID for a given Office 365 SharePoint Shared Library?
if (cfg.getValueString("get_o365_drive_id") != "") { if (cfg.getValueString("get_o365_drive_id") != "") {
sync.querySiteCollectionForDriveID(cfg.getValueString("get_o365_drive_id")); sync.querySiteCollectionForDriveID(cfg.getValueString("get_o365_drive_id"));
// Exit application
// Use exit scopes to shutdown API
return EXIT_SUCCESS;
} }
// Are we obtaining the URL path for a synced file? // Are we obtaining the URL path for a synced file?
if (cfg.getValueString("get_file_link") != "") { if (cfg.getValueString("get_file_link") != "") {
sync.queryOneDriveForFileURL(cfg.getValueString("get_file_link"), syncDir); sync.queryOneDriveForFileURL(cfg.getValueString("get_file_link"), syncDir);
// Exit application
// Use exit scopes to shutdown API
return EXIT_SUCCESS;
}
// Are we listing OneDrive Business Shared Folders
if (cfg.getValueBool("list_business_shared_folders")) {
// Is this a business account type?
if (sync.getAccountType() == "business"){
// List OneDrive Business Shared Folders
sync.listOneDriveBusinessSharedFolders();
} else {
log.error("ERROR: Unsupported account type for listing OneDrive Business Shared Folders");
}
// Exit application
// Use exit scopes to shutdown API
return EXIT_SUCCESS;
}
// Are we going to sync OneDrive Business Shared Folders
if (cfg.getValueBool("sync_business_shared_folders")) {
// Is this a business account type?
if (sync.getAccountType() == "business"){
// Configure flag to sync business folders
sync.setSyncBusinessFolders();
} else {
log.error("ERROR: Unsupported account type for syncing OneDrive Business Shared Folders");
}
} }
// Are we displaying the sync status of the client? // Are we displaying the sync status of the client?
@ -752,9 +843,9 @@ int main(string[] args)
if (cfg.getValueBool("synchronize")) { if (cfg.getValueBool("synchronize")) {
if (online) { if (online) {
// Check user entry for local path - the above chdir means we are already in ~/OneDrive/ thus singleDirectory is local to this path // Check user entry for local path - the above chdir means we are already in ~/OneDrive/ thus singleDirectory is local to this path
if (cfg.getValueString("single_directory") != ""){ if (cfg.getValueString("single_directory") != "") {
// Does the directory we want to sync actually exist? // Does the directory we want to sync actually exist?
if (!exists(cfg.getValueString("single_directory"))){ if (!exists(cfg.getValueString("single_directory"))) {
// the requested directory does not exist .. // the requested directory does not exist ..
log.logAndNotify("ERROR: The requested local directory does not exist. Please check ~/OneDrive/ for requested path"); log.logAndNotify("ERROR: The requested local directory does not exist. Please check ~/OneDrive/ for requested path");
// Use exit scopes to shutdown API // Use exit scopes to shutdown API
@ -938,7 +1029,7 @@ int main(string[] args)
} }
try { try {
// perform a --monitor sync // perform a --monitor sync
log.vlog("Starting a sync with OneDrive"); if (logMonitorCounter == logInterval) log.log("Starting a sync with OneDrive");
performSync(sync, cfg.getValueString("single_directory"), cfg.getValueBool("download_only"), cfg.getValueBool("local_first"), cfg.getValueBool("upload_only"), (logMonitorCounter == logInterval ? MONITOR_LOG_QUIET : MONITOR_LOG_SILENT), fullScanRequired, syncListConfiguredFullScanOverride, displaySyncOptions, cfg.getValueBool("monitor"), m); performSync(sync, cfg.getValueString("single_directory"), cfg.getValueBool("download_only"), cfg.getValueBool("local_first"), cfg.getValueBool("upload_only"), (logMonitorCounter == logInterval ? MONITOR_LOG_QUIET : MONITOR_LOG_SILENT), fullScanRequired, syncListConfiguredFullScanOverride, displaySyncOptions, cfg.getValueBool("monitor"), m);
if (!cfg.getValueBool("download_only")) { if (!cfg.getValueBool("download_only")) {
// discard all events that may have been generated by the sync that have not already been handled // discard all events that may have been generated by the sync that have not already been handled
@ -949,7 +1040,7 @@ int main(string[] args)
log.error("ERROR: The following inotify error was generated: ", e.msg); log.error("ERROR: The following inotify error was generated: ", e.msg);
} }
} }
log.vlog("Sync with OneDrive is complete"); if (logMonitorCounter == logInterval) log.log("Sync with OneDrive is complete");
} catch (CurlException e) { } catch (CurlException e) {
// we already tried three times in the performSync routine // we already tried three times in the performSync routine
// if we still have problems, then the sync handle might have // if we still have problems, then the sync handle might have

View file

@ -60,6 +60,9 @@ private {
string driveUrl = globalGraphEndpoint ~ "/v1.0/me/drive"; string driveUrl = globalGraphEndpoint ~ "/v1.0/me/drive";
string driveByIdUrl = globalGraphEndpoint ~ "/v1.0/drives/"; string driveByIdUrl = globalGraphEndpoint ~ "/v1.0/drives/";
// What is 'shared with me' Query
string sharedWithMe = globalGraphEndpoint ~ "/v1.0/me/drive/sharedWithMe";
// Item Queries // Item Queries
string itemByIdUrl = globalGraphEndpoint ~ "/v1.0/me/drive/items/"; string itemByIdUrl = globalGraphEndpoint ~ "/v1.0/me/drive/items/";
string itemByPathUrl = globalGraphEndpoint ~ "/v1.0/me/drive/root:/"; string itemByPathUrl = globalGraphEndpoint ~ "/v1.0/me/drive/root:/";
@ -156,6 +159,8 @@ final class OneDriveApi
// Office 365 / SharePoint Queries // Office 365 / SharePoint Queries
siteSearchUrl = usl4GraphEndpoint ~ "/v1.0/sites?search"; siteSearchUrl = usl4GraphEndpoint ~ "/v1.0/sites?search";
siteDriveUrl = usl4GraphEndpoint ~ "/v1.0/sites/"; siteDriveUrl = usl4GraphEndpoint ~ "/v1.0/sites/";
// Shared With Me
sharedWithMe = usl4GraphEndpoint ~ "/v1.0/me/drive/sharedWithMe";
break; break;
case "USL5": case "USL5":
log.log("Configuring Azure AD for US Government Endpoints (DOD)"); log.log("Configuring Azure AD for US Government Endpoints (DOD)");
@ -172,6 +177,8 @@ final class OneDriveApi
// Office 365 / SharePoint Queries // Office 365 / SharePoint Queries
siteSearchUrl = usl5GraphEndpoint ~ "/v1.0/sites?search"; siteSearchUrl = usl5GraphEndpoint ~ "/v1.0/sites?search";
siteDriveUrl = usl5GraphEndpoint ~ "/v1.0/sites/"; siteDriveUrl = usl5GraphEndpoint ~ "/v1.0/sites/";
// Shared With Me
sharedWithMe = usl5GraphEndpoint ~ "/v1.0/me/drive/sharedWithMe";
break; break;
case "DE": case "DE":
log.log("Configuring Azure AD Germany"); log.log("Configuring Azure AD Germany");
@ -188,6 +195,8 @@ final class OneDriveApi
// Office 365 / SharePoint Queries // Office 365 / SharePoint Queries
siteSearchUrl = deGraphEndpoint ~ "/v1.0/sites?search"; siteSearchUrl = deGraphEndpoint ~ "/v1.0/sites?search";
siteDriveUrl = deGraphEndpoint ~ "/v1.0/sites/"; siteDriveUrl = deGraphEndpoint ~ "/v1.0/sites/";
// Shared With Me
sharedWithMe = deGraphEndpoint ~ "/v1.0/me/drive/sharedWithMe";
break; break;
case "CN": case "CN":
log.log("Configuring AD China operated by 21Vianet"); log.log("Configuring AD China operated by 21Vianet");
@ -204,6 +213,8 @@ final class OneDriveApi
// Office 365 / SharePoint Queries // Office 365 / SharePoint Queries
siteSearchUrl = cnGraphEndpoint ~ "/v1.0/sites?search"; siteSearchUrl = cnGraphEndpoint ~ "/v1.0/sites?search";
siteDriveUrl = cnGraphEndpoint ~ "/v1.0/sites/"; siteDriveUrl = cnGraphEndpoint ~ "/v1.0/sites/";
// Shared With Me
sharedWithMe = cnGraphEndpoint ~ "/v1.0/me/drive/sharedWithMe";
break; break;
// Default - all other entries // Default - all other entries
default: default:
@ -392,9 +403,25 @@ final class OneDriveApi
url = driveUrl ~ "/root"; url = driveUrl ~ "/root";
return get(url); return get(url);
} }
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get
JSONValue getDriveIdRoot(const(char)[] driveId)
{
checkAccessTokenExpired();
const(char)[] url;
url = driveByIdUrl ~ driveId ~ "/root";
return get(url);
}
// https://docs.microsoft.com/en-us/graph/api/drive-sharedwithme
JSONValue getSharedWithMe()
{
checkAccessTokenExpired();
return get(sharedWithMe);
}
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delta // https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delta
JSONValue viewChangesById(const(char)[] driveId, const(char)[] id, const(char)[] deltaLink) JSONValue viewChangesByItemId(const(char)[] driveId, const(char)[] id, const(char)[] deltaLink)
{ {
checkAccessTokenExpired(); checkAccessTokenExpired();
const(char)[] url; const(char)[] url;
@ -408,6 +435,18 @@ final class OneDriveApi
return get(url); return get(url);
} }
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delta
JSONValue viewChangesByDriveId(const(char)[] driveId, const(char)[] deltaLink)
{
checkAccessTokenExpired();
const(char)[] url = deltaLink;
if (url == null) {
url = driveByIdUrl ~ driveId ~ "/root/delta";
url ~= "?select=id,name,eTag,cTag,deleted,file,folder,root,fileSystemInfo,remoteItem,parentReference,size";
}
return get(url);
}
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_list_children // https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_list_children
JSONValue listChildren(const(char)[] driveId, const(char)[] id, const(char)[] nextLink) JSONValue listChildren(const(char)[] driveId, const(char)[] id, const(char)[] nextLink)
{ {
@ -422,7 +461,7 @@ final class OneDriveApi
} }
return get(url); return get(url);
} }
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get_content // https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get_content
void downloadById(const(char)[] driveId, const(char)[] id, string saveToPath, long fileSize) void downloadById(const(char)[] driveId, const(char)[] id, string saveToPath, long fileSize)
{ {
@ -506,6 +545,19 @@ final class OneDriveApi
return get(url); return get(url);
} }
// Return the requested details of the specified path on the specified drive id
JSONValue getPathDetailsByDriveId(const(char)[] driveId, const(string) path)
{
checkAccessTokenExpired();
const(char)[] url;
// string driveByIdUrl = "https://graph.microsoft.com/v1.0/drives/";
// Required format: /drives/{drive-id}/root:/{item-path}
url = driveByIdUrl ~ driveId ~ "/root:/" ~ encodeComponent(path);
url ~= "?select=id,name,eTag,cTag,deleted,file,folder,root,fileSystemInfo,remoteItem,parentReference,size";
return get(url);
}
// Return the requested details of the specified id // Return the requested details of the specified id
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get // https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get
JSONValue getFileDetails(const(char)[] driveId, const(char)[] id) JSONValue getFileDetails(const(char)[] driveId, const(char)[] id)

View file

@ -1,185 +1,240 @@
import std.algorithm; import std.algorithm;
import std.array; import std.array;
import std.file; import std.file;
import std.path; import std.path;
import std.regex; import std.regex;
import std.stdio; import std.stdio;
import util; import util;
final class SelectiveSync final class SelectiveSync
{ {
private string[] paths; private string[] paths;
private Regex!char mask; private string[] businessSharedFoldersList;
private Regex!char dirmask; private Regex!char mask;
private bool skipDirStrictMatch = false; private Regex!char dirmask;
private bool skipDotfiles = false; private bool skipDirStrictMatch = false;
private bool skipDotfiles = false;
void load(string filepath)
{ // load sync_list file
if (exists(filepath)) { void load(string filepath)
// open file as read only {
auto file = File(filepath, "r"); if (exists(filepath)) {
auto range = file.byLine(); // open file as read only
foreach (line; range) { auto file = File(filepath, "r");
// Skip comments in file auto range = file.byLine();
if (line.length == 0 || line[0] == ';' || line[0] == '#') continue; foreach (line; range) {
paths ~= buildNormalizedPath(line); // Skip comments in file
} if (line.length == 0 || line[0] == ';' || line[0] == '#') continue;
file.close(); paths ~= buildNormalizedPath(line);
} }
} file.close();
}
// Configure skipDirStrictMatch if function is called }
// By default, skipDirStrictMatch = false;
void setSkipDirStrictMatch() // Configure skipDirStrictMatch if function is called
{ // By default, skipDirStrictMatch = false;
skipDirStrictMatch = true; void setSkipDirStrictMatch()
} {
skipDirStrictMatch = true;
void setFileMask(const(char)[] mask) }
{
this.mask = wild2regex(mask); // load business_shared_folders file
} void loadSharedFolders(string filepath)
{
void setDirMask(const(char)[] dirmask) if (exists(filepath)) {
{ // open file as read only
this.dirmask = wild2regex(dirmask); auto file = File(filepath, "r");
} auto range = file.byLine();
foreach (line; range) {
// Configure skipDotfiles if function is called // Skip comments in file
// By default, skipDotfiles = false; if (line.length == 0 || line[0] == ';' || line[0] == '#') continue;
void setSkipDotfiles() businessSharedFoldersList ~= buildNormalizedPath(line);
{ }
skipDotfiles = true; file.close();
} }
}
// return value of skipDotfiles
bool getSkipDotfiles() void setFileMask(const(char)[] mask)
{ {
return skipDotfiles; this.mask = wild2regex(mask);
} }
// config file skip_dir parameter void setDirMask(const(char)[] dirmask)
bool isDirNameExcluded(string name) {
{ this.dirmask = wild2regex(dirmask);
// Does the directory name match skip_dir config entry? }
// Returns true if the name matches a skip_dir config entry
// Returns false if no match // Configure skipDotfiles if function is called
// By default, skipDotfiles = false;
// Try full path match first void setSkipDotfiles()
if (!name.matchFirst(dirmask).empty) { {
return true; skipDotfiles = true;
} else { }
// Do we check the base name as well?
if (!skipDirStrictMatch) { // return value of skipDotfiles
// check just the basename in the path bool getSkipDotfiles()
string filename = baseName(name); {
if(!filename.matchFirst(dirmask).empty) { return skipDotfiles;
return true; }
}
} // config file skip_dir parameter
} bool isDirNameExcluded(string name)
// no match {
return false; // Does the directory name match skip_dir config entry?
} // Returns true if the name matches a skip_dir config entry
// Returns false if no match
// config file skip_file parameter
bool isFileNameExcluded(string name) // Try full path match first
{ if (!name.matchFirst(dirmask).empty) {
// Does the file name match skip_file config entry? return true;
// Returns true if the name matches a skip_file config entry } else {
// Returns false if no match // Do we check the base name as well?
if (!skipDirStrictMatch) {
// Try full path match first // check just the basename in the path
if (!name.matchFirst(mask).empty) { string filename = baseName(name);
return true; if(!filename.matchFirst(dirmask).empty) {
} else { return true;
// check just the file name }
string filename = baseName(name); }
if(!filename.matchFirst(mask).empty) { }
return true; // no match
} return false;
} }
// no match
return false; // config file skip_file parameter
} bool isFileNameExcluded(string name)
{
// Match against sync_list only // Does the file name match skip_file config entry?
bool isPathExcludedViaSyncList(string path) // Returns true if the name matches a skip_file config entry
{ // Returns false if no match
return .isPathExcluded(path, paths);
} // Try full path match first
if (!name.matchFirst(mask).empty) {
// Match against skip_dir, skip_file & sync_list entries return true;
bool isPathExcludedMatchAll(string path) } else {
{ // check just the file name
return .isPathExcluded(path, paths) || .isPathMatched(path, mask) || .isPathMatched(path, dirmask); string filename = baseName(name);
} if(!filename.matchFirst(mask).empty) {
return true;
// is the path a dotfile? }
bool isDotFile(string path) }
{ // no match
// always allow the root return false;
if (path == ".") return false; }
path = buildNormalizedPath(path); // Match against sync_list only
auto paths = pathSplitter(path); bool isPathExcludedViaSyncList(string path)
foreach(base; paths) { {
if (startsWith(base, ".")){ return .isPathExcluded(path, paths);
return true; }
}
} // Match against skip_dir, skip_file & sync_list entries
return false; bool isPathExcludedMatchAll(string path)
} {
} return .isPathExcluded(path, paths) || .isPathMatched(path, mask) || .isPathMatched(path, dirmask);
}
// test if the given path is not included in the allowed paths
// if there are no allowed paths always return false // is the path a dotfile?
private bool isPathExcluded(string path, string[] allowedPaths) bool isDotFile(string path)
{ {
// always allow the root // always allow the root
if (path == ".") return false; if (path == ".") return false;
// if there are no allowed paths always return false
if (allowedPaths.empty) return false; path = buildNormalizedPath(path);
auto paths = pathSplitter(path);
path = buildNormalizedPath(path); foreach(base; paths) {
foreach (allowed; allowedPaths) { if (startsWith(base, ".")){
auto comm = commonPrefix(path, allowed); return true;
if (comm.length == path.length) { }
// the given path is contained in an allowed path }
return false; return false;
} }
if (comm.length == allowed.length && path[comm.length] == '/') {
// the given path is a subitem of an allowed path // is business shared folder matched
return false; bool isSharedFolderMatched(string name)
} {
} // if there are no shared folder always return false
return true; if (businessSharedFoldersList.empty) return false;
}
if (!name.matchFirst(businessSharedFoldersList).empty) {
// test if the given path is matched by the regex expression. return true;
// recursively test up the tree. } else {
private bool isPathMatched(string path, Regex!char mask) { return false;
path = buildNormalizedPath(path); }
auto paths = pathSplitter(path); }
string prefix = ""; // is business shared folder included
foreach(base; paths) { bool isPathIncluded(string path, string[] allowedPaths)
prefix ~= base; {
if (!path.matchFirst(mask).empty) { // always allow the root
// the given path matches something which we should skip if (path == ".") return true;
return true; // if there are no allowed paths always return true
} if (allowedPaths.empty) return true;
prefix ~= dirSeparator;
} path = buildNormalizedPath(path);
return false; foreach (allowed; allowedPaths) {
} auto comm = commonPrefix(path, allowed);
if (comm.length == path.length) {
unittest // the given path is contained in an allowed path
{ return true;
assert(isPathExcluded("Documents2", ["Documents"])); }
assert(!isPathExcluded("Documents", ["Documents"])); if (comm.length == allowed.length && path[comm.length] == '/') {
assert(!isPathExcluded("Documents/a.txt", ["Documents"])); // the given path is a subitem of an allowed path
assert(isPathExcluded("Hello/World", ["Hello/John"])); return true;
assert(!isPathExcluded(".", ["Documents"])); }
} }
return false;
}
}
// test if the given path is not included in the allowed paths
// if there are no allowed paths always return false
private bool isPathExcluded(string path, string[] allowedPaths)
{
// always allow the root
if (path == ".") return false;
// if there are no allowed paths always return false
if (allowedPaths.empty) return false;
path = buildNormalizedPath(path);
foreach (allowed; allowedPaths) {
auto comm = commonPrefix(path, allowed);
if (comm.length == path.length) {
// the given path is contained in an allowed path
return false;
}
if (comm.length == allowed.length && path[comm.length] == '/') {
// the given path is a subitem of an allowed path
return false;
}
}
return true;
}
// test if the given path is matched by the regex expression.
// recursively test up the tree.
private bool isPathMatched(string path, Regex!char mask) {
path = buildNormalizedPath(path);
auto paths = pathSplitter(path);
string prefix = "";
foreach(base; paths) {
prefix ~= base;
if (!path.matchFirst(mask).empty) {
// the given path matches something which we should skip
return true;
}
prefix ~= dirSeparator;
}
return false;
}
// unit tests
unittest
{
assert(isPathExcluded("Documents2", ["Documents"]));
assert(!isPathExcluded("Documents", ["Documents"]));
assert(!isPathExcluded("Documents/a.txt", ["Documents"]));
assert(isPathExcluded("Hello/World", ["Hello/John"]));
assert(!isPathExcluded(".", ["Documents"]));
}

File diff suppressed because it is too large Load diff