Skip to content

fix: handle deep conflict resolution to preserve non-conflicting files#5873

Open
ArielLeyva wants to merge 2 commits intofilebrowser:masterfrom
ArielLeyva:#5842/fix-files-in-folder-are-always-reuploaded-even-if-Skip-all-conflicting-files-is-selected
Open

fix: handle deep conflict resolution to preserve non-conflicting files#5873
ArielLeyva wants to merge 2 commits intofilebrowser:masterfrom
ArielLeyva:#5842/fix-files-in-folder-are-always-reuploaded-even-if-Skip-all-conflicting-files-is-selected

Conversation

@ArielLeyva
Copy link
Copy Markdown
Contributor

Description

Previous conflicts were handled in the following way.
For example, considering this file and folder structure:

Upload queue:
lorem.txt
ipsun/
---- dolor.txt
---- sit.txt
acmet.txt

Existing files and folders:
lorem.txt
ipsun/
---- dolor.txt
acmet.txt

Selecting "Skip all conflicting files" should only upload the sit.txt file that doesn't exist in the destination location; however, the backend endpoint doesn't perform a "deep" check and only verifies conflicting files or folders in the root location (it returns lorem.txt, ipsun/, and acmet.txt) as conflicting files, thus emptying the upload queue (since the entire ipsun/ folder would be deleted).

Based on this behavior, there were several problems:

  1. In the conflict resolution option "Skip all conflicting files", if a conflicting directory was uploaded, the subdirectories and files in that directory were not deleted, and the overwrite option was applied to them.
  2. At some point in trying to correct this behavior, some changes were made that broke the "Decide for each conflicting file" option.

This PR changes the conflict resolution behavior to handle it as follows:

  1. The "Skip all conflicting files" option does not delete the entire conflicting directory (if there are conflicting directories), but sends a query parameter in the URL (skip=true) so that the server decides on each subdirectory and file in the conflicting directories.
  2. The "Rename conflicting files" option behaves similarly, but if there is a conflicting directory, instead of renaming all subdirectories and files within it, only the conflicting directory is renamed, and the files and subdirectories are uploaded/copied/moved to that newly renamed directory.
  3. The option to overwrite everything behaves similarly without any particular behavior.
  4. The option to decide on each file individually behaves the same way when selecting the overwrite or rename options, but in the skip option, the directory with its subdirectories and files is removed from the list of files to upload/copy/move.

Additional Information

The code for these functions was refactored to make it shorter and more understandable.
Closes #5842

Checklist

Before submitting your PR, please indicate which issues are either fixed or closed by this PR. See GitHub Help: Closing issues using keywords.

  • I am aware the project is currently in maintenance-only mode. See README
  • I am aware that translations MUST be made through Transifex and that this PR is NOT a translation update
  • I am making a PR against the master branch.
  • I am sure File Browser can be successfully built. See builds and development.

@ArielLeyva ArielLeyva requested a review from a team as a code owner March 30, 2026 03:20
kumaraguru1735 added a commit to kumaraguru1735/filebrowser-laravel that referenced this pull request Mar 31, 2026
Bug fixes:
- filebrowser#5627: Skip inaccessible subfolders in directory listing (was 500)
- filebrowser#5294: Cap text editor content at 5MB (prevents memory exhaustion DoS)
- filebrowser#5683: Allow copy/move TO root directory (was blocked)
- filebrowser#5835: Share creation requires both share AND download permissions
- filebrowser#5834: Reject negative/zero TUS Upload-Length, add 10GB sanity cap
- filebrowser#5239: Block sharing root directory (too broad, accidental exposure)
- filebrowser#5216: Validate JWT signature on token renewal
- filebrowser#2078: Support BMP thumbnails, skip GIF resize (preserve animation)
- filebrowser#5306: Normalize paths in copy/rename (strip trailing slashes)

Ported PRs:
- filebrowser#5876: Reject negative TUS upload-length
- filebrowser#5875: Check download perm when sharing
- filebrowser#5873: Deep conflict resolution — merge dirs, preserve non-conflicting
- filebrowser#5832: Optional directory sizes via ?dirSizes=true param
- filebrowser#5658: User quota management — configurable quota_resolver callback

Config additions:
- quota_resolver: callable for per-user disk quotas
- max_content_size: cap for text editor loading (default 5MB)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@geourjoa
Copy link
Copy Markdown

geourjoa commented Apr 1, 2026

Hi @ArielLeyva,

I actually worked on the same issue a few days ago and just submitted a PR for it: #5884.

It's unfortunate that I didn't see your work earlier

@daxid
Copy link
Copy Markdown

daxid commented Apr 2, 2026

@ArielLeyva Thanks for your PR. That is a pity that we worked with @geourjoa at exactly the same time!

I think that your code has performance issues when you upload a deeply nested structure like:

foo/
  bar/
    baz/
      qux/
        file.txt

The browser's file input gives you a flat list of files with relative paths. The client iterates over each file and makes an upload request (either POST or TUS) with the full destination path.

With skip=true, every single file and folder would hit the server, and the server checks if the destination already exists before writing. This means:

If bar/ doesn't exist on the server at all, then none of the nested files can possibly conflict. Yet the client would still:

  • Send foo/bar/baz/qux/file.txt with skip=true
  • Server checks existence → doesn't exist → writes it

This is fine for correctness (it works), but it's wasteful when you could short-circuit: if a parent directory doesn't exist, you know nothing inside it conflicts, so you could skip the per-file existence check and just upload everything directly.

This a big problem for our use case where hundreds or thousands of nested folders are uploaded.

If it is not to much of a burden, could you have a look to @geourjoa draft PR #5884

It implements a tree structure and a recursive function over this tree so it does not check for subfolders if it discovers that a parent does not exist on the server.

Also, it adds a "Resume previous transfert" that ignores existing files, except if they are smaller on the server. Indeed, if a big transfert is abruptly interrupter, some files will be partially uploaded and smaller on the server than they should be.

I'm not sure but it seems that Anthony's code is more concise (no changes in the backend) and yet more efficient than this proposal. I might be mistaken here and it is not at all to trash talk this contribution that is very welcome :-)

@daxid
Copy link
Copy Markdown

daxid commented Apr 2, 2026

If you are interested in seeing PR #5884 in action, we have a demo here: https://filebrowser-fix.tetras-libre.fr

Please comment here and I will send the credentials in DM.

@ArielLeyva
Copy link
Copy Markdown
Contributor Author

@ArielLeyva Thanks for your PR. That is a pity that we worked with @geourjoa at exactly the same time!

I think that your code has performance issues when you upload a deeply nested structure like:

foo/
  bar/
    baz/
      qux/
        file.txt

The browser's file input gives you a flat list of files with relative paths. The client iterates over each file and makes an upload request (either POST or TUS) with the full destination path.

With skip=true, every single file and folder would hit the server, and the server checks if the destination already exists before writing. This means:

If bar/ doesn't exist on the server at all, then none of the nested files can possibly conflict. Yet the client would still:

  • Send foo/bar/baz/qux/file.txt with skip=true
  • Server checks existence → doesn't exist → writes it

This is fine for correctness (it works), but it's wasteful when you could short-circuit: if a parent directory doesn't exist, you know nothing inside it conflicts, so you could skip the per-file existence check and just upload everything directly.

This a big problem for our use case where hundreds or thousands of nested folders are uploaded.

If it is not to much of a burden, could you have a look to @geourjoa draft PR #5884

It implements a tree structure and a recursive function over this tree so it does not check for subfolders if it discovers that a parent does not exist on the server.

Also, it adds a "Resume previous transfert" that ignores existing files, except if they are smaller on the server. Indeed, if a big transfert is abruptly interrupter, some files will be partially uploaded and smaller on the server than they should be.

I'm not sure but it seems that Anthony's code is more concise (no changes in the backend) and yet more efficient than this proposal. I might be mistaken here and it is not at all to trash talk this contribution that is very welcome :-)

Hi @daxid, I've been testing @geourjoa 's PR and I think it's excellent. I find it a better implementation than this one in terms of speed, since it's not necessary to upload all the files if you select "Skip All" or the new "Resume previous transfer" option you've implemented. I've been testing it and haven't found any bugs so far. Just a couple of notes.

It would be good if, before displaying the conflict resolution modal, instead of making multiple requests for each directory and subdirectory, a single request were made to return the information for all of them (this would make displaying the modal a bit faster). I can work on this while it's still a draft.

And the other thing is basically a minor issue: the tooltip that appears in the "Resume previous transfer" option is great, but in the mobile version, its content is difficult to see (I've shown it by long-pressing the button because if I tap it, the "Resume previous transfer" option is selected). Adding the modifier @click.stop="() => {}" would work.

Otherwise, it's great; I think the implementation is solid.

@daxid
Copy link
Copy Markdown

daxid commented Apr 3, 2026

Tank you @ArielLeyva this is great news.

Anthony will complete his PR and add your suggestions next week. We will probably have a couple of other PR on the way :-)

Go Filebrowser !

@geourjoa
Copy link
Copy Markdown

geourjoa commented Apr 7, 2026

Hi @ArielLeyva @daxid

I just pushed new code in my PR :

  • Solve a regression (when uploading a single file from UI not by drag and drop)
  • Clean some useless log
  • include your fix for the tooltip (thanks !)
  • I revert a commit from fix: Fix docker compose deployment #5878

Have you work on a single fetch ?

I'm not so sure if its worth to implement it. The PR will be bigger, more complex to review. We have all tested a lot the current implementation and we are confident on it. And last but not least it, someone must do it. 😆

What's your sentiment on it ?


EDIT : Claude has done the fetchAll impl for me, I can start testing !

EDIT 2 : So far no issue, so I make my PR ready to review #5884

@ArielLeyva
Copy link
Copy Markdown
Contributor Author

Hi @ArielLeyva @daxid

I just pushed new code in my PR :

  • Solve a regression (when uploading a single file from UI not by drag and drop)
  • Clean some useless log
  • include your fix for the tooltip (thanks !)
  • I revert a commit from fix: Fix docker compose deployment #5878

Have you work on a single fetch ?

I'm not so sure if its worth to implement it. The PR will be bigger, more complex to review. We have all tested a lot the current implementation and we are confident on it. And last but not least it, someone must do it. 😆

What's your sentiment on it ?

EDIT : Claude has done the fetchAll impl for me, I can start testing !

EDIT 2 : So far no issue, so I make my PR ready to review #5884

Excellent, @geourjoa, excellent implementation. I'll take a look and test it out. Regarding the single request improvement, that can be addressed in a future pull request once the current one is approved, keeping the review process simple.

…lways-reuploaded-even-if-Skip-all-conflicting-files-is-selected
@hacdias
Copy link
Copy Markdown
Member

hacdias commented Apr 11, 2026

I'll wait until you decide whether this PR or #5884 should be merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Files in folder are always reuploaded even if "Skip all conflicting files" is selected

4 participants