mirror of
https://github.com/mikedilger/gossip.git
synced 2024-09-29 08:21:47 +00:00
Merge branch 'unstable'
This commit is contained in:
commit
190db64dd3
210
Cargo.lock
generated
210
Cargo.lock
generated
@ -198,6 +198,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
|
||||
|
||||
[[package]]
|
||||
name = "arboard"
|
||||
version = "3.2.0"
|
||||
@ -421,6 +427,26 @@ version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.64.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"peeking_take_while",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.2"
|
||||
@ -593,6 +619,15 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
@ -651,8 +686,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"time 0.1.45",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@ -667,6 +705,17 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "4.5.0"
|
||||
@ -678,6 +727,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cocoa"
|
||||
version = "0.24.1"
|
||||
@ -1150,6 +1208,23 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui-video"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/n00kii/egui-video?rev=50b4efd6d8250b555a85f966deca737c161ee6df#50b4efd6d8250b555a85f966deca737c161ee6df"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"egui",
|
||||
"ffmpeg-next",
|
||||
"itertools",
|
||||
"parking_lot",
|
||||
"ringbuf",
|
||||
"sdl2",
|
||||
"tempfile",
|
||||
"timer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui-winit"
|
||||
version = "0.21.1"
|
||||
@ -1380,6 +1455,30 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ffmpeg-next"
|
||||
version = "6.0.0"
|
||||
source = "git+https://github.com/bu5hm4nn/rust-ffmpeg?rev=a08d9f86c2d2fb05fcf6ceb65ae68ef788964f18#a08d9f86c2d2fb05fcf6ceb65ae68ef788964f18"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"ffmpeg-sys-next",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ffmpeg-sys-next"
|
||||
version = "6.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf650f461ccf130f4eef4927affed703cc387b183bfc4a7dfee86a076c131127"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"libc",
|
||||
"num_cpus",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.25"
|
||||
@ -1573,7 +1672,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@ -1598,6 +1697,12 @@ dependencies = [
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "glow"
|
||||
version = "0.12.1"
|
||||
@ -1684,6 +1789,7 @@ dependencies = [
|
||||
"dashmap",
|
||||
"dirs 5.0.0",
|
||||
"eframe",
|
||||
"egui-video",
|
||||
"egui-winit",
|
||||
"egui_extras",
|
||||
"encoding_rs",
|
||||
@ -1706,15 +1812,17 @@ dependencies = [
|
||||
"regex",
|
||||
"reqwest",
|
||||
"rusqlite",
|
||||
"sdl2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"tungstenite",
|
||||
"url",
|
||||
"vecmap-rs",
|
||||
"zeroize",
|
||||
]
|
||||
@ -2029,6 +2137,15 @@ version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.6"
|
||||
@ -2119,6 +2236,12 @@ version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "lebe"
|
||||
version = "0.5.2"
|
||||
@ -2332,7 +2455,7 @@ checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
@ -2755,6 +2878,12 @@ dependencies = [
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
@ -3124,6 +3253,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ringbuf"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79abed428d1fd2a128201cec72c5f6938e2da607c6f3745f769fabea399d950a"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ron"
|
||||
version = "0.8.0"
|
||||
@ -3160,6 +3298,12 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
@ -3290,6 +3434,28 @@ dependencies = [
|
||||
"tiny-skia",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdl2"
|
||||
version = "0.35.2"
|
||||
source = "git+https://github.com/Rust-SDL2/rust-sdl2?rev=27cd1fd67c811e06b9d997a77bb6089a1b65070d#27cd1fd67c811e06b9d997a77bb6089a1b65070d"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"sdl2-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdl2-sys"
|
||||
version = "0.35.2"
|
||||
source = "git+https://github.com/Rust-SDL2/rust-sdl2?rev=27cd1fd67c811e06b9d997a77bb6089a1b65070d#27cd1fd67c811e06b9d997a77bb6089a1b65070d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cmake",
|
||||
"libc",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sec1"
|
||||
version = "0.7.2"
|
||||
@ -3418,6 +3584,12 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
@ -3667,6 +3839,17 @@ dependencies = [
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.20"
|
||||
@ -3694,6 +3877,15 @@ dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "timer"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31d42176308937165701f50638db1c31586f183f1aab416268216577aec7306b"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia"
|
||||
version = "0.8.4"
|
||||
@ -4056,6 +4248,12 @@ version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfd18cb0279ed472822644fed8a86507fa8578241d4e047eddb3129591350cc0"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
@ -4088,6 +4286,12 @@ dependencies = [
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
15
Cargo.toml
15
Cargo.toml
@ -12,10 +12,22 @@ edition = "2021"
|
||||
[features]
|
||||
default = ["rustls-tls"]
|
||||
lang-cjk = []
|
||||
video-ffmpeg = [ "egui-video", "sdl2" ]
|
||||
|
||||
native-tls = [ "reqwest/native-tls", "tungstenite/native-tls", "tokio-tungstenite/native-tls"]
|
||||
rustls-tls = [ "reqwest/rustls-tls", "tungstenite/rustls-tls-webpki-roots", "tokio-tungstenite/rustls-tls-webpki-roots"]
|
||||
|
||||
[patch.crates-io]
|
||||
# override egui crate for egui_video dependency
|
||||
egui = { git = "https://github.com/mikedilger/egui", rev = "a18ee5e5867afb7c80c8cd0bc2815278274124b3" }
|
||||
# Use the master branch of SDL2 to include a fix related to clang (and XCode after 14.2)
|
||||
sdl2 = { git = "https://github.com/Rust-SDL2/rust-sdl2", rev = "27cd1fd67c811e06b9d997a77bb6089a1b65070d" }
|
||||
|
||||
[patch.'https://github.com/n00kii/rust-ffmpeg']
|
||||
# Use upstream which is newer
|
||||
ffmpeg-next = { git = "https://github.com/bu5hm4nn/rust-ffmpeg", rev = "a08d9f86c2d2fb05fcf6ceb65ae68ef788964f18" }
|
||||
|
||||
|
||||
[dependencies]
|
||||
async-recursion = "1.0"
|
||||
async-trait = "0.1"
|
||||
@ -25,6 +37,7 @@ dirs = "5.0"
|
||||
eframe = { git = "https://github.com/mikedilger/egui", rev = "a18ee5e5867afb7c80c8cd0bc2815278274124b3", features = [ "persistence" ] }
|
||||
egui-winit = { git = "https://github.com/mikedilger/egui", rev = "a18ee5e5867afb7c80c8cd0bc2815278274124b3", features = [ "default" ] }
|
||||
egui_extras = { git = "https://github.com/mikedilger/egui", rev = "a18ee5e5867afb7c80c8cd0bc2815278274124b3", features = [ "image", "svg" ] }
|
||||
egui-video = { git = "https://github.com/n00kii/egui-video", rev = "50b4efd6d8250b555a85f966deca737c161ee6df", features = [ "from_bytes" ], optional = true }
|
||||
encoding_rs = "0.8"
|
||||
fallible-iterator = "0.2"
|
||||
futures = "0.3"
|
||||
@ -45,6 +58,7 @@ rand = "0.8"
|
||||
regex = "1.8"
|
||||
reqwest = { version = "0.11", default-features=false, features = ["brotli", "deflate", "gzip", "json"] }
|
||||
rusqlite = { version = "0.29", features = ["bundled", "chrono", "serde_json"] }
|
||||
sdl2 = { version = "0.35.2", features = ["bundled"], optional = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
sha2 = "0.10"
|
||||
@ -54,6 +68,7 @@ tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = [ "std", "env-filter" ] }
|
||||
tokio-tungstenite = { version = "0.18", default-features = false, features = [ "connect", "handshake" ] }
|
||||
tungstenite = { version = "0.18", default-features = false }
|
||||
url = "2.3"
|
||||
vecmap-rs = "0.1"
|
||||
zeroize = "1.6"
|
||||
|
||||
|
26
README.md
26
README.md
@ -117,6 +117,16 @@ Most dependencies are probably already installed in your base operating system.
|
||||
- openssl (debian: "libssl-dev")
|
||||
- fontconfig (debian: "libfontconfig1-dev")
|
||||
|
||||
#### macOS
|
||||
|
||||
a. Install rust with rust-up: https://rustup.rs/
|
||||
b. Install homebrew if you don't have it yet https://brew.sh/
|
||||
c. Install these dependencies:
|
||||
|
||||
```
|
||||
brew install cmake sdl2 pkg-config ffmpeg
|
||||
```
|
||||
|
||||
### Step 3 - Clone this Repository
|
||||
|
||||
````bash
|
||||
@ -189,15 +199,25 @@ Gossip by default does not include the CJK font because it is larger than all ot
|
||||
|
||||
There are so many of these (172) that it becomes a real pain to add them all. But if you need one, please ask (open an issue) and I'll add it for you.
|
||||
|
||||
### Known Issues
|
||||
### Video Playback
|
||||
|
||||
#### Sqlite Constraint Issues (Foreign or Unique Key)
|
||||
You will need to install sdl2 (follow the instructions in the [readme](https://github.com/Rust-SDL2/rust-sdl2/)) and ffmpeg on your system.
|
||||
|
||||
Compile with
|
||||
|
||||
````
|
||||
--features=video-ffmpeg
|
||||
````
|
||||
|
||||
## Known Issues
|
||||
|
||||
### Sqlite Constraint Issues (Foreign or Unique Key)
|
||||
|
||||
First you need to locate your database file. The gossip directory is under this path: https://docs.rs/dirs/4.0.0/dirs/fn.data_dir.html The database file is `gossip.sqlite`. Then you need to install `sqlite3` on your system.
|
||||
|
||||
Using `sqlite3` on your database file, the following kind of SQL can help you identify rows that violate foreign key constraints.
|
||||
|
||||
##### Error: Sql(SqliteFailure(Error { code: ConstraintViolation, extended_code: 2067 }, Some("UNIQUE constraint failed: person_relay.person, person_relay.relay")))
|
||||
#### Error: Sql(SqliteFailure(Error { code: ConstraintViolation, extended_code: 2067 }, Some("UNIQUE constraint failed: person_relay.person, person_relay.relay")))
|
||||
|
||||
You can find which rows are duplicated using: `select a.person, a.relay FROM person_relay a INNER JOIN person_relay b WHERE a.person=b.person AND a.relay=b.relay AND a.rowid!=b.rowid;` You'll need to delete one row from each pair (by rowid so you don't delete both of them).
|
||||
|
||||
|
2
run.sh
2
run.sh
@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
RUSTFLAGS="-C target-cpu=native --cfg tokio_unstable" cargo build --release && \
|
||||
RUSTFLAGS="-C target-cpu=native --cfg tokio_unstable" cargo build --features=lang-cjk,video-ffmpeg --release && \
|
||||
RUST_BACKTRACE=1 RUST_LOG="info,gossip=debug" ./target/release/gossip
|
||||
|
||||
|
64
src/media.rs
64
src/media.rs
@ -12,7 +12,8 @@ pub struct Media {
|
||||
// until the UI next asks for them, at which point we remove them
|
||||
// and hand them over. This way we can do the work that takes
|
||||
// longer and the UI can do as little work as possible.
|
||||
media_temp: DashMap<Url, ColorImage>,
|
||||
image_temp: DashMap<Url, ColorImage>,
|
||||
data_temp: DashMap<Url, Vec<u8>>,
|
||||
media_pending_processing: DashSet<Url>,
|
||||
failed_media: RwLock<HashSet<UncheckedUrl>>,
|
||||
}
|
||||
@ -20,7 +21,8 @@ pub struct Media {
|
||||
impl Media {
|
||||
pub fn new() -> Media {
|
||||
Media {
|
||||
media_temp: DashMap::new(),
|
||||
image_temp: DashMap::new(),
|
||||
data_temp: DashMap::new(),
|
||||
media_pending_processing: DashSet::new(),
|
||||
failed_media: RwLock::new(HashSet::new()),
|
||||
}
|
||||
@ -33,7 +35,6 @@ impl Media {
|
||||
Err(_) => {
|
||||
// this cannot recover without new metadata
|
||||
self.failed_media.blocking_write().insert(unchecked_url);
|
||||
|
||||
return None;
|
||||
}
|
||||
};
|
||||
@ -48,18 +49,9 @@ impl Media {
|
||||
self.failed_media.blocking_write().remove(unchecked_url);
|
||||
}
|
||||
|
||||
pub fn get_media(&self, url: &Url) -> Option<ColorImage> {
|
||||
// If it failed before, error out now
|
||||
if self
|
||||
.failed_media
|
||||
.blocking_read()
|
||||
.contains(&url.to_unchecked_url())
|
||||
{
|
||||
return None; // cannot recover.
|
||||
}
|
||||
|
||||
pub fn get_image(&self, url: &Url) -> Option<ColorImage> {
|
||||
// If we have it, hand it over (we won't need a copy anymore)
|
||||
if let Some(th) = self.media_temp.remove(url) {
|
||||
if let Some(th) = self.image_temp.remove(url) {
|
||||
return Some(th.1);
|
||||
}
|
||||
|
||||
@ -68,14 +60,8 @@ impl Media {
|
||||
return None; // will recover after processing completes
|
||||
}
|
||||
|
||||
// Do not fetch if disabled
|
||||
if !GLOBALS.settings.read().load_media {
|
||||
return None; // can recover if the setting is switched
|
||||
}
|
||||
|
||||
match GLOBALS.fetcher.try_get(url.clone()) {
|
||||
Ok(None) => None,
|
||||
Ok(Some(bytes)) => {
|
||||
match self.get_data(url) {
|
||||
Some(bytes) => {
|
||||
// Finish this later (spawn)
|
||||
let aurl = url.to_owned();
|
||||
tokio::spawn(async move {
|
||||
@ -85,12 +71,12 @@ impl Media {
|
||||
.load(Ordering::Relaxed)
|
||||
/ 100;
|
||||
if let Ok(color_image) = egui_extras::image::load_image_bytes(&bytes) {
|
||||
GLOBALS.media.media_temp.insert(aurl, color_image);
|
||||
GLOBALS.media.image_temp.insert(aurl, color_image);
|
||||
} else if let Ok(color_image) = egui_extras::image::load_svg_bytes_with_size(
|
||||
&bytes,
|
||||
FitTo::Size(size, size),
|
||||
) {
|
||||
GLOBALS.media.media_temp.insert(aurl, color_image);
|
||||
GLOBALS.media.image_temp.insert(aurl, color_image);
|
||||
} else {
|
||||
// this cannot recover without new metadata
|
||||
GLOBALS
|
||||
@ -104,6 +90,36 @@ impl Media {
|
||||
self.media_pending_processing.insert(url.clone());
|
||||
None
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_data(&self, url: &Url) -> Option<Vec<u8>> {
|
||||
// If it failed before, error out now
|
||||
if self
|
||||
.failed_media
|
||||
.blocking_read()
|
||||
.contains(&url.to_unchecked_url())
|
||||
{
|
||||
return None; // cannot recover.
|
||||
}
|
||||
|
||||
// If we have it, hand it over (we won't need a copy anymore)
|
||||
if let Some(th) = self.data_temp.remove(url) {
|
||||
return Some(th.1);
|
||||
}
|
||||
|
||||
// Do not fetch if disabled
|
||||
if !GLOBALS.settings.read().load_media {
|
||||
return None; // can recover if the setting is switched
|
||||
}
|
||||
|
||||
match GLOBALS.fetcher.try_get(url.clone()) {
|
||||
Ok(None) => None,
|
||||
Ok(Some(bytes)) => {
|
||||
self.data_temp.insert(url.clone(), bytes);
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("{}", e);
|
||||
// this cannot recover without new metadata
|
||||
|
@ -115,11 +115,14 @@ pub(super) fn render_hyperlink(
|
||||
linkspan: &Span,
|
||||
) {
|
||||
let link = note.shattered_content.slice(linkspan).unwrap();
|
||||
if let Some(image_url) = as_image_url(app, link) {
|
||||
show_image_toggle(app, ui, image_url);
|
||||
//} else if is_video_url(&lowercase) {
|
||||
// TODO
|
||||
// crate::ui::widgets::break_anywhere_hyperlink_to(ui, link, link);
|
||||
if let (Ok(url), Some(nurl)) = (url::Url::try_from(link), app.try_check_url(link)) {
|
||||
if is_image_url(&url) {
|
||||
show_image_toggle(app, ui, nurl);
|
||||
} else if is_video_url(&url) {
|
||||
show_video_toggle(app, ui, nurl);
|
||||
} else {
|
||||
crate::ui::widgets::break_anywhere_hyperlink_to(ui, link, link);
|
||||
}
|
||||
} else {
|
||||
crate::ui::widgets::break_anywhere_hyperlink_to(ui, link, link);
|
||||
}
|
||||
@ -172,8 +175,8 @@ pub(super) fn render_unknown_reference(ui: &mut Ui, num: usize) {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_image_url(url: &str) -> bool {
|
||||
let lower = url.to_lowercase();
|
||||
fn is_image_url(url: &url::Url) -> bool {
|
||||
let lower = url.path().to_lowercase();
|
||||
lower.ends_with(".jpg")
|
||||
|| lower.ends_with(".jpeg")
|
||||
|| lower.ends_with(".png")
|
||||
@ -181,23 +184,13 @@ fn is_image_url(url: &str) -> bool {
|
||||
|| lower.ends_with(".webp")
|
||||
}
|
||||
|
||||
fn as_image_url(app: &mut GossipUi, url: &str) -> Option<Url> {
|
||||
if is_image_url(url) {
|
||||
app.try_check_url(url)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fn is_video_url(url: &str) -> bool {
|
||||
let lower = url.to_lowercase();
|
||||
fn is_video_url(url: &url::Url) -> bool {
|
||||
let lower = url.path().to_lowercase();
|
||||
lower.ends_with(".mov")
|
||||
|| lower.ends_with(".mp4")
|
||||
|| lower.ends_with(".mkv")
|
||||
|| lower.ends_with(".webm")
|
||||
}
|
||||
*/
|
||||
|
||||
fn show_image_toggle(app: &mut GossipUi, ui: &mut Ui, url: Url) {
|
||||
let row_height = ui.cursor().height();
|
||||
@ -209,7 +202,7 @@ fn show_image_toggle(app: &mut GossipUi, ui: &mut Ui, url: Url) {
|
||||
|| (!app.settings.show_media && app.media_show_list.contains(&url));
|
||||
|
||||
if show_image {
|
||||
if let Some(response) = try_render_media(app, ui, url.clone()) {
|
||||
if let Some(response) = try_render_image(app, ui, url.clone()) {
|
||||
show_link = false;
|
||||
|
||||
// full-width toggle
|
||||
@ -268,54 +261,20 @@ fn show_image_toggle(app: &mut GossipUi, ui: &mut Ui, url: Url) {
|
||||
|
||||
/// Try to fetch and render a piece of media
|
||||
/// - return: true if successfully rendered, false otherwise
|
||||
fn try_render_media(app: &mut GossipUi, ui: &mut Ui, url: Url) -> Option<Response> {
|
||||
fn try_render_image(app: &mut GossipUi, ui: &mut Ui, url: Url) -> Option<Response> {
|
||||
let mut response_return = None;
|
||||
if let Some(media) = app.try_get_media(ui.ctx(), url.clone()) {
|
||||
let ui_max = if app.media_full_width_list.contains(&url) {
|
||||
Vec2::new(
|
||||
ui.available_width() * 0.9,
|
||||
ui.ctx().screen_rect().height() * 0.9,
|
||||
)
|
||||
} else {
|
||||
Vec2::new(
|
||||
ui.available_width() / 2.0,
|
||||
ui.ctx().screen_rect().height() / 3.0,
|
||||
)
|
||||
};
|
||||
let msize = media.size_vec2();
|
||||
let aspect = media.aspect_ratio();
|
||||
let size = media_scale(
|
||||
app.media_full_width_list.contains(&url),
|
||||
ui,
|
||||
media.size_vec2(),
|
||||
);
|
||||
|
||||
// insert a newline if the current line has text
|
||||
if ui.cursor().min.x > ui.max_rect().min.x {
|
||||
ui.end_row();
|
||||
}
|
||||
|
||||
// determine maximum x and y sizes
|
||||
let max_x = if ui_max.x > msize.x {
|
||||
msize.x
|
||||
} else {
|
||||
ui_max.x
|
||||
};
|
||||
let max_y = if ui_max.y > msize.y {
|
||||
msize.y
|
||||
} else {
|
||||
ui_max.y
|
||||
};
|
||||
|
||||
// now determine if we are constrained by x or by y and
|
||||
// calculate the resulting size
|
||||
let mut size = Vec2::new(0.0, 0.0);
|
||||
size.x = if max_x > max_y * aspect {
|
||||
max_y * aspect
|
||||
} else {
|
||||
max_x
|
||||
};
|
||||
size.y = if max_y > max_x / aspect {
|
||||
max_x / aspect
|
||||
} else {
|
||||
max_y
|
||||
};
|
||||
|
||||
// render the image with a nice frame around it
|
||||
egui::Frame::none()
|
||||
.inner_margin(egui::Margin::same(0.0))
|
||||
@ -396,3 +355,151 @@ fn try_render_media(app: &mut GossipUi, ui: &mut Ui, url: Url) -> Option<Respons
|
||||
};
|
||||
response_return
|
||||
}
|
||||
|
||||
fn show_video_toggle(app: &mut GossipUi, ui: &mut Ui, url: Url) {
|
||||
let row_height = ui.cursor().height();
|
||||
let url_string = url.to_string();
|
||||
let mut show_link = true;
|
||||
|
||||
// FIXME show/hide lists should persist app restarts
|
||||
let show_video = (app.settings.show_media && !app.media_hide_list.contains(&url))
|
||||
|| (!app.settings.show_media && app.media_show_list.contains(&url));
|
||||
|
||||
if show_video {
|
||||
if let Some(response) = try_render_video(app, ui, url.clone()) {
|
||||
show_link = false;
|
||||
|
||||
// full-width toggle
|
||||
if response.clicked() {
|
||||
if app.media_full_width_list.contains(&url) {
|
||||
app.media_full_width_list.remove(&url);
|
||||
} else {
|
||||
app.media_full_width_list.insert(url.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if show_link {
|
||||
let response = ui.link("[ Video ]");
|
||||
// show url on hover
|
||||
response.clone().on_hover_text(url_string.clone());
|
||||
// show media toggle
|
||||
if response.clicked() {
|
||||
if app.settings.show_media {
|
||||
app.media_hide_list.remove(&url);
|
||||
} else {
|
||||
app.media_show_list.insert(url.clone());
|
||||
}
|
||||
if !app.settings.load_media {
|
||||
*GLOBALS.status_message.blocking_write() = "Fetch Media setting is disabled. Right-click link to open in browser or copy URL".to_owned();
|
||||
}
|
||||
}
|
||||
// context menu
|
||||
response.context_menu(|ui| {
|
||||
if ui.button("Open in browser").clicked() {
|
||||
let modifiers = ui.ctx().input(|i| i.modifiers);
|
||||
ui.ctx().output_mut(|o| {
|
||||
o.open_url = Some(egui::output::OpenUrl {
|
||||
url: url_string.clone(),
|
||||
new_tab: modifiers.any(),
|
||||
});
|
||||
});
|
||||
}
|
||||
if ui.button("Copy URL").clicked() {
|
||||
ui.output_mut(|o| o.copied_text = url_string.clone());
|
||||
}
|
||||
if app.has_media_loading_failed(url_string.as_str())
|
||||
&& ui.button("Retry loading ...").clicked()
|
||||
{
|
||||
app.retry_media(&url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ui.end_row();
|
||||
|
||||
// workaround for egui bug where image enlarges the cursor height
|
||||
ui.set_row_height(row_height);
|
||||
}
|
||||
|
||||
#[cfg(feature = "video-ffmpeg")]
|
||||
fn try_render_video(app: &mut GossipUi, ui: &mut Ui, url: Url) -> Option<Response> {
|
||||
let mut response_return = None;
|
||||
let show_full_width = app.media_full_width_list.contains(&url);
|
||||
if let Some(player_ref) = app.try_get_player(ui.ctx(), url) {
|
||||
if let Ok(mut player) = player_ref.try_borrow_mut() {
|
||||
let size = media_scale(
|
||||
show_full_width,
|
||||
ui,
|
||||
Vec2 {
|
||||
x: player.width as f32,
|
||||
y: player.height as f32,
|
||||
},
|
||||
);
|
||||
|
||||
// insert a newline if the current line has text
|
||||
if ui.cursor().min.x > ui.max_rect().min.x {
|
||||
ui.end_row();
|
||||
}
|
||||
|
||||
// show the player
|
||||
if !show_full_width {
|
||||
player.stop();
|
||||
}
|
||||
let response = player.ui(ui, [size.x, size.y]);
|
||||
|
||||
// TODO fix click action
|
||||
let new_rect = response.rect.shrink(size.x / 2.0);
|
||||
response_return = Some(response.with_new_rect(new_rect))
|
||||
}
|
||||
}
|
||||
response_return
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "video-ffmpeg"))]
|
||||
fn try_render_video(_app: &mut GossipUi, _ui: &mut Ui, _url: Url) -> Option<Response> {
|
||||
return None;
|
||||
}
|
||||
|
||||
fn media_scale(show_full_width: bool, ui: &Ui, media_size: Vec2) -> Vec2 {
|
||||
let aspect = media_size.x / media_size.y;
|
||||
let ui_max = if show_full_width {
|
||||
Vec2::new(
|
||||
ui.available_width() * 0.9,
|
||||
ui.ctx().screen_rect().height() * 0.9,
|
||||
)
|
||||
} else {
|
||||
Vec2::new(
|
||||
ui.available_width() / 2.0,
|
||||
ui.ctx().screen_rect().height() / 3.0,
|
||||
)
|
||||
};
|
||||
|
||||
// determine maximum x and y sizes
|
||||
let max_x = if ui_max.x > media_size.x {
|
||||
media_size.x
|
||||
} else {
|
||||
ui_max.x
|
||||
};
|
||||
let max_y = if ui_max.y > media_size.y {
|
||||
media_size.y
|
||||
} else {
|
||||
ui_max.y
|
||||
};
|
||||
|
||||
// now determine if we are constrained by x or by y and
|
||||
// calculate the resulting size
|
||||
let mut size = Vec2::new(0.0, 0.0);
|
||||
size.x = if max_x > max_y * aspect {
|
||||
max_y * aspect
|
||||
} else {
|
||||
max_x
|
||||
};
|
||||
size.y = if max_y > max_x / aspect {
|
||||
max_x / aspect
|
||||
} else {
|
||||
max_y
|
||||
};
|
||||
size
|
||||
}
|
||||
|
@ -32,13 +32,19 @@ use crate::people::DbPerson;
|
||||
use crate::settings::Settings;
|
||||
pub use crate::ui::theme::{Theme, ThemeVariant};
|
||||
use crate::ui::widgets::CopyButton;
|
||||
#[cfg(feature = "video-ffmpeg")]
|
||||
use core::cell::RefCell;
|
||||
use eframe::{egui, IconData};
|
||||
use egui::{
|
||||
Color32, ColorImage, Context, Image, ImageData, Label, RichText, SelectableLabel, Sense,
|
||||
TextStyle, TextureHandle, TextureOptions, Ui, Vec2,
|
||||
};
|
||||
#[cfg(feature = "video-ffmpeg")]
|
||||
use egui_video::{AudioDevice, Player};
|
||||
use nostr_types::{Id, IdHex, Metadata, PublicKey, PublicKeyHex, RelayUrl, UncheckedUrl, Url};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
#[cfg(feature = "video-ffmpeg")]
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::{Duration, Instant};
|
||||
use zeroize::Zeroize;
|
||||
@ -108,6 +114,11 @@ pub enum HighlightType {
|
||||
}
|
||||
|
||||
struct GossipUi {
|
||||
#[cfg(feature = "video-ffmpeg")]
|
||||
audio_device: Option<AudioDevice>,
|
||||
#[cfg(feature = "video-ffmpeg")]
|
||||
video_players: HashMap<Url, Rc<RefCell<egui_video::Player>>>,
|
||||
|
||||
// Rendering
|
||||
next_frame: Instant,
|
||||
override_dpi: bool,
|
||||
@ -144,7 +155,7 @@ struct GossipUi {
|
||||
placeholder_avatar: TextureHandle,
|
||||
settings: Settings,
|
||||
avatars: HashMap<PublicKeyHex, TextureHandle>,
|
||||
media: HashMap<Url, TextureHandle>,
|
||||
images: HashMap<Url, TextureHandle>,
|
||||
/// used when settings.show_media=false to explicitly show
|
||||
media_show_list: HashSet<Url>,
|
||||
/// used when settings.show_media=false to explicitly hide
|
||||
@ -261,6 +272,19 @@ impl GossipUi {
|
||||
)
|
||||
};
|
||||
|
||||
#[cfg(feature = "video-ffmpeg")]
|
||||
let audio_device = {
|
||||
let mut device = None;
|
||||
if let Ok(init) = sdl2::init() {
|
||||
if let Ok(audio) = init.audio() {
|
||||
if let Ok(dev) = egui_video::init_audio_device(&audio) {
|
||||
device = Some(dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
device
|
||||
};
|
||||
|
||||
// how to load an svg
|
||||
// let expand_right_symbol = {
|
||||
// let bytes = include_bytes!("../../assets/expand-image.svg");
|
||||
@ -293,6 +317,10 @@ impl GossipUi {
|
||||
theme::apply_theme(settings.theme, &cctx.egui_ctx);
|
||||
|
||||
GossipUi {
|
||||
#[cfg(feature = "video-ffmpeg")]
|
||||
audio_device,
|
||||
#[cfg(feature = "video-ffmpeg")]
|
||||
video_players: HashMap::new(),
|
||||
next_frame: Instant::now(),
|
||||
override_dpi,
|
||||
override_dpi_value,
|
||||
@ -315,7 +343,7 @@ impl GossipUi {
|
||||
placeholder_avatar: placeholder_avatar_texture_handle,
|
||||
settings,
|
||||
avatars: HashMap::new(),
|
||||
media: HashMap::new(),
|
||||
images: HashMap::new(),
|
||||
media_show_list: HashSet::new(),
|
||||
media_hide_list: HashSet::new(),
|
||||
media_full_width_list: HashSet::new(),
|
||||
@ -762,20 +790,61 @@ impl GossipUi {
|
||||
}
|
||||
|
||||
// see if we already have a texturehandle for this media
|
||||
if let Some(th) = self.media.get(&url) {
|
||||
if let Some(th) = self.images.get(&url) {
|
||||
return Some(th.to_owned());
|
||||
}
|
||||
|
||||
if let Some(color_image) = GLOBALS.media.get_media(&url) {
|
||||
if let Some(color_image) = GLOBALS.media.get_image(&url) {
|
||||
let texture_handle =
|
||||
ctx.load_texture(url.0.clone(), color_image, TextureOptions::default());
|
||||
self.media.insert(url, texture_handle.clone());
|
||||
self.images.insert(url, texture_handle.clone());
|
||||
Some(texture_handle)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "video-ffmpeg")]
|
||||
pub fn try_get_player(
|
||||
&mut self,
|
||||
ctx: &Context,
|
||||
url: Url,
|
||||
) -> Option<Rc<RefCell<egui_video::Player>>> {
|
||||
// Do not keep retrying if failed
|
||||
if GLOBALS.media.has_failed(&url.to_unchecked_url()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// see if we already have a player for this video
|
||||
if let Some(player) = self.video_players.get(&url) {
|
||||
return Some(player.to_owned());
|
||||
}
|
||||
|
||||
if let Some(bytes) = GLOBALS.media.get_data(&url) {
|
||||
if let Ok(player) = Player::new_from_bytes(ctx, &bytes) {
|
||||
if let Some(audio) = &mut self.audio_device {
|
||||
if let Ok(player) = player.with_audio(audio) {
|
||||
let player_ref = Rc::new(RefCell::new(player));
|
||||
self.video_players.insert(url.clone(), player_ref.clone());
|
||||
Some(player_ref)
|
||||
} else {
|
||||
GLOBALS.media.has_failed(&url.to_unchecked_url());
|
||||
None
|
||||
}
|
||||
} else {
|
||||
let player_ref = Rc::new(RefCell::new(player));
|
||||
self.video_players.insert(url.clone(), player_ref.clone());
|
||||
Some(player_ref)
|
||||
}
|
||||
} else {
|
||||
GLOBALS.media.has_failed(&url.to_unchecked_url());
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_qr(&mut self, ui: &mut Ui, ctx: &Context, key: &str, content: &str) {
|
||||
// Remember the UI runs this every frame. We do NOT want to load the texture to the GPU
|
||||
// every frame, so we remember the texture handle in app.qr_codes, and only load to the GPU
|
||||
|
Loading…
Reference in New Issue
Block a user