add S3 browser section to bib_explorer notebook
refs #94 New section 10 connects to the zotero S3 bucket via s3fs and lists files synced from Zotero through the WebDAV-to-S3 bridge. Shows both the bucket root and the zotero/zotero/ sync directory.
This commit is contained in:
@@ -716,6 +716,95 @@ def _(library_df, mo):
|
|||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def _(mo):
|
||||||
|
mo.md("""
|
||||||
|
## 10. Zotero WebDAV Storage (S3)
|
||||||
|
|
||||||
|
Browse files synced from Zotero to the WebDAV-backed S3 bucket.
|
||||||
|
The `webdav` service (rclone) serves the `zotero` bucket in RustFS.
|
||||||
|
""")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def _(mo):
|
||||||
|
import os
|
||||||
|
import s3fs
|
||||||
|
|
||||||
|
_endpoint = os.environ.get("S3_ENDPOINT", "http://rustfs:9000")
|
||||||
|
_key = os.environ.get("RUSTFS_ACCESS_KEY", "")
|
||||||
|
_secret = os.environ.get("RUSTFS_SECRET_KEY", "")
|
||||||
|
|
||||||
|
fs = s3fs.S3FileSystem(
|
||||||
|
key=_key,
|
||||||
|
secret=_secret,
|
||||||
|
endpoint_url=_endpoint,
|
||||||
|
client_kwargs={"region_name": "us-east-1"},
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
_files = fs.ls("zotero/", detail=True)
|
||||||
|
_rows = [
|
||||||
|
{
|
||||||
|
"path": f["Key"].removeprefix("zotero/"),
|
||||||
|
"size_kb": round(f.get("size", f.get("Size", 0)) / 1024, 1),
|
||||||
|
"modified": str(f.get("LastModified", "")),
|
||||||
|
}
|
||||||
|
for f in _files
|
||||||
|
if f.get("type", f.get("StorageClass", "")) != "directory"
|
||||||
|
]
|
||||||
|
except Exception as e:
|
||||||
|
_rows = []
|
||||||
|
mo.md(f"Could not list S3 bucket: `{e}`")
|
||||||
|
|
||||||
|
import polars as _pl
|
||||||
|
|
||||||
|
s3_df = _pl.DataFrame(_rows) if _rows else _pl.DataFrame()
|
||||||
|
|
||||||
|
if s3_df.height > 0:
|
||||||
|
mo.md(f"**{s3_df.height}** files in `s3://zotero/`")
|
||||||
|
else:
|
||||||
|
mo.md("No files in `s3://zotero/` yet. Sync from Zotero to populate.")
|
||||||
|
|
||||||
|
return (fs, s3_df)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def _(mo, s3_df):
|
||||||
|
mo.ui.table(s3_df, label="Zotero S3 Files") if s3_df.height > 0 else mo.md("")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell(hide_code=True)
|
||||||
|
def _(fs, mo):
|
||||||
|
_zotero_dir = []
|
||||||
|
try:
|
||||||
|
_zotero_dir = fs.ls("zotero/zotero/", detail=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
_sync_rows = [
|
||||||
|
{
|
||||||
|
"file": f["Key"].removeprefix("zotero/zotero/"),
|
||||||
|
"size_kb": round(f.get("size", f.get("Size", 0)) / 1024, 1),
|
||||||
|
}
|
||||||
|
for f in _zotero_dir
|
||||||
|
if f.get("type", f.get("StorageClass", "")) != "directory"
|
||||||
|
]
|
||||||
|
|
||||||
|
import polars as _pl
|
||||||
|
|
||||||
|
_sync_df = _pl.DataFrame(_sync_rows) if _sync_rows else _pl.DataFrame()
|
||||||
|
|
||||||
|
if _sync_df.height > 0:
|
||||||
|
mo.md(f"### Zotero Sync Directory\n\n**{_sync_df.height}** synced items")
|
||||||
|
mo.ui.table(_sync_df, label="Synced Files")
|
||||||
|
else:
|
||||||
|
mo.md("No synced files in `zotero/zotero/` yet.")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(mo):
|
def _(mo):
|
||||||
mo.md("""
|
mo.md("""
|
||||||
|
|||||||
Reference in New Issue
Block a user