8000 feat(shares): import files added over S3 and WebDAV by adeelahmad · Pull Request #408 · adeelahmad/lense · GitHub
[go: up one dir, main page]

Skip to content

feat(shares): import files added over S3 and WebDAV - #408

Merged
adeelahmad merged 17 commits into
mainfrom
claude/project-thread-cmjahx
Oct 17, 2026
Merged

adeelahmad merged 17 commits into
mainfrom
claude/project-thread-cmjahx

Conversation

@adeelahmad
@adeelahmad adeelahmad commented Oct 15, 2026 •
Copy link
Copy Markdown
Owner

Requested by Adeel · project thread

Description

Before: the S3 and WebDAV file shares were read-only. Copying a file into them answered AccessDenied or 403.

After: a file copied into the tree over S3 or WebDAV is imported, as a web upload is. Put it in a collection's folder and it goes to that collection; make a new folder under Collections and it's a new collection; put it anywhere else in a namespace and it goes to the default collection. rclone copy ./talks lens:pods/Collections/Talks, Finder and Windows Explorer all work, and a second rclone copy copies nothing. This also works at the web app's address (/s3, /dav, from #402) once an admin turns that on.

How: a new share_import.py places each write (namespace, collection, new collections to make), checks it like an upload, and hands the file to uploads.take(), so it gets the same type and size checks, the namespace's pipeline and fingerprint dedupe. The name the client gave stays in its folder, for that person, for 10 minutes (with the client's MD5 and mtime) so clients find what they just wrote. Files an OS or app writes for itself (.DS_Store, ._*, Thumbs.db, Office lock files, .part/.tmp) and empty files are kept aside and never imported; renaming one to a real name imports it. http_body.py streams bodies to disk with a size cap (Content-Length, chunked, and S3's aws-chunked with signed chunks and trailing checksums), and closes a refused upload gracefully so the client reads the 413 instead of a reset. S3 gets PutObject, multipart uploads, folder markers, rclone's self-copy to set times, and deletes of files kept aside. WebDAV gets PUT, MKCOL, MOVE/DELETE of files kept aside, and LOCK/UNLOCK/PROPPATCH answered as class 2, which Finder and Explorer need before they mount a share writable. Expect: 100-continue is held until the write is known to be allowed, so refused files are never sent.

Motivation and Context

Adeel asked that any file added through Lens's file protocols be imported automatically. This PR covers S3 and WebDAV. SMB and NFS follow in their own PRs.

Steps to reproduce (if appropriate):

Turn on File shares, make an access key, then rclone copy ./some-pdfs lens:<namespace>/Collections/New and look at the namespace: the collection exists and the PDFs are being processed.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change; ! in the title)

Checklist:

  • The title is a Conventional Commit.
  • My code follows the code style of this project (make hooks checks it on commit).
  • I added or updated tests: test_s3_writes.py (9), test_webdav_writes.py (6, including writes through the web app's relay), test_webdav.py updated. ci/local-ci.sh on this branch merged with main: backend, Next.js and pre-commit pass.
  • My change requires documentation updates, and I have updated docs/ accordingly (docs/file-tree.md, Adding files).
  • Users need more than the title to understand it, so I wrote it under ## Unreleased in CHANGELOG.md.

Done questions (AGENTS.md)

  • Goal: S3 and WebDAV writes import; SMB and NFS are the next two PRs on the same thread. Merged on green.
  • Agents: any S3 or WebDAV client an agent runs (rclone, boto3) adds files; GET /api/v1/shares/s3 reports add_files.
  • Zero setup: nothing new to install; it works as soon as shares are on.
  • Fewest clicks: drag files in, or one rclone copy. Collections are made from folder names.
  • Configurable: shares.add_files (on) is a switch in Settings, File shares; not in the wizard.
  • Light: no new dependency or service. Bodies stream to disk; nothing is held in memory.
  • Private and secure: same permission as a web upload (import permission or editor role on the collection), the same size cap enforced while streaming, Lens's own files can't be overwritten, imports and new collections are audited, XML bodies refuse DTDs. Through the web app only with shares.remote on as well. Threat model (P-SHARES, entry points, shares.enabled) updated in md, yaml and json.
  • Cost: no model calls beyond the namespace's normal pipeline.
  • User decides: shares stay off until an admin turns them on. add_files defaults on because importing what's added is what was asked; one switch makes the shares read-only again.
  • Upgrades: no stored-data change; the new setting is read through DEFAULTS.
  • Honest: README built list and docs/requirements.md now name the checks (and fold in feat(shares): reach S3 and WebDAV through remote access, at /s3 and /dav #402's line). Finder and Explorer flows are tested from their request sequences; rclone 1.75.1 was run for real (copy, multipart, check, re-copy, touch).
  • Tests, docs and generated files: make openapi regenerated openapi.json and the client.
  • Open standards: S3, WebDAV (RFC 4918). No new dependencies.
  • Reported in the thread; refine-later items are in docs/file-tree.md: deleting, renaming and moving assets through shares; S3 copies; read-only keys; keeping added names longer than 10 minutes and across restarts.
  • Reuse: imports go through the existing uploads.take() and the shares' existing servers, keys and file tree; no new library.

🤖 Generated with Claude Code

https://claude.ai/code/session_013sC1p4dJXjteytbxDXnk5g

A file written into the file tree over S3 or WebDAV is imported as a web
upload is (uploads.take): same type and size checks, the namespace's
pipeline, fingerprint dedupe. Where it lands decides where it goes: a
collection's folder, a new folder under Collections (a new collection),
or the default collection. It needs the import permission or an editor's
role on the collection, and never replaces a file Lens made.

S3 takes PutObject (plain, chunked, aws-chunked with signed chunks and
checksums), multipart uploads, folder markers, rclone's self-copy to set
times, and deletes of files kept aside. WebDAV takes PUT, MKCOL, MOVE and
DELETE of files kept aside, and answers LOCK/UNLOCK/PROPPATCH as class 2
so Finder and Explorer mount it writable. Expect: 100-continue is held
until the write is known to be allowed.

shares.add_files (on) turns it off from Settings, File shares.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013sC1p4dJXjteytbxDXnk5g
@adeelahmad adeelahmad self-assigned this Oct 15, 2026
…-cmjahx

# Conflicts:
#	CHANGELOG.md
#	threat-model.yaml
…-cmjahx

# Conflicts:
#	CHANGELOG.md
#	threat-model.yaml
Closing a connection with part of a refused body unread reset it, so a
client still sending (a chunked body over the size limit) got a broken
pipe instead of 413. The S3 and WebDAV servers now read and drop what
follows for up to two seconds before closing (RFC 9112, 9.6).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013sC1p4dJXjteytbxDXnk5g
adeelahmad added a commit that referenced this pull request Oct 15, 2026
…pages and notes as built (#424)

Move usage analytics (#274) and assets and people (#273) out of the
README's In progress list, and add what merged since #383: a namespace's
own MCP server, chat completions and one-namespace API keys; editing and
merging entities and topics with undo, from agents too; entity pages at
wiki addresses; Connect with Google and rclone sign-in on the server;
the notes tree, today's note, pins, search and Recently deleted; backups
that keep Lens's files; and grouped, searchable settings. In progress now
lists the open feature PRs (#398, #400, #402, #408, #419), and the docs
index links usage analytics.


Claude-Session: https://claude.ai/code/session_01HDx7PPUauyjMn1LZs8JAsS

Co-authored-by: Adeel Ahmad <noreply@anthropic.com>
…-cmjahx

# Conflicts:
#	CHANGELOG.md
#	threat-model.md
#	threat-model.yaml
…-cmjahx

# Conflicts:
#	CHANGELOG.md
#	threat-model.yaml
…-cmjahx

# Conflicts:
#	CHANGELOG.md
#	threat-model.yaml
…-cmjahx

# Conflicts:
#	docs/file-tree.md
#	fastapi_backend/app/domain/s3.py
#	fastapi_backend/app/domain/settings.py
#	fastapi_backend/app/domain/store.py
#	fastapi_backend/app/domain/webdav.py
#	threat-model.json
#	threat-model.md
#	threat-model.yaml
…ecks

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013sC1p4dJXjteytbxDXnk5g
Since #395 the entity-types upgrade step types Terms such as Monday as
Dates, which the entities list leaves out unless asked for, so the check
read them as gone. It now lists the quiet types too: an entity may change
type in an upgrade, but none may go.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013sC1p4dJXjteytbxDXnk5g
…-cmjahx

# Conflicts:
#	CHANGELOG.md
#	threat-model.yaml
@adeelahmad
adeelahmad merged commit 534d8d7 into main Oct 17, 2026
12 checks passed
@adeelahmad
adeelahmad deleted the claude/project-thread-cmjahx branch October 17, 2026 07:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0