Skip to content

Commit

Permalink
update prefered content function
Browse files Browse the repository at this point in the history
  • Loading branch information
wiiznokes committed Dec 16, 2024
1 parent 79ca7c4 commit a746ad6
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 76 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ futures = "0.3"
include_dir = "0.7"
itertools = "0.13.0"
alive_lock_file = "0.2"
regex = "1"

[dependencies.libcosmic]
git = "https://github.com/pop-os/libcosmic"
Expand Down
7 changes: 7 additions & 0 deletions res/config_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@
"type": "integer",
"format": "uint32",
"minimum": 1.0
},
"preferred_mime_types": {
"default": [],
"type": "array",
"items": {
"type": "string"
}
}
},
"X_CONFIGURATOR_SOURCE_HOME_PATH": ".config/cosmic/io.github.wiiznokes.cosmic-ext-applet-clipboard-manager/v3",
Expand Down
62 changes: 45 additions & 17 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use cosmic::widget::{MouseArea, Space};
use cosmic::{app::Task, Element};
use futures::executor::block_on;
use futures::StreamExt;
use regex::Regex;

use crate::config::{Config, PRIVATE_MODE};
use crate::db::{DbMessage, DbTrait, EntryTrait};
Expand Down Expand Up @@ -46,6 +47,7 @@ pub struct AppState<Db: DbTrait> {
pub page: usize,
pub qr_code: Option<Result<qr_code::Data, ()>>,
last_quit: Option<(i64, PopupKind)>,
pub preferred_mime_types_regex: Vec<Regex>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -222,9 +224,20 @@ impl<Db: DbTrait + 'static> cosmic::Application for AppState<Db> {
clipboard_state: ClipboardState::Init,
focused: 0,
qr_code: None,
config,
last_quit: None,
page: 0,
preferred_mime_types_regex: config
.preferred_mime_types
.iter()
.filter_map(|r| match Regex::new(r) {
Ok(r) => Some(r),
Err(e) => {
error!("regex {e}");
None
}
})
.collect(),
config,
};

#[cfg(debug_assertions)]
Expand Down Expand Up @@ -261,10 +274,23 @@ impl<Db: DbTrait + 'static> cosmic::Application for AppState<Db> {

match message {
AppMsg::ChangeConfig(config) => {
if config != self.config {
if config.private_mode != self.config.private_mode {
PRIVATE_MODE.store(config.private_mode, atomic::Ordering::Relaxed);
self.config = config;
}
if config.preferred_mime_types != self.config.preferred_mime_types {
self.preferred_mime_types_regex = config
.preferred_mime_types
.iter()
.filter_map(|r| match Regex::new(r) {
Ok(r) => Some(r),
Err(e) => {
error!("regex {e}");
None
}
})
.collect();
}
self.config = config;
}
AppMsg::ToggleQuickSettings => {
return self.toggle_popup(PopupKind::QuickSettings);
Expand Down Expand Up @@ -364,22 +390,24 @@ impl<Db: DbTrait + 'static> cosmic::Application for AppState<Db> {
AppMsg::ShowQrCode(id) => {
match self.db.get_from_id(id) {
Some(entry) => {
let content = entry.qr_code_content();

// todo: handle better this error
if content.len() < 700 {
match qr_code::Data::new(content) {
Ok(s) => {
self.qr_code.replace(Ok(s));
}
Err(e) => {
error!("{e}");
self.qr_code.replace(Err(()));
if let Some(((_, content), _)) =
entry.preferred_content(&self.preferred_mime_types_regex)
{
// todo: handle better this error
if content.len() < 700 {
match qr_code::Data::new(content) {
Ok(s) => {
self.qr_code.replace(Ok(s));
}
Err(e) => {
error!("{e}");
self.qr_code.replace(Err(()));
}
}
} else {
error!("qr code to long: {}", content.len());
self.qr_code.replace(Err(()));
}
} else {
error!("qr code to long: {}", content.len());
self.qr_code.replace(Err(()));
}
}
None => error!("id not found"),
Expand Down
6 changes: 4 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ pub struct Config {
/// Reset the database at each login
pub unique_session: bool,
pub maximum_entries_by_page: NonZeroU32,
pub preferred_mime_types: Vec<String>,
}

pub static PRIVATE_MODE: AtomicBool = AtomicBool::new(false);

impl Config {
pub fn maximum_entries_lifetime(&self) -> Option<Duration> {
self.maximum_entries_lifetime
Expand All @@ -52,12 +55,11 @@ impl Default for Config {
horizontal: false,
unique_session: false,
maximum_entries_by_page: NonZero::new(50).unwrap(),
preferred_mime_types: Vec::new(),
}
}
}

pub static PRIVATE_MODE: AtomicBool = AtomicBool::new(false);

pub fn sub() -> Subscription<AppMsg> {
struct ConfigSubscription;

Expand Down
133 changes: 88 additions & 45 deletions src/db/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::{collections::HashMap, fmt::Debug, path::Path};
use std::{collections::HashMap, fmt::Debug, path::Path, sync::LazyLock};

use anyhow::{bail, Result};
use anyhow::Result;

use chrono::Utc;
use regex::Regex;

use crate::config::Config;

Expand All @@ -17,14 +18,41 @@ fn now() -> i64 {
}

pub type EntryId = i64;
pub type MimeDataMap = HashMap<String, Vec<u8>>;
pub type Mime = String;
pub type RawContent = Vec<u8>;
pub type MimeDataMap = HashMap<Mime, RawContent>;

pub enum Content<'a> {
Text(&'a str),
Image(&'a [u8]),
UriList(Vec<&'a str>),
}

impl<'a> Content<'a> {
fn try_new(mime: &str, content: &'a [u8]) -> Result<Option<Self>> {
if mime == "text/uri-list" {
let text = core::str::from_utf8(content)?;

let uris = text
.lines()
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.collect();

return Ok(Some(Content::UriList(uris)));
}

if mime.starts_with("text/") {
return Ok(Some(Content::Text(core::str::from_utf8(content)?)));
}

if mime.starts_with("image/") {
return Ok(Some(Content::Image(content)));
}

Ok(None)
}
}

impl Debug for Content<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Expand All @@ -35,7 +63,25 @@ impl Debug for Content<'_> {
}
}

const PREFERRED_MIME_TYPES: &[&str] = &["text/plain"];
/// More we have mime types here, Less we spend time in the [`EntryTrait::preferred_content`] function.
const PRIV_MIME_TYPES_SIMPLE: &[&str] = &[
"text/plain;charset=utf-8",
"text/plain",
"STRING",
"UTF8_STRING",
"TEXT",
"image/png",
"image/jpg",
"image/jpeg",
];
const PRIV_MIME_TYPES_REGEX_STR: &[&str] = &["text/plain*", "text/*", "image/*"];

static PRIV_MIME_TYPES_REGEX: LazyLock<Vec<Regex>> = LazyLock::new(|| {
PRIV_MIME_TYPES_REGEX_STR
.iter()
.map(|r| Regex::new(r).unwrap())
.collect()
});

pub trait EntryTrait: Debug + Clone + Send {
fn is_favorite(&self) -> bool;
Expand All @@ -46,61 +92,58 @@ pub trait EntryTrait: Debug + Clone + Send {

fn id(&self) -> EntryId;

// todo: prioritize certain mime types
fn qr_code_content(&self) -> &[u8] {
self.raw_content().iter().next().unwrap().1
}

fn viewable_content(&self) -> Result<Content<'_>> {
fn try_get_content<'a>(mime: &str, content: &'a [u8]) -> Result<Option<Content<'a>>> {
if mime == "text/uri-list" {
let text = core::str::from_utf8(content)?;

let uris = text
.lines()
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.collect();

return Ok(Some(Content::UriList(uris)));
}

if mime.starts_with("text/") {
return Ok(Some(Content::Text(core::str::from_utf8(content)?)));
}

if mime.starts_with("image/") {
return Ok(Some(Content::Image(content)));
fn preferred_content(
&self,
preferred_mime_types: &[Regex],
) -> Option<((&str, &RawContent), Content<'_>)> {
for pref_mime_regex in preferred_mime_types {
for (mime, raw_content) in self.raw_content() {
if !raw_content.is_empty() && pref_mime_regex.is_match(mime) {
match Content::try_new(mime, raw_content) {
Ok(Some(content)) => return Some(((mime, raw_content), content)),
Ok(None) => error!("unsupported mime type {}", pref_mime_regex),
Err(e) => {
error!("{e}");
}
}
}
}

Ok(None)
}

for pref_mime in PREFERRED_MIME_TYPES {
if let Some(content) = self.raw_content().get(*pref_mime) {
match try_get_content(pref_mime, content) {
Ok(Some(content)) => return Ok(content),
Ok(None) => error!("unsupported mime type {}", pref_mime),
Err(e) => {
error!("{e}");
for pref_mime in PRIV_MIME_TYPES_SIMPLE {
if let Some(raw_content) = self.raw_content().get(*pref_mime) {
if !raw_content.is_empty() {
match Content::try_new(pref_mime, raw_content) {
Ok(Some(content)) => return Some(((pref_mime, raw_content), content)),
Ok(None) => {}
Err(e) => {
error!("{e}");
}
}
}
}
}

for (mime, content) in self.raw_content() {
match try_get_content(mime, content) {
Ok(Some(content)) => return Ok(content),
Ok(None) => {}
Err(e) => {
error!("{e}");
for pref_mime_regex in PRIV_MIME_TYPES_REGEX.iter() {
for (mime, raw_content) in self.raw_content() {
if !raw_content.is_empty() && pref_mime_regex.is_match(mime) {
match Content::try_new(mime, raw_content) {
Ok(Some(content)) => return Some(((mime, raw_content), content)),
Ok(None) => {}
Err(e) => {
error!("{e}");
}
}
}
}
}

bail!(
warn!(
"unsupported mime types {:#?}",
self.raw_content().keys().collect::<Vec<_>>()
)
);

None
}

fn searchable_content(&self) -> impl Iterator<Item = &str> {
Expand Down
2 changes: 1 addition & 1 deletion src/db/sqlite_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ impl Debug for Entry {
f.debug_struct("Data")
.field("id", &self.id)
.field("creation", &self.creation)
.field("content", &self.viewable_content())
.field("content", &self.preferred_content(&[]))
.finish()
}
}
Expand Down
24 changes: 13 additions & 11 deletions src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,17 +149,19 @@ impl<Db: DbTrait> AppState<Db> {
.iter()
.enumerate()
.get(range)
.filter_map(|(pos, data)| match data.viewable_content() {
Ok(c) => match c {
Content::Text(text) => self.text_entry(data, pos == self.focused, text),
Content::Image(image) => {
self.image_entry(data, pos == self.focused, image)
}
Content::UriList(uris) => {
self.uris_entry(data, pos == self.focused, &uris)
}
},
Err(_) => None,
.filter_map(|(pos, data)| {
data.preferred_content(&self.preferred_mime_types_regex)
.and_then(|(_, content)| match content {
Content::Text(text) => {
self.text_entry(data, pos == self.focused, text)
}
Content::Image(image) => {
self.image_entry(data, pos == self.focused, image)
}
Content::UriList(uris) => {
self.uris_entry(data, pos == self.focused, &uris)
}
})
})
.collect();

Expand Down

0 comments on commit a746ad6

Please sign in to comment.