Introduction
I have been using Google Drive for many years without any issues, but since the only constant in life is change, I recently found myself having to use OneDrive.
I’m not very familiar with OneDrive, so the functions (and partial solutions) presented below might also be available in Microsoft OneDrive (as they’re quite basic). However, I haven’t been able to find them. And just in case you missed the title, this all apply to Apple macOS (though the situation isn’t much better even in Microsoft Windows, let alone Linux).
Google Drive I was using mainly for two reasons:
- synchronise copy of few directories my local computer disk
- collaborate with my colleagues on shared files and directories (folders shared to me) in finder
Solution
After installation, OneDrive doesn’t offer many configuration options. It creates a few directories, including a link in your home directory. There’s no option for collaboration, no option to configure additional directories, and no way to configure how files are replicated. Therefore, OneDrive can’t be treated as a utility for creating cloud backups, as any action in the cloud would also delete the file on my local drive.
I can’t do much about collaboration; it will always require opening OneDrive in a web browser. However, a solution for managing multiple folders and backups would be to keep folders (such as “docs,” “current work,” “projects,” etc.) in their current location and automatically copy their contents to the OneDrive folder, as described below. The downside of this approach is that your data will consume twice the storage space on your computer.
For automatic copy process I am using two brilliant open-source software, rsync and fswatch. First one, to replicate changed files (in source directories) and fswatch to observer which files changed and if any, trigger rsync.
Implementation
- install rsync and fswatch with the command:
brew install rsync fswatch
- create file under /usr/local/bin for every of the directory you would like to copy
#!/bin/zsh
# Define the source and destination directories
SOURCE_DIR="/Users/YOURUSERNAME/SOURCE_DIRECTORY"
DESTINATION_DIR="/Users/YOURUSERNAME/One Drive"
REPLICATIME=3600
# Function to run rsync
sync_files() {
echo "/opt/homebrew/bin/rsync -av --delete $1 $2"
[ -d $2 ] || mkdir -p $2 && /opt/homebrew/bin/rsync -a --delete $1 $2
}
# check if $1 is subdir of $2
isSubDir() {
# = is for comparint string, :P - the same as dirname; whole is checking if $1 is substring of $2
[[ $1:P/ = $2:P/* ]]
}
# Use fswatch to monitor the source directory
# fswatch run every -l X second, change it as needed (rsync is quite fast so shouldn't be an issue
# to set check every several seconds but reasonable would be minitues or hours
/opt/homebrew/bin/fswatch -0 -l $REPLICATIME -e ".*DS_Store" "$SOURCE_DIR" | while read -d "" event; do
echo $event
#remove last subdir/file in the path as rsync can operate only od dir, /* remove all after last slash
SOURCE_PARENT_PATH="${event:h}"
DESTINATION_SUFFIX=""
#remove 2 directories from dest as otherwise rsync copy directory it itself
if ! isSubDir $SOURCE_PARENT_PATH $SOURCE_DIR; then
SOURCE_PARENT_PATH=$event:P
DESTINATION_SUFFIX=$DESTINATION_DIR
fi
echo "SOURCE_PARENT_PATH: $SOURCE_PARENT_PATH"
#remove base dir from dest path, so it will take only path visible in the Finder
if [[ -z "$DESTINATION_SUFFIX" ]]; then
DESTINATION_SUFFIX="$DESTINATION_DIR${SOURCE_PARENT_PATH#$SOURCE_DIR}"
fi
echo "DESTINATION_SUFFIX: $DESTINATION_SUFFIX"
sync_files $SOURCE_PARENT_PATH/ $DESTINATION_SUFFIX/
done
- adjust directories and REPLICATIME with number of minutes of how often the rsync will be run. Test the script by running it and then creating new directory or file in your source directory
- If everything works as expected, you can can use below template to create service that will run script every system restart. Change YOUR_SCRIPT.zsh with the name of your script
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.la.copy_documents_to_onedrive2.plist</string>
<key>ProgramArguments</key>
<array>
<string>/bin/zsh</string>
<string>/usr/local/bin/YOUR_SCRIPT.zsh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardErrorPath</key>
<string>/tmp/monitor_directory.err</string>
<key>StandardOutPath</key>
<string>/tmp/monitor_directory.out</string>
</dict>
</plist>
To load service (and unload if necessary) you can use below commands:
launchctl load ~/Library/LaunchAgents/com.YOURUSERNAME_SCRIPTNAME.plist
launchctl unload ~/Library/LaunchAgents/com.YOURUSERNAME_SCRIPTNAME.plist
When loaded, check if you can see your script in the process list with ps aux | grep YOUR_SCRIPT_NAME.zsh
Remarks
- the first time the script is run, you might need to grant rsync access to your folder
- the initial run may take slightly longer, but subsequent runs should be very quick and won’t consume many resources
- OneDrive can’t handle certain characters in filenames. To rename files, you can customize the command below to suit your needs:
find . -type f -name "#\$!\@\%!#" | while read -r file; do new_name=$(echo "$file" | sed 's/#\$!\@\%!#/-/g'); echo "oldname: $file, newname: $new_name" ; done
find . -type f -name "#\$!\@\%!#" | while read -r file; do new_name=$(echo "$file" | sed 's/#\$!\@\%!#/-/g'); mv -v $file $new_name ; done
The described solution isn’t perfect, but it works quite well for me. I hope it works for you too.
No Comments