Skip to content

Commit

Permalink
feat: History sidebar
Browse files Browse the repository at this point in the history
TODO:
- Browsing OkuNet
- Setting home replicas
- Making bookmarks & OkuNet posts
    - Viewing bookmarks in sidebar
- Following and blocking users in OkuNet
  • Loading branch information
emmyoh committed Oct 25, 2024
1 parent c421242 commit 7bef619
Show file tree
Hide file tree
Showing 23 changed files with 2,395 additions and 875 deletions.
1,378 changes: 1,016 additions & 362 deletions Cargo.lock

Large diffs are not rendered by default.

22 changes: 12 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,39 +32,41 @@ bincode = "1.3.3"
chrono = { version = "0.4.38", features = ["unstable-locales", "serde"] }
cid = "0.11.1"
directories-next = "2.0.0"
futures = "0.3.30"
futures = "0.3.31"
gdk = { version = "*", package = "gdk4", features = ["v4_14"] }
gio = { version = "*", features = ["v2_80"] }
glib = { version = "*", features = ["v2_80"] }
glib-macros = { version = "*" }
gtk = { version = "*", package = "gtk4", features = ["gnome_46"], default-features = false }
ipfs = { git = "https://github.com/dariusc93/rust-ipfs.git", branch = "libp2p-next", package = "rust-ipfs"}
lazy_static = "1.5.0"
libadwaita = { version = "*", features = ["v1_5", "gio_v2_80"] }
libadwaita = { version = "*", features = ["v1_6", "gio_v2_80"] }
oku-fs = { git = "https://github.com/OkuBrowser/oku-fs", features = ["fuse"] }
# oku-fs = { path = "/home/emil/Documents/GitHub/oku-fs", features = ["fuse"] }
pango = { version = "*" }
url = "2.5.2"
tokio = { version = "1.40.0", features = ["full"] }
tokio = { version = "1.41.0", features = ["full"] }
tokio-stream = "0.1.16"
webkit2gtk = { version = "*", package = "webkit6", features = ["v2_44"] }
tree_magic_mini = { version = "3.1.5", features = ["with-gpl-data"] }
tree_magic_mini = { version = "3.1.6", features = ["with-gpl-data"] }
open = "5.3.0"
env_logger = "0.11.5"
log = "0.4.22"
toml = "0.8.19"
serde = "1.0.210"
uuid = { version = "1.10.0", features = ["v4", "fast-rng", "serde"] }
serde = "1.0.213"
uuid = { version = "1.11.0", features = ["v7", "fast-rng", "serde"] }
miette = "7.2.0"
glob = "0.3.1"
liquid = "0.26.9"
liquid-core = "0.26.9"
liquid-lib = { version = "0.26.9", features = ["all", "stdlib", "jekyll", "shopify", "extra"] }
daggy = { version = "0.8.0", features = ["stable_dag", "serde-1"] }
indicium = { version = "0.6.2", features = ["simple", "strsim", "ahash", "serde"], default-features = false }
once_cell = "1.19.0"
once_cell = "1.20.2"
html-escape = "0.2.13"
bytes = "1.7.2"
bytes = "1.8.0"
native_db = "0.8.1"
native_model = "0.4.20"
rayon = "1.10.0"
tantivy = "0.22.0"

[profile.release]
codegen-units = 1
Expand Down
7 changes: 3 additions & 4 deletions build-aux/com.github.OkuBrowser.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@
"--socket=wayland",
"--device=all",
"--socket=pulseaudio",
"--socket=cups",
"--filesystem=xdg-download",
"--filesystem=xdg-pictures",
"--filesystem=host",
"--talk-name=org.freedesktop.Flatpak",
"--talk-name=org.freedesktop.FileManager1",
"--talk-name=org.freedesktop.Notifications"
"--filesystem=host"
],
"cleanup": [
"/include",
Expand Down Expand Up @@ -63,6 +61,7 @@
"cargo --offline fetch --manifest-path Cargo.toml --verbose",
"cargo --offline build --release --verbose",
"install -Dm755 ./target/release/oku -t /app/bin/",
"install -Dm755 ./resources.gresource -t /app/bin/",
"install -Dm644 ./data/${FLATPAK_ID}.metainfo.xml -t /app/share/metainfo/",
"install -Dm644 ./data/${FLATPAK_ID}.desktop -t /app/share/applications/",
"mkdir -p /app/share/icons/hicolor",
Expand Down
1 change: 0 additions & 1 deletion data/com.github.OkuBrowser.desktop
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ Name=Oku
GenericName=Web Browser
Comment=A Web browser with an emphasis on local-first data storage
Type=Application
TryExec=oku
Exec=oku %U
Terminal=false
Categories=Network;WebBrowser;GTK;GNOME;
Expand Down
2 changes: 2 additions & 0 deletions data/hicolor/scalable/actions/copy-symbolic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions data/hicolor/scalable/actions/hourglass-symbolic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions data/hicolor/scalable/actions/ticket-special-symbolic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions data/hicolor/scalable/actions/ticket-symbolic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions resources.gresource.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,9 @@
<file preprocess="xml-stripblanks">update-symbolic.svg</file>
<file preprocess="xml-stripblanks">uppercase-symbolic.svg</file>
<file preprocess="xml-stripblanks">user-trash-symbolic.svg</file>
<file preprocess="xml-stripblanks">hourglass-symbolic.svg</file>
<file preprocess="xml-stripblanks">ticket-special-symbolic.svg</file>
<file preprocess="xml-stripblanks">ticket-symbolic.svg</file>
<file preprocess="xml-stripblanks">copy-symbolic.svg</file>
</gresource>
</gresources>
4 changes: 2 additions & 2 deletions src/browser_pages/snippets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
}

:root {
--body-bg: linear-gradient(var(--light-2), color-mix(in srgb, var(--grey) 25%, var(--light-2)));
--body-bg: linear-gradient(transparent, color-mix(in srgb, var(--grey) 25%, transparent));
--body-color: var(--dark-3);
--link-color: color-mix(in srgb, var(--pink) 80%, var(--purple));
--link-hover-color: color-mix(in srgb, color-mix(in srgb, var(--pink) 80%, var(--purple)), black 35%);
Expand All @@ -118,7 +118,7 @@

@media (prefers-color-scheme: dark) {
:root {
--body-bg: linear-gradient(color-mix(in srgb, var(--grey), var(--dark-4) 90%), var(--dark-4));
--body-bg: linear-gradient(color-mix(in srgb, var(--grey), transparent 90%), transparent);
--body-color: var(--light-2);
--link-color: color-mix(in srgb, var(--orange) 80%, var(--yellow));
--link-hover-color: color-mix(in srgb, color-mix(in srgb, var(--orange) 80%, var(--yellow)) 90%, white);
Expand Down
238 changes: 238 additions & 0 deletions src/database/bookmark.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
use super::{BrowserDatabase, DATABASE};
use miette::IntoDiagnostic;
use native_db::*;
use native_model::{native_model, Model};
use oku_fs::{database::OkuNote, fs::FS_PATH};
use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
path::PathBuf,
sync::{Arc, LazyLock},
};
use tantivy::{
collector::TopDocs,
directory::MmapDirectory,
query::QueryParser,
schema::{Field, Schema, Value, TEXT},
Directory, Index, IndexReader, IndexWriter, TantivyDocument, Term,
};
use tokio::sync::Mutex;

pub(crate) static BOOKMARK_INDEX_PATH: LazyLock<PathBuf> =
LazyLock::new(|| PathBuf::from(FS_PATH).join("BOOKMARK_INDEX"));
pub(crate) static BOOKMARK_SCHEMA: LazyLock<(Schema, HashMap<&str, Field>)> = LazyLock::new(|| {
let mut schema_builder = Schema::builder();
let fields = HashMap::from([
("url", schema_builder.add_text_field("url", TEXT)),
("title", schema_builder.add_text_field("title", TEXT)),
("body", schema_builder.add_text_field("body", TEXT)),
("tag", schema_builder.add_text_field("tag", TEXT)),
]);
let schema = schema_builder.build();
(schema, fields)
});
pub(crate) static BOOKMARK_INDEX: LazyLock<Index> = LazyLock::new(|| {
let _ = std::fs::create_dir_all(&*BOOKMARK_INDEX_PATH);
let mmap_directory: Box<dyn Directory> =
Box::new(MmapDirectory::open(&*BOOKMARK_INDEX_PATH).unwrap());
Index::open_or_create(mmap_directory, BOOKMARK_SCHEMA.0.clone()).unwrap()
});
pub(crate) static BOOKMARK_INDEX_READER: LazyLock<IndexReader> =
LazyLock::new(|| BOOKMARK_INDEX.reader().unwrap());
pub(crate) static BOOKMARK_INDEX_WRITER: LazyLock<Arc<Mutex<IndexWriter>>> =
LazyLock::new(|| Arc::new(Mutex::new(BOOKMARK_INDEX.writer(50_000_000).unwrap())));

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[native_model(id = 2, version = 1)]
#[native_db]
pub struct Bookmark {
#[primary_key]
pub url: String,
pub title: String,
pub body: String,
pub tags: Vec<String>,
}

impl Bookmark {
fn index_term(&self) -> Term {
Term::from_field_text(BOOKMARK_SCHEMA.1["url"], &self.url)
}
}

impl TryFrom<Bookmark> for OkuNote {
type Error = miette::Report;

fn try_from(value: Bookmark) -> Result<Self, Self::Error> {
Ok(OkuNote {
url: url::Url::parse(&value.url).into_diagnostic()?,
title: value.title,
body: value.body,
tags: value.tags,
})
}
}

impl From<OkuNote> for Bookmark {
fn from(value: OkuNote) -> Self {
Self {
url: value.url.to_string(),
title: value.title,
body: value.body,
tags: value.tags,
}
}
}

impl From<Bookmark> for TantivyDocument {
fn from(value: Bookmark) -> Self {
let mut doc = TantivyDocument::default();
doc.add_text(BOOKMARK_SCHEMA.1["url"], value.url);
doc.add_text(BOOKMARK_SCHEMA.1["title"], value.title);
doc.add_text(BOOKMARK_SCHEMA.1["body"], value.body);
for tag in value.tags {
doc.add_text(BOOKMARK_SCHEMA.1["tag"], tag);
}
doc
}
}

impl TryFrom<TantivyDocument> for Bookmark {
type Error = miette::Report;

fn try_from(value: TantivyDocument) -> Result<Self, Self::Error> {
let url = value
.get_first(BOOKMARK_SCHEMA.1["url"])
.ok_or(miette::miette!("No URL for document in index … "))?
.as_str()
.ok_or(miette::miette!("No URL for document in index … "))?
.to_string();
DATABASE
.get_bookmark(url.clone())
.ok()
.flatten()
.ok_or(miette::miette!("No bookmark with URL {} found … ", url))
}
}

impl BrowserDatabase {
pub fn search_bookmarks(
query_string: String,
result_limit: Option<usize>,
) -> miette::Result<Vec<Bookmark>> {
let searcher = BOOKMARK_INDEX_READER.searcher();
let query_parser = QueryParser::for_index(
&*BOOKMARK_INDEX,
BOOKMARK_SCHEMA.1.clone().into_values().collect(),
);
let query = query_parser.parse_query(&query_string).into_diagnostic()?;
let limit = result_limit.unwrap_or(10);
let top_docs = searcher
.search(&query, &TopDocs::with_limit(limit))
.into_diagnostic()?;
Ok(top_docs
.par_iter()
.filter_map(|x| searcher.doc(x.1).ok())
.collect::<Vec<TantivyDocument>>()
.into_par_iter()
.filter_map(|x| TryInto::try_into(x).ok())
.collect())
}

pub fn upsert_bookmark(&self, bookmark: Bookmark) -> miette::Result<Option<Bookmark>> {
let rw: transaction::RwTransaction<'_> =
self.database.rw_transaction().into_diagnostic()?;
let old_value: Option<Bookmark> = rw.upsert(bookmark.clone()).into_diagnostic()?;
rw.commit().into_diagnostic()?;

let mut index_writer = BOOKMARK_INDEX_WRITER
.clone()
.try_lock_owned()
.into_diagnostic()?;
if let Some(old_bookmark) = old_value.clone() {
index_writer.delete_term(old_bookmark.index_term());
}
index_writer
.add_document(bookmark.into())
.into_diagnostic()?;
index_writer.commit().into_diagnostic()?;

Ok(old_value)
}

pub fn upsert_bookmarks(
&self,
bookmarks: Vec<Bookmark>,
) -> miette::Result<Vec<Option<Bookmark>>> {
let rw = self.database.rw_transaction().into_diagnostic()?;
let old_bookmarks: Vec<_> = bookmarks
.into_iter()
.filter_map(|bookmark| rw.upsert(bookmark).ok())
.collect();
rw.commit().into_diagnostic()?;

let mut index_writer = BOOKMARK_INDEX_WRITER
.clone()
.try_lock_owned()
.into_diagnostic()?;
old_bookmarks.par_iter().for_each(|old_bookmark| {
if let Some(old_bookmark) = old_bookmark {
index_writer.delete_term(old_bookmark.index_term());
}
});
index_writer.commit().into_diagnostic()?;

Ok(old_bookmarks)
}

pub fn delete_bookmark(&self, bookmark: Bookmark) -> miette::Result<Bookmark> {
let rw = self.database.rw_transaction().into_diagnostic()?;
let removed_bookmark = rw.remove(bookmark).into_diagnostic()?;
rw.commit().into_diagnostic()?;

let mut index_writer = BOOKMARK_INDEX_WRITER
.clone()
.try_lock_owned()
.into_diagnostic()?;
index_writer.delete_term(removed_bookmark.index_term());
index_writer.commit().into_diagnostic()?;

Ok(removed_bookmark)
}

pub fn delete_bookmarks(&self, bookmarks: Vec<Bookmark>) -> miette::Result<Vec<Bookmark>> {
let rw = self.database.rw_transaction().into_diagnostic()?;
let removed_bookmarks: Vec<_> = bookmarks
.into_iter()
.filter_map(|bookmark| rw.remove(bookmark).ok())
.collect();
rw.commit().into_diagnostic()?;

let mut index_writer = BOOKMARK_INDEX_WRITER
.clone()
.try_lock_owned()
.into_diagnostic()?;
removed_bookmarks.par_iter().for_each(|removed_bookmark| {
index_writer.delete_term(removed_bookmark.index_term());
});
index_writer.commit().into_diagnostic()?;

Ok(removed_bookmarks)
}

pub fn get_bookmarks(&self) -> miette::Result<Vec<Bookmark>> {
let r = self.database.r_transaction().into_diagnostic()?;
Ok(r.scan()
.primary()
.into_diagnostic()?
.all()
.into_diagnostic()?
.collect::<Result<Vec<_>, _>>()
.into_diagnostic()?)
}

pub fn get_bookmark(&self, original_uri: String) -> miette::Result<Option<Bookmark>> {
let r = self.database.r_transaction().into_diagnostic()?;
Ok(r.get().primary(original_uri).into_diagnostic()?)
}
}
Loading

0 comments on commit 7bef619

Please sign in to comment.