<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>emre şahin's digital garden 🍃 - rclone</title>
    <link>https://emresahin.net/tags/rclone/</link>
    <description>Posts in the rclone tag</description>
    <language>en</language>
    <managingEditor>contact@emresahin.net (Emre Şahin)</managingEditor>
    <lastBuildDate>Tue, 15 Sep 2026 19:46:32 +0000</lastBuildDate>
    <atom:link href="https://emresahin.net/tags/rclone/rss.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>devlog 30</title>
      <published>2025-05-11T18:17:52+00:00</published>
      <updated>2025-05-11T18:17:52+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Sun, 11 May 2025 18:17:52 +0000</pubDate>
      <link>https://emresahin.net/devlog-30/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-30/</guid>
      <description>🐢 Let’s discuss how to move Xvc forward—maybe we can write a post to Reddit and the Rust forum in the meantime. 🐇 I think the next step is rclone remotes. It will allow us to use all remote storages supported by Rclone, which is a nice feature. 🐢 Don’t you think we need to publish the current ver...</description>
      <category>devlog</category>
      <category>xvc</category>
      <category>rclone</category>
      <category>rsync</category>
      <category>ecs</category>
      <category>ecs index</category>
      <category>architecture</category>
      <category>storage</category>
      <category>doctor</category>
      <content:encoded><![CDATA[<p>🐢 Let’s discuss how to move Xvc forward—maybe we can write a post to Reddit and the Rust forum in the meantime.
🐇 I think the next step is rclone remotes. It will allow us to use all remote storages supported by Rclone, which is a nice feature.
🐢 Don’t you think we need to publish the current version to Reddit and the forum?
🐇 We can do that as well.
🦊 Adding rclone remote must be a straightforward task.
🐢 We need to understand rclone paths, but overall, yes. We’ll just need to get the remote name, like <code>drive://</code>, and a path, like <code>my-xvc-storage</code>, and build paths with these.
🐇 What are the commands?
🐢 We need to learn how to upload files from local to remote and how to download these files. We can also list the files and get files as well.
🐲 How about adding a <code>paths.txt</code> to folders in remotes to show which paths the files in <code>0.jpg</code> belong to? This will change the remote cache structure a bit. We will have a reverse index of files and they will be findable.
🐢 What’s the reason for this?
🐲 When I upload a file to Drive with only the content hash, I lose track of the actual path. This is not desirable. We can add a file to the directory, called <code>paths.txt</code>, to get the paths for a file.
🐢 This may prove to be a feat, though; adding these <code>XvcPaths</code> to a file requires a lookup.
🐇 Maybe a JSON file? It might be possible to look up a path with a JSON file, and it will be easier to parse.
🐲 I don’t think the issue is about parsing, though. We can just have a plain text file that lists the paths. It’s a text file, which is the most compatible across all storages.
🐢 Storages, you mean.
🐲 Ugh, yeah. If I have a file called <code>Alan Watts</code> but I only have the content, this file will be immensely useful.
🐢 This makes <code>XvcCachePath</code> and <code>XvcPath</code> coupled. Architecture-wise, it may not be a good thing, though.
🐇 Also, there may be common storages for multiple repositories.
🐲 Umm, that’s a good point. I don’t think the architecture will be much compromised, though. We already keep the file paths and their cache paths somewhere.
🐢 Cache paths are generated from the content, but any number of paths can point to a single path in the cache. If I have 1 million copies of the same file, will I add all these files to the <code>paths.txt</code> you mentioned?
🐲 That’s a good point too. We can have a limit, like 1,000 or something, not to make these files too big.
🐢 Instead of this, we can store the output of <code>xvc file list</code> at the storage root and allow looking up the files that way.
🐲 It has the same problem, though; if we have a million files, their list will be too large.
🐢 There can be a manual command, like <code>xvc file index --to storage</code>, that will show content hashes and paths of each file. We can also add URLs to files if possible.
🐲 No one will use it when it’s manual, though.
🐢 We can add functionality to update this index when we send a file, though.<br>🐲 So, after each send, we’ll update the index for the repository on that storage. Is that correct?
🐢 Not after each send. After each send session, maybe.
🐇 We can have an incremental way of updating the index, like we do in ECS?
🐢 It will be overkill for this functionality and add too much noise to the storage.
🐲 Let’s keep this discussion here, but I also want to have an index merge or index cleanup mechanism for the entity generator and the ECS.
🐢 We can have a “merge indices” functionality in ECS. That will remove all older entity-generator files and merge all store files.
🐇 Removing older entity files is easy, but what about merging the store files?
🐢 It’s easy too. We’ll just load all event logs from the directory, remove all other files, and save the event log to a file.
🐇 Will this be manual or automatic?
🐢 I think the first version can be manual, something like <code>xvc fsck merge-store-files</code> or something like that. We can notify the user if the number of files is &gt; 10,000 or something like that. I don’t think we need to make it automatic unless we measure the impact of these files. There is no point in trying to do it at every command.
🐇 Then we’ll have two new commands for the next version?
🐢 I think we can just add rclone remote now and release it, then make changes in the ECS for this new <code>xvc fsck</code> command.
🐇 Can the name be <code>doctor</code> or something? Or <code>util</code>? Or can we add a top-level <code>merge indices</code> command?
🐢 <code>xvc doctor</code> seems like a better alternative. We can have a <code>diagnose</code> subcommand as well to check for possible inconsistencies. <code>xvc doctor merge-store-files</code> is a better command.
🐲 Will we use <code>d</code> for this command?
🐢 No need to add a single-letter command for this, I believe. It shouldn’t be required to run frequently.
🐇 Hmm, ok. What do we need to know for rclone remote?
🐲 I noticed we don’t have the <code>xvc storage remove</code> command implemented yet. Maybe we can start from that.
🐢 Hmm, yeap. Let’s start by implementing that first. We can add the rclone command next.
🐇 Will we use a feature flag for rclone? It will run the command only with an external binary.
🐢 It’s better to have a feature flag. I think we can add a feature flag for rsync remote as well.
🐇 We can use the generic one to update the feature flag.
🐢 I think the only two items of information we need for rclone are the remote name and the remote directory. Will we make these required?</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 29</title>
      <published>2025-04-24T03:29:07+00:00</published>
      <updated>2025-04-24T03:29:07+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 24 Apr 2025 03:29:07 +0000</pubDate>
      <link>https://emresahin.net/devlog-29/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-29/</guid>
      <description>🐢 As the new version is updated, we can go back to the project. What’s the next step? 🐇 We can continue working on rclone. 🐢 Umm, ok. Let’s try to focus on adding another storage type. ✅ #🌻 ADD rclone storage type (2025-04-24 06:27) 🐢 I think the first option is to run the commands from the comma...</description>
      <category>XVC</category>
      <category>Storage</category>
      <category>rclone</category>
      <category>xvc storage new generic</category>
      <category>rclone alias</category>
      <category>Rust</category>
      <category>Development</category>
      <content:encoded><![CDATA[<p>🐢 As the new version is updated, we can go back to the project. What’s the next step?</p>
<p>🐇 We can continue working on rclone.</p>
<p>🐢 Umm, ok. Let’s try to focus on adding another storage type.</p>
<ul>
<li>✅ #🌻 ADD rclone storage type (2025-04-24 06:27)</li>
</ul>
<p>🐢 I think the first option is to run the commands from the command line. We can just use a modified generic storage type without trying to make it fast.</p>
<p>🐇 Let’s make it run first, you say?</p>
<p>🐢 Yes, let’s make it run first and then we can think about making it run fast.</p>
<p>🐇 You’re right!</p>
<ul>
<li>✅ #🌻 ADD generic rclone tests (2025-04-24 06:27)</li>
</ul>
<p>It’s possible to use the <a href="https://www.reddit.com/r/rclone/comments/qu5l5k/how_to_specify_local_directory_as_a_remote/">alias</a> remote with a local path.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 28</title>
      <published>2025-04-24T03:20:52+00:00</published>
      <updated>2025-04-24T03:20:52+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 24 Apr 2025 03:20:52 +0000</pubDate>
      <link>https://emresahin.net/devlog-28/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-28/</guid>
      <description>🐢 We can start adding an rclone remote as well. 🐢 Created the PR, waiting for the tests. 🦊 Let’s search if there is a crate to manage rclone. Maybe it will be easier that way. 🐇 It will add another dependency though. 🦊 We can always add a feature flag for this. 🐢 There is a librclone crate that c...</description>
      <category>XVC</category>
      <category>Storage</category>
      <category>rclone</category>
      <category>librclone</category>
      <category>Rust</category>
      <category>celeste</category>
      <category>cloud-storage</category>
      <content:encoded><![CDATA[<p>🐢 We can start adding an rclone remote as well.</p>
<p>🐢 Created the PR, waiting for the tests.</p>
<p>🦊 Let’s search if there is a crate to manage rclone. Maybe it will be easier that way.</p>
<p>🐇 It will add another dependency though.</p>
<p>🦊 We can always add a feature flag for this.</p>
<p>🐢 There is a <a href="https://github.com/trevyn/librclone">librclone</a> crate that can be used to call rclone commands like https://rclone.org/rc/#supported-commands</p>
<p>🦊 We can test it from the command line perhaps.</p>
<pre><code class="language-bash">rclone rc 
2025/03/15 17:41:09 NOTICE: Failed to rc: failed to list: connection failed: Post "http://localhost:5572/rc/list": dial tcp [::1]:5572: connect: connection refused
</code></pre>
<p>🐇 It requires the backend to be running in the background.</p>
<p>🦊 There may be examples in the repository.</p>
<p>🐢 There are none. We can search GH for this crate though.</p>
<p>🦊 It’s also possible to search for dependents in crates.io.</p>
<p>🐢 I think Xvc will be the first dependent of this crate: https://crates.io/crates/librclone/reverse_dependencies</p>
<p>🐇 The following two projects depend on librclone:</p>
<ul>
<li>https://github.com/Sh3mm/WarpDrive/tree/master</li>
<li>https://github.com/hwittenborn/celeste</li>
</ul>
<p>🐢 Let’s clone Celeste. It uses librclone and looks like it’s a user interface for rclone written in Rust.</p>
<p>🐇 The examples are in <code>celeste/src/rclone.rs</code>.</p>
<p>🐢 Cool. Let’s take a look at how commands are run:</p>
<pre><code class="language-rust">    /// Common function for some of the below command.
    fn common(command: &amp;str, remote_name: &amp;str, path: &amp;str) -&gt; Result&lt;(), RcloneError&gt; {
        let resp = run(
            command,
            &amp;json!({
                "fs": get_remote_name(remote_name),
                "remote": util::strip_slashes(path),
            })
            .to_string(),
        );

        match resp {
            Ok(_) =&gt; Ok(()),
            Err(json_str) =&gt; Err(serde_json::from_str(&amp;json_str).unwrap()),
        }
    }</code></pre>
<p>All commands are run like <code>librclone::rpc(method, input))</code> and the commands are like:</p>
<pre><code class="language-rust">    /// make a directory on the remote.
    pub fn mkdir(remote_name: &amp;str, path: &amp;str) -&gt; Result&lt;(), RcloneError&gt; {
        common("operations/mkdir", remote_name, path)
    }</code></pre>
<p>🐇 We have all commands in this file that are relevant to Xvc. Let’s list them here:</p>
<ul>
<li>make directory: <code>common("operations/mkdir", remote_name, path)</code></li>
<li>delete file: <code>common("operations/delete", remote_name, path)</code></li>
<li>remove a dir and all of its contents: <code>common("operations/purge", remote_name, path)</code></li>
<li>copy file:</li>
</ul>
<pre><code class="language-rust">run( "operations/copyfile",
            &amp;json!({
                "srcFs": src_fs,
                "srcRemote": util::strip_slashes(src_remote),
                "dstFs": dst_fs,
                "dstRemote": util::strip_slashes(dst_remote)
            })</code></pre>
<p>and</p>
<pre><code class="language-rust">
    /// Copy a file from the local machine to the remote.
    pub fn copy_to_remote(
        local_file: &amp;str,
        remote_name: &amp;str,
        remote_destination: &amp;str,
    ) -&gt; Result&lt;(), RcloneError&gt; {
        copy(
            "/",
            local_file,
            &amp;get_remote_name(remote_name),
            remote_destination,
        )
    }

    /// Copy a file from the remote to the local machine.
    pub fn copy_to_local(
        local_destination: &amp;str,
        remote_name: &amp;str,
        remote_file: &amp;str,
    ) -&gt; Result&lt;(), RcloneError&gt; {
        copy(
            &amp;get_remote_name(remote_name),
            remote_file,
            "/",
            local_destination,
        )
    }</code></pre>
<p>🐇 It looks like that’s all we need. We can organize the commands differently, but these examples are enough to use <code>librclone</code>. It seems rather straightforward.</p>]]></content:encoded>
    </item>
    <item>
      <title>Differences between DVC and Xvc</title>
      <published>2024-07-17T09:57:44+00:00</published>
      <updated>2024-07-17T09:57:44+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Wed, 17 Jul 2024 09:57:44 +0000</pubDate>
      <link>https://emresahin.net/differences-between-dvc-and-xvc/</link>
      <guid isPermaLink="true">https://emresahin.net/differences-between-dvc-and-xvc/</guid>
      <description>I wrote this on Reddit; let’s put it here too. Full list of similarities and differences is rather long. Let me summarize it. Xvc has different commands; xvc file track is used instead of dvc add . Xvc doesn’t add files (like .dvc files) to your repository and keeps all metadata tracking under th...</description>
      <category>xvc</category>
      <category>MLOps</category>
      <category>xvc</category>
      <category>dvc</category>
      <category>mlops</category>
      <category>data-versioning</category>
      <category>rclone</category>
      <category>s5cmd</category>
      <category>data-science</category>
      <content:encoded><![CDATA[<p>I wrote <a href="https://www.reddit.com/r/mlops/comments/1e4vvv3/xvc_a_free_as_in_freedom_cli_and_python_tool_to/">this</a> on Reddit; let’s put it here too.</p>
<p><a href="https://docs.xvc.dev/start/from-dvc">Full list of similarities and differences</a> is rather long. Let me summarize it.</p>
<p>Xvc has different commands; <code>xvc file track</code> is used instead of <code>dvc add</code>. Xvc doesn’t add files (like <code>.dvc</code> files) to your repository and keeps all metadata tracking under the <code>.xvc</code> directory. <a href="https://docs.xvc.dev/ref/xvc-file-recheck"><em>Checkout method</em></a> is per-file, not configured globally, so you can keep track of your data directory with symlinks and your model directory as copies. Xvc uses BLAKE3 as the default hashing algorithm, and you can configure this to be BLAKE2, SHA-2, or SHA-3.</p>
<p>Pipelines are not defined using YAML files. You can write a shell script with <code>xvc pipeline step ...</code> or use Python <code>xvc.pipeline().step().dependency(step_name="preprocess", param="hyperparams.yaml::batch_size")</code> to define pipelines first. Then you can use <code>xvc pipeline export</code> and <code>xvc pipeline import</code> to modify the pipeline in YAML, JSON, or TOML.</p>
<p>There are <a href="https://docs.xvc.dev/ref/xvc-pipeline-step-dependency">more dependency options</a>; e.g., a pipeline step may depend on a text file partially, by <a href="https://docs.xvc.dev/ref/xvc-pipeline-step-dependency#regex-item-dependencies">regex</a> or by the <a href="https://docs.xvc.dev/ref/xvc-pipeline-step-dependency#line-item-dependencies">line</a> options. There is a <a href="https://docs.xvc.dev/ref/xvc-pipeline-step-dependency#generic-command-dependencies">generic dependency</a> option; the output of a shell command can be used as a dependency to a step.</p>
<p><a href="https://docs.xvc.dev/ref/xvc-storage-new">Remote storage options</a> are rather limited for Xvc; local, ssh+rsync, and S3-compatible storages are supported for now. There is also a <code>generic</code> storage option where you can define upload and download commands for the tool you’re using, e.g., <code>rclone</code> or <code>s5cmd</code>, and Xvc can use it. I’ll add Azure and rclone as natively supported storage options eventually, but I don’t like the idea of keeping credentials, so there won’t be any OAuth-required storage options, e.g., Google Drive. (You’ll be able to use these through rclone, though.) All Xvc storages use environment variables for authentication.</p>
<p>Xvc doesn’t have experiment tracking yet. <a href="https://docs.xvc.dev/how-to/git-branches">You can use <code>--from-ref</code> and <code>--to-branch</code> options</a> to store artifacts from the pipeline to different branches. I’ll add features to run pipelines and commands quickly and compare these eventually (I need one too), but it may take some time.</p>
<p>Xvc doesn’t track anything about the user. It shouldn’t make any network connections if you don’t specifically ask it to do so. I’m planning to add binaries that only do file operations or pipeline operations, so if someone doesn’t need pipeline features, they will simply use <code>xvc-file</code>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Deleting duplicate files in Google Drive using rclone</title>
      <published>2021-01-06T21:31:08+00:00</published>
      <updated>2022-08-06T00:00:00+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Wed, 06 Jan 2021 21:31:08 +0000</pubDate>
      <link>https://emresahin.net/rclone-google-drive-deletion/</link>
      <guid isPermaLink="true">https://emresahin.net/rclone-google-drive-deletion/</guid>
      <description>I’m a Google One user, and my Google Drive has about 1TB of content from various sources. A few years ago, I used a third-party utility to sync my Linux boxes to Drive, which created many duplicate files. I had around 3-4 different versions of some directories with different sets of files. As a t...</description>
      <category>Scripts</category>
      <category>Cloud Storage</category>
      <category>drive</category>
      <category>duplicates</category>
      <category>rclone</category>
      <category>google-drive</category>
      <category>automation</category>
      <category>zsh</category>
      <content:encoded><![CDATA[<p>I’m a Google One user, and my Google Drive has about 1TB of content from various sources.
A few years ago, I used a third-party utility to sync my Linux boxes to Drive, which created many duplicate files.
I had around 3-4 different versions of some directories with different sets of files.
As a true procrastinator, I postponed addressing the problem until Google One alerted me about my quota.</p>
<p>Nowadays, I’m using <a href="https://rclone.org/"><code>rclone</code></a>.
It has become my primary way of using Drive on Linux after that half-baked sync application.
I discovered that <code>rclone</code> supports server-side MD5 hashes for files.
I decided to write a script to delete the duplicate files in Drive.</p>
<p>First, I obtain the MD5 hashes of all files in Drive using:</p>
<pre><code>$ rclone md5hash drive:/ &gt; $HOME/Google-Drive-md5-$(date +%F).txt
</code></pre>
<p>This may take some time depending on the number of files, but it finished more quickly than I expected.</p>
<p>The file contents look like this:</p>
<pre><code>39044094333de4a47d7478227cfa22a9  Facebin/vgg-clean/clea_duvall/00000501.jpg
398d44f0b32d1b4855e838754c2c49fc  Facebin/vgg-clean/Bob_Barker/00000445.jpg
c2090f9412b93d71fed884bb45b26518  Facebin/vgg-clean/Adam_Goldberg/00000432.jpg
cd6971f809a534ab02ee1b03eb6c1183  CHECK Uploads/Google Photos/2017/06/IMG_1461.JPG
1fb80cc9ef622410557cb42c4abf26a8  Facebin/vgg-clean/Angell_Conwell/00000510.jpg
160793fd8f24fcf27efb9a2b3698a9c8  Facebin/makimface-artifacts/dataset-images/user-test-v4/00056--/img-5c1fdc5d4fd82c1e12a7d49937d7f47b.png
bcddf6ebbc1eed83950d908c2ccbac4a  Facebin/vgg-clean/danny_pino/00000102.jpg
eb2746a9559e7a93fd0002f4bfd90517  Facebin/makimface-artifacts/dataset-images/user-test/00009--/ds-767cd16bfdf4e31860d597708a586979.png
9605d69aaf5fb83d309f10f9e2544630  Facebin/vgg-clean/Adam_Beach/00000953.jpg
68e6d3ec3cca8a728a78fb60c9a1ddba  Facebin/vgg-clean/corey_stoll/00000137.jpg
</code></pre>
<p>The first 32 characters are the MD5 hash of the file, and the rest of the line is the file path.</p>
<p>You’ll probably see some blanks for the MD5 hashes of certain files.
<strong>It’s important</strong> to remove these from the list, as these are your Google Docs, spreadsheets, etc.</p>
<pre><code>$ grep '[^0-9a-f]' $HOME/Google-Drive-md5-$(date +%F).txt &gt; $HOME/Google-Drive-md5-cleaned.txt
</code></pre>
<p>Next, we sort the file using:</p>
<pre><code>$ sort $HOME/Google-Drive-md5-cleaned.txt &gt; $HOME/Google-Drive-md5-sorted.txt
</code></pre>
<p>We keep all intermediate files because you might want to review the differences after the cleanup.</p>
<p>Then, we write the following script and run it:</p>
<pre><code class="language-zsh">#!/bin/zsh

PREV_MD5=""
PREV_PATH=""
CURRENT_MD5=""
CURRENT_PATH=""
MD5_FILE=$HOME/Google-Drive-md5-sorted.txt
cat $MD5_FILE | while read current_line ; do
    # echo $current_line
    CURRENT_MD5=$(echo "$current_line" | cut -c -32)
    CURRENT_PATH=$(echo "$current_line" | cut -c 35-)
    # echo $CURRENT_MD5
    # echo "$CURRENT_PATH"
    if [[ "$CURRENT_MD5" == "$PREV_MD5" ]] ; then
        echo "EQUAL: $CURRENT_MD5 $PREV_MD5"
        echo "DELETE: drive:/$CURRENT_PATH"
        rclone -v delete "drive:/$CURRENT_PATH"
    else
        PREV_MD5=$CURRENT_MD5
        PREV_PATH=$CURRENT_PATH
    fi
done
</code></pre>
<p>You can save this script as <code>google-drive-delete-duplicates.sh</code> and run:</p>
<pre><code>$ chmod +x google-drive-delete-duplicates.sh
$ ./google-drive-delete-duplicates.sh
</code></pre>
<p>The script checks each line one by one; if two duplicate MD5 hashes are found consecutively, the second file is deleted.
It keeps only one of the duplicates, even if there are more than two copies.</p>
<p>I have gained about 200GB by running this script.</p>]]></content:encoded>
    </item>
  </channel>
</rss>
