nextcloudcmd file deleted on client or created on server?

Looking at “man nextcloudcmd” it says “A sync run synchronizes a single local directory using a WebDAV share on a remote nextCloud server”

So, if a file is existing on the server but missing on the client: will it be deleted on the server or will this file be copied from the server to the client?

And, if a file is existing on the client but missing on the server: will it be deleted on the client or will this file be copied from the client to the server?

I’m runnung nextcloudcmd on a linux system, but this might not be relevant (?).

Why not simply use linux command touch to create files on client side and server side and so test what nextcloudcmd will do?

touch will simply create an empty file. In result you will lose nothing, if nextcloudcmd will delete it. But i do not expect that.

Hi @MarkusEh

Quick note on what nextcloudcmd actually is: it’s the same sync engine as the regular desktop client, just without the GUI — it runs headless, does a single sync pass, and exits. The desktop client runs permanently in the background and reacts to file changes; nextcloudcmd does the same thing once on demand, which makes it suitable for scripts and cron jobs. The official documentation is here. Because the underlying engine is identical, everything below applies to both.



The answer to your question depends entirely on one thing: the sync journal.

nextcloudcmd keeps a small SQLite database file called .sync_*.db in your local sync directory. This journal records every file that has ever been successfully synced between client and server. It is the only way the program can tell the difference between “this file is new” and “this file was deleted”.


Scenario A — File exists on the server, your local folder doesn’t have it

The program looks up the file in the journal:

  • Not in the journal (never synced before): “I have no record of this file — it must be new on the server.” → downloaded to the client.

    This is line 918–926 in discovery.cpp:

    // Unknown in db: new file on the server
    Q_ASSERT(!dbEntry.isValid());
    ...
    item->_instruction = CSYNC_INSTRUCTION_NEW;
    item->_direction = SyncFileItem::Down;
    
  • In the journal (was synced before, now missing locally): “I know this file — it existed on the client before and is now gone. It was deleted locally.” → deletion is propagated to the server.

    This is line 1307–1314 in discovery.cpp:

    // Removed locally: also remove on the server.
    item->_instruction = CSYNC_INSTRUCTION_REMOVE;
    item->_direction = SyncFileItem::Up;
    

Scenario B — File exists on your local folder, server doesn’t have it

Same logic in reverse: if the file is not in the journal, it is treated as new and uploaded. If it was in the journal (meaning the server previously had it and it disappeared), the deletion is propagated to the client.


Practical summary

Situation Journal entry? Result
File on server, not on client No Downloaded to client
File on server, not on client Yes Client deletion propagated → deleted from server
File on client, not on server No Uploaded to server
File on client, not on server Yes Server deletion propagated → deleted from client

First run (no journal exists yet): every file on both sides is treated as new and synced bidirectionally. Nothing is deleted.

Caution: if the journal exists from a previous sync but your local folder is now empty (e.g. you moved the files elsewhere), nextcloudcmd will interpret every file as “deleted locally” and delete them from the server too.


Inspecting the journal yourself

The journal is a standard SQLite database. If you want to check whether a file is tracked before running nextcloudcmd, you can read it directly (install sqlite3 if needed: apt install sqlite3).

Find the journal in your local sync folder:

ls -la /path/to/your/syncfolder/.sync_*.db

List all files currently tracked in the journal:

sqlite3 /path/to/your/syncfolder/.sync_*.db "SELECT path, modtime, filesize FROM metadata;"

Check whether a specific file is tracked:

sqlite3 /path/to/your/syncfolder/.sync_*.db "SELECT path FROM metadata WHERE path LIKE '%yourfilename%';"

If the query returns a row, the file is known to the sync engine — a missing copy on either side will be treated as a deletion. If it returns nothing, it is treated as new and will be synced over.

The three files always belong together

You may notice that the journal is not a single file but three:

.sync_3eea21c084cb.db       ← main database
.sync_3eea21c084cb.db-shm   ← Shared Memory index (32 KB, fixed size)
.sync_3eea21c084cb.db-wal   ← Write-Ahead Log

SQLite operates in WAL mode (Write-Ahead Log). Instead of writing changes directly into the main .db file, SQLite appends every write to the .db-wal file first — this is faster and safer because it avoids modifying the main database mid-write. The .db-shm file is a shared-memory index that helps multiple readers locate the right entries in the WAL without locking each other out.

These three files form a single logical database. Never copy or back up just the .db without the other two — you would get a database that is missing all changes written since the last checkpoint. Always copy all three together.

The WAL is periodically folded back into the main database in an operation called a checkpoint. SQLite triggers this automatically when the WAL reaches around 1000 pages (~4 MB). Until then, the WAL grows with every sync run. This is why the WAL file can be noticeably larger than the main database — even when the client reports everything as synced and is sitting idle. It does not indicate any problem: SQLite simply hasn’t hit the checkpoint threshold yet, or the last checkpoint left some pages behind for reader-safety reasons. The correct database state is always .db + .db-wal read together.


@adelaar’s suggestion

The touch approach is a perfectly valid practical test — create an empty file on each side, run nextcloudcmd, observe what happens, and since the files are empty you lose nothing. Highly recommended to verify behaviour on your specific setup. What the code analysis above adds is the why: knowing about the journal means you can predict the outcome in advance rather than having to test every scenario, and it explains why the same operation behaves differently depending on whether a previous sync has run or not.


I went through the sync engine source code to write this up — it’s been a long time since I last read C++ seriously, so I may have missed something. But the core logic around the journal should be correct.


h.t.h.


ernolf

Thank you for the really detailed explanation.

This is exactly what I wanted to find out.