More control over iCloud Drive syncing

One might attempt to gain more control over what is being downloaded by iCloud drive to your computer (see below solutions). There is absolutely no warranty that it will work, though.

Personally I gave-up on trying to control the iCloud mechanism and just reorganised my files for optimal performance and backup capability.

I also highly recommend leaving Desktop & Documents sync disabled and only use the iCloud drive folder for the files you’d like to sync with iCloud.


The scripts below attempt to monitor the filesystem on your Mac and send the evict command to any unwanted files that your Mac is trying to download.

First you need to enable Optimise Mac Storage option on your Mac.

Solution 1 – Automator

Prevent a folder or a file in iCloud Drive from downloading to a Mac:

  1. Open Automator and create a new document Folder Action
  2. Choose a folder in your iCloud Drive (FOLDER_TO_EXCLUDE_FROM_SYNC)
  3. From Actions menu choose ‘Run Shell Script’ and add it to your workflow
  4. In the Shell Script window add the following line:
    brctl evict /Users/YOUR_USERNAME/Library/Mobile\ Documents/com\~apple\~CloudDocs/FOLDER_TO_EXCLUDE_FROM_SYNC/*

(Update YOUR_USERNAME and the FOLDER_TO_EXCLUDE_FROM_SYNC accordingly)

Solution 2 – command-line tools & lunchd

The below solution evicts each file separately rather than using a wildcard. It is very accurate and able to evict folders that contain many elements. It recurses them pretty fast and it might impact the battery life.

This solution requires an open-source command-line program fswatch to be installed in your system.

FSWATCH is available for download from GitHub or install it via Homebrew.

I wrote my main script in PHP, mostly because that’s the language I know best.

<?php
##
# watch a folder for changes and run script on elements that change
#
# unloads the files synced with iCloud Drive folder and all subfolders
#
# This program starts from Launch Daemon:
# launchctl load -w ~/Library/LaunchAgents/mattr.icloudDriveFolderEvict.plist
#

$watch_dirs= [
    '"PATH to EVICT 1"',
    '"PATH to EVICT 1"',
];

# using custom flag separator to simplify things with paths containing spaces
$proc_open= proc_open('/usr/local/bin/fswatch -0 -x --event-flag-separator -- ' . implode(' ', $watch_dirs) . ' | xargs -0 -n1 -I{}',
   [
      ["pipe","r"], // stdin 
      ["pipe","w"], // stdout
      ["pipe","w"]  // stderr
   ],
   $pipes
);

if (is_resource($proc_open)) {
   
   while ($fsw_event = fgets($pipes[1])) {
      
      //sleep(1);
      //echo $fsw_event."\n";
      
      # get the event flags from the end of string
      # reverse to find the last space in the string
      $fsw_event= strrev($fsw_event); 
      $fsw_event_cut_pos= strlen($fsw_event)-strpos($fsw_event, ' ');
      $fsw_event= strrev($fsw_event);

      $fsw_event_path = trim(substr($fsw_event, 0, $fsw_event_cut_pos));
      $fsw_event_flags = explode('--', substr($fsw_event, $fsw_event_cut_pos)); 
      
      if (file_exists($fsw_event_path) && pathinfo($fsw_event_path, PATHINFO_BASENAME) != '.DS_Store') {         
         exec('brctl evict "'.$fsw_event_path.'"');
         //echo $a."\n";
      }
   }
   fclose($pipes[1]);
   while ($fsw_event = fgets($pipes[2])) {
      echo "\n   ", ' -error---> ', $fsw_event, "\n";
   }
   fclose($pipes[2]);
   proc_close($proc_open);

} else {
      echo 'no resource';
}

And here’s my lunchd 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>uk.co.mattr.icloudDriveFolderEvict.plist</string>
	<key>ProgramArguments</key>
	<array>
		<string>/Users/maciek/mr-os-tools/icloud-watch-and-evict.sh</string>
	</array>
	<key>RunAtLoad</key>
	<true/>
	<key>KeepAlive</key>
	<true/>
	<key>LowPriorityIO</key>
	<true/>
	<key>StandardOutPath</key>
	<string>/Users/maciek/mr-os-tools/log/uk.co.mattr.icloudDriveFolderEvict.stdout.log</string>
	<key>StandardErrorPath</key>
	<string>/Users/maciek/mr-os-tools/log/uk.co.mattr.icloudDriveFolderEvict.stderr.log</string>
</dict>
</plist>

As you might have noticed the lunchd actually starts the .sh script rather than PHP script. That is just because it is hard to run PHP directly from lunchd.

My icloud-watch-and-evict.sh consists only of one line: php /Users/maciek/mr-os-tools/icloud-watch-and-evict.php

It’s not over yet…

Sometimes, somehow MacOS manages to download the files quietly, without fswatch noticing. We can detect the system cache files changes, though, and run our eviction accordingly:

<?php
##
# monitors changes in iCloud Drive system temp dirs to catch the start of sync
# and evicts items from specific iCloud Drive dirs that not supposed to be downloaded
#
# lunched with Lunch Daemon:
# launchctl load -w ~/Library/LaunchAgents/mattr.icloudDriveFolderEvict2.plist

# dirs to monitor
# you need to find your iCloud Drive cache files on your machine
$watch_dirs= "/Users/maciek/Library/Caches/CloudKit/com.apple.bird/com.apple.CloudDocs/9fadf220be21a0ed291ebfea5f836e034473cd93/MMCS";

$evict_dirs= [
    '"/Users/maciek/Library/Mobile Documents/com~apple~CloudDocs/Aretha"',
    '"/Users/maciek/Library/Mobile Documents/com~apple~CloudDocs/Wormhole"',
];

$run_end_time= time();
$wait= 30;


$proc_open= proc_open('/usr/local/bin/fswatch -0 -x "'.$watch_dirs.'" | xargs -0 -n1 -I{}',
   [
      ["pipe","r"], // stdin 
      ["pipe","w"], // stdout
      ["pipe","w"]  // stderr
   ],
   $pipes
);


if (is_resource($proc_open)) {
   
   while ($fsw_event = fgets($pipes[1])) {
      
      //echo $fsw_event."\n";
   
      if (($run_end_time + $wait) > time()) {
         //echo 'wait.';  
         sleep(1);
         continue;
      }
      //echo 'run...';
      # scanning and evicting all files found in the dirs -- expensive therfore all that throttling
      exec('find ' . implode(' ', $evict_dirs) . ' -name "*" -not -name ".DS_Store" -depth -exec brctl evict {} +');
      //echo 'complete.';
      $run_end_time= time();
   }
   fclose($pipes[1]);
   
   while ($fsw_event = fgets($pipes[2])) {
      echo "\n   ", ' -error---> ', $fsw_event, "\n";
   }
   
   fclose($pipes[2]);
   proc_close($proc_open);

} else {
      echo 'no resource';
}

This program should be started and kept alive with lunchd same way as the first one.

Conclusion

Taking control over an macOS automatic scripts from Apple such as iCloud sync is troublesome and even if we manage there is no guarantee it will work after an macOS update.

iCloud drive is a cheap way to keep your files safe but it is not very flexible.

It is usually better to make the best of what’s offered out-of-the-box without too much tinkering.

You might also like..

Add a comment