Files
proton-stream/crates/pstr-stream/src/lib.rs
T
nvrl 0b2813cd42 feat(core,stream,meta,app,android,cli): sign in to Proton and sync watch history
A Proton account is now optional alongside public links. Signing in (password,
then a second-factor code and a mailbox password where the account has them;
a CAPTCHA gate is solved in the Android app) stores one session blob in the OS
credential store under `proton-account`, writing back every refreshed token
since refresh tokens are single-use. With an account, folders of the viewer's
own Drive — My Files, other devices, shared with me — can be added as shares
through the same `ShareClient` surface as a public link.

Watch history syncs through a `proton-stream` device in that Drive: one
`watch-history/<installation>.json` per installation, so no two devices ever
write the same file. Each sync reads the others and merges per episode, newer
`updated_at` wins, with a deterministic tie-break. It runs at launch, after
sign-in, whenever the player closes and every five minutes. A session that can
no longer refresh signs the app out instead of failing on every launch.

The CLI gains `login`, `logout`, `account`, `browse` and `sync`.

Also in this change:
- Android offline downloads no longer look stuck (B64): two transfer at once,
  through a copy stream with no read-ahead, with a smoothed transfer rate.
- Choosing a metadata match by hand now enriches the title too, instead of
  keeping the old match's episodes and artwork.

The catalog schema is unchanged.
2026-10-06 13:06:54 +02:00

46 lines
2.0 KiB
Rust

//! Seekable, cached byte streams over Proton Drive revisions.
//!
//! Proton stores a file as a list of independently-encrypted 4 MiB blocks, each
//! a self-contained PGP packet under one session key. Nothing about that is
//! sequential — which is the fact this whole crate rests on. Seeking is not an
//! approximation of streaming here; block 400 costs exactly what block 0 costs.
//!
//! What sits between the SDK and the player:
//!
//! ```text
//! mpv demuxer VideoStream BlockRing → DiskCache → Proton
//! (small, forward, → byte range → → memory disk network
//! seeking reads) blocks (128 MiB) (4 GiB)
//! ```
//!
//! * [`StreamSource`] opens files and keeps the open ones in an LRU.
//! * [`VideoStream`] answers byte ranges, reading ahead of the player.
//! * The ring and the disk cache are byte-budgeted, shared across streams, and
//! keyed by revision id so a resealed file can never serve stale bytes.
//!
//! Only [`reader`] touches the SDK. Everything else runs against a
//! [`BlockSource`], which is what makes the caching and read-ahead testable
//! without an account, a network or a share.
#![forbid(unsafe_code)]
pub mod block;
pub mod disk;
pub mod error;
pub mod reader;
pub mod ring;
mod single_flight;
pub mod source;
pub mod stream;
pub use block::{BlockMap, BlockSource, MemoryBlocks, SharedBlocks};
pub use disk::{DiskCacheConfig, DiskStats};
pub use error::{Error, Result};
/// Re-exported because every caller of this crate names one — `VideoStream::uid`
/// returns it and `StreamSource::open` takes it — and taking a dependency on the
/// whole SDK for a newtype is not a reasonable price for that.
pub use proton_sdk::ids::NodeUid;
pub use reader::{FileBlocks, LibraryOpener, RevisionBlocks};
pub use ring::{DEFAULT_RING_BYTES, RingStats};
pub use source::{RevisionOpener, StreamConfig, StreamSource};
pub use stream::{COPY_BLOCKS_IN_FLIGHT, DEFAULT_READAHEAD_BLOCKS, StreamStats, VideoStream};