root/src/settings_data.rs

// settings_data.rs
//
// Copyright 2020-2024 nee <nee-git@hidamari.blue>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-or-later

use crate::booru::Booru;
use crate::data::*;

use serde::{Deserialize, Serialize};
use std::collections::HashSet;

// TODO rename to Booru and rename the old Booru to BooruDriver
#[derive(Debug)]
pub struct BooruData {
    pub booru: Box<dyn Booru>,
    pub settings: BooruSettings,
}

impl BooruData {
    pub fn clone_data(&self) -> BooruData {
        BooruData {
            booru: self.booru.clone_booru(),
            settings: self.settings.clone(),
        }
    }
}

// Size for the gtk Ui Image Element
// 360 is the pinephone/librem5 default window width
// 360 / 4, 3, 2
// = 90, 120, 180
// - 2 pixels for borders
pub const DEFAULT_THUMB_SIZE_UI: i32 = 118;
// Size for the texture image, and how it is saved for local
pub const DEFAULT_THUMB_SIZE_IMAGE: i32 = DEFAULT_THUMB_SIZE_UI * 2;

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(default)]
pub struct Preferences {
    // prefs
    pub infinite_scroll: bool,
    pub fetch_on_startup: bool,
    pub thumb_size: u32,
    pub save_new_tags_to_tagdb: bool,
}

impl Default for Preferences {
    fn default() -> Self {
        Self {
            infinite_scroll: false,
            fetch_on_startup: false,
            thumb_size: DEFAULT_THUMB_SIZE_UI as u32,
            save_new_tags_to_tagdb: false,
        }
    }
}

#[derive(Deserialize, Serialize)]
pub struct SettingsFile {
    pub boorus: Vec<(String, BooruSettings)>,
    pub active_booru: Option<String>,
    pub used_tags: Vec<String>,
    pub favourite_tags: Vec<String>,
    pub muted_tags: Vec<String>,
    pub saved_searches: Vec<SavedSearch>,
    #[serde(default)]
    pub preferences: Preferences,
}

#[derive(Debug, Clone, Default, Deserialize, Serialize)]
// decides how viewed images are saved
pub enum ViewStorage {
    ByDate(u32), // mark everything before the date as viewed
    #[default]
    Individual, // store every post-id that was viewed
    No,          // don't track viewed posts
}

#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct BooruSettings {
    pub view_storage: ViewStorage,
    pub viewed: HashSet<u64>,
    pub max_search_tags: u32,
    pub additional_requests: AdditionRequestSettings,
}

// Wether additional automatic requests should be done
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct AdditionRequestSettings {
    unknown_tags: bool,
    web_source: bool,
}

fn default_true() -> bool {
    true
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct SavedSearch {
    pub search: String,
    pub booru_allowlist: Vec<String>, // empty = no allowlist
    #[serde(default = "default_true")]
    pub auto_load: bool,
}

pub fn read_settings() -> Option<SettingsFile> {
    let mut path = gtk::glib::user_config_dir();
    path.push("boorus/settings.json");
    let buffer = std::fs::File::open(path.as_path()).ok()?;
    let mut settings: SettingsFile = serde_json::from_reader(buffer).ok()?;

    // force validate the thumb size
    settings.preferences.thumb_size = validate_thumb_size(settings.preferences.thumb_size);

    Some(settings)
}

pub fn validate_thumb_size(thumb_size_arg: u32) -> u32 {
    (thumb_size_arg as i32).clamp(MIN_THUMB_SIZE, MAX_THUMB_SIZE) as u32
}

pub fn write_settings(state: &State) -> Option<SettingsFile> {
    let mut path = gtk::glib::user_config_dir();
    path.push("boorus");
    std::fs::create_dir_all(&path).ok()?;
    path.push("settings.json");
    let buffer = std::fs::File::create(path.as_path()).ok()?;
    let settings = SettingsFile {
        boorus: state
            .boorus
            .iter()
            .filter(|b| b.booru.get_domain() != "__builtin_localbooru")
            .map(|b| (b.booru.get_domain().clone(), b.settings.clone()))
            .collect(),
        active_booru: state.active_booru.clone(),
        used_tags: vec![],
        favourite_tags: vec![],
        muted_tags: vec![],
        saved_searches: state.saved_searches.clone(),
        preferences: state.preferences.clone(),
    };
    serde_json::to_writer_pretty(buffer, &settings).ok()?;
    Some(settings)
}