Skip to content

Commit 4f5af6d

Browse files
committed
add caching for home and view + add storage cleanup + update app version
1 parent bc24f97 commit 4f5af6d

File tree

25 files changed

+605
-168
lines changed

25 files changed

+605
-168
lines changed

snap/snapcraft.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: hyperionbox
22
base: core22
3-
version: '2.0.5'
3+
version: '2.0.6'
44
summary: An open-source anime and movie streaming desktop app. # 79 char long summary
55
description: |
66
An open-source anime and movie streaming app that support watch progress and download for many sources.

src-tauri/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ tauri-plugin-android-fs = "16.1.2"
5858
tauri-plugin-dialog = "2"
5959
dunce = "1.0.5"
6060
html-escape = "0.2.13"
61+
fs_extra = "1.3.0"
6162

6263
[target.'cfg(target_os = "android")'.dependencies]
6364
tauri-plugin-android-package-install = "2.0.2"

src-tauri/src/commands/favorite.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,33 @@ pub fn get_item_from_favorite(tag_name: String) -> Result<Vec<ItemFromFavorite>,
226226
Ok(items)
227227
}
228228

229+
230+
pub fn get_all_item_from_favorite() -> Result<Vec<ItemFromFavorite>, String> {
231+
let conn = get_db()?;
232+
233+
let mut stmt = conn
234+
.prepare(
235+
"SELECT source, id, timestamp
236+
FROM favorite
237+
GROUP BY source, id, timestamp",
238+
)
239+
.map_err(|e| e.to_string())?;
240+
241+
let items = stmt
242+
.query_map([], |row| {
243+
Ok(ItemFromFavorite {
244+
source: row.get(0)?,
245+
id: row.get(1)?,
246+
timestamp: row.get::<_, i64>(2)? as usize,
247+
})
248+
})
249+
.map_err(|e| e.to_string())?
250+
.collect::<Result<Vec<_>, _>>()
251+
.map_err(|e| e.to_string())?;
252+
253+
Ok(items)
254+
}
255+
229256
#[tauri::command]
230257
pub async fn update_timestamp_favorite(source: String, id: String) -> Result<(), String> {
231258
let conn = get_db()?; // Reuse your existing get_db function

src-tauri/src/commands/local_manifest.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use serde_json::{from_reader, to_string_pretty};
1+
use serde_json::{from_reader, to_string};
22
use std::fs;
33
use std::io::BufReader;
44

@@ -72,7 +72,7 @@ pub async fn save_local_manifest(
7272
let manifest_path = item_dir.join("manifest.json");
7373

7474
let local_manifest_data_to_string =
75-
to_string_pretty(&local_manifest_data).map_err(|e| e.to_string())?;
75+
to_string(&local_manifest_data).map_err(|e| e.to_string())?;
7676
fs::write(&manifest_path, local_manifest_data_to_string).map_err(|e| e.to_string())?;
7777

7878
return Ok(());
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use fs_extra::dir::get_size;
2+
use std::fs;
3+
4+
use crate::utils::configs::Configs;
5+
use crate::commands::favorite;
6+
7+
#[tauri::command]
8+
pub async fn get_storage_size() -> Result<usize, String> {
9+
let app_configs = Configs::get()?;
10+
let storage_dir = app_configs.storage_dir.ok_or("Storage directory not set".to_string())?;
11+
12+
if storage_dir.exists() {
13+
match get_size(&storage_dir) {
14+
Ok(size) => return Ok(size as usize),
15+
Err(e) => return Err(e.to_string()),
16+
}
17+
}else {
18+
return Ok(0);
19+
}
20+
}
21+
22+
#[tauri::command]
23+
pub async fn clean_storage() -> Result<(), String> {
24+
let app_configs = Configs::get()?;
25+
let storage_dir = app_configs.storage_dir.ok_or("Storage directory not set".to_string())?;
26+
27+
let favorite_data = favorite::get_all_item_from_favorite()?;
28+
29+
30+
let mut favorite_source_data: Vec<String> = Vec::new();
31+
let mut favorite_id_data: Vec<String> = Vec::new();
32+
33+
for item in favorite_data {
34+
favorite_source_data.push(item.source);
35+
favorite_id_data.push(item.id);
36+
}
37+
38+
/* Clean all item that not in favorite */
39+
let entries = fs::read_dir(&storage_dir).map_err(|e|e.to_string())?;
40+
for entry in entries {
41+
let entry = entry.map_err(|e|e.to_string())?;
42+
let path = entry.path();
43+
if path.is_dir() {
44+
let folder_name = path.file_name().ok_or("Fail to get folder name")?.to_string_lossy().to_string();
45+
if !favorite_source_data.contains(&folder_name) {
46+
fs::remove_dir_all(path).map_err(|e|e.to_string())?;
47+
}else{
48+
let current_source_dir = storage_dir.join(&folder_name);
49+
let current_source_entries = fs::read_dir(&current_source_dir).map_err(|e|e.to_string())?;
50+
for current_source_entry in current_source_entries {
51+
let current_source_entry = current_source_entry.map_err(|e|e.to_string())?;
52+
let path = current_source_entry.path();
53+
if path.is_dir() {
54+
let folder_name = path.file_name().ok_or("Fail to get folder name")?.to_string_lossy().to_string();
55+
if !favorite_id_data.contains(&folder_name) {
56+
fs::remove_dir_all(path).map_err(|e|e.to_string())?;
57+
}
58+
}
59+
}
60+
}
61+
}
62+
}
63+
/* --- */
64+
65+
66+
67+
return Ok(())
68+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
use tracing::{error};
2+
use std::fs;
3+
use std::io::BufReader;
4+
use serde_json::{from_reader, to_string};
5+
use chrono::Utc;
6+
7+
use crate::sources::anime;
8+
use crate::sources::movie;
9+
use crate::commands::favorite::{get_recent_from_favorite};
10+
use crate::commands::local_manifest::{get_local_manifest};
11+
use crate::utils::configs::Configs;
12+
13+
use crate::models::home::{Content, ContentData, HomeData};
14+
15+
const CACHE_DELAY: usize = 3 * 60 * 60 * 1000; // In milliseconds
16+
17+
async fn get_cache(source: &str) -> Result<Option<HomeData>, String> {
18+
let app_config = Configs::get()?;
19+
20+
let cache_dir = app_config.cache_dir.ok_or("Cache dir not set!")?;
21+
22+
let home_cache_path = cache_dir.join(&format!("home_{}.json", source));
23+
24+
if home_cache_path.exists() {
25+
let file = fs::File::open(& home_cache_path).map_err(|e| e.to_string())?;
26+
let reader = BufReader::new(file);
27+
match from_reader::<BufReader<std::fs::File>, HomeData>(reader) {
28+
Ok(data) => return Ok(Some(data)),
29+
Err(_) => {
30+
return Ok(None)
31+
}
32+
}
33+
}
34+
35+
return Ok(None);
36+
}
37+
38+
async fn save_cache(source: &str, data: &HomeData) -> Result<(), String> {
39+
let app_config = Configs::get()?;
40+
let cache_dir = app_config.cache_dir.ok_or("Cache dir not set!")?;
41+
42+
if !cache_dir.exists() {
43+
fs::create_dir_all(&cache_dir).map_err(|e| e.to_string())?;
44+
}
45+
46+
let home_cache_path = cache_dir.join(&format!("home_{}.json", source));
47+
48+
let data_to_string = to_string(&data).map_err(|e| e.to_string())?;
49+
fs::write(&home_cache_path, data_to_string).map_err(|e| e.to_string())?;
50+
51+
return Ok(());
52+
}
53+
54+
#[tauri::command]
55+
pub async fn home(source: String, force_remote: bool) -> Result<HomeData, String> {
56+
let mut home_data: HomeData = HomeData {
57+
relevant_content: vec![],
58+
content: vec![],
59+
last_save_timestamp: 0,
60+
};
61+
62+
let mut should_fetch_remote: bool = true;
63+
64+
if !force_remote {
65+
if let Some(cache_home_data) = get_cache(&source).await?{
66+
let current_timestamp: usize = Utc::now().timestamp_millis() as usize;
67+
if (current_timestamp - cache_home_data.last_save_timestamp) < CACHE_DELAY {
68+
home_data = cache_home_data;
69+
should_fetch_remote = false;
70+
}
71+
}
72+
}
73+
74+
if should_fetch_remote {
75+
if source == "anime" {
76+
match anime::home::new(&source).await {
77+
Ok(data) => home_data = data,
78+
Err(e) => {
79+
error!("[HOME] Error: {}", e);
80+
}
81+
}
82+
}else if source == "movie" {
83+
match movie::home::new(&source).await {
84+
Ok(data) => home_data = data,
85+
Err(e) => {
86+
error!("[HOME] Error: {}", e);
87+
}
88+
}
89+
}else{
90+
return Err("Unkown Source".to_string());
91+
}
92+
93+
save_cache(&source, &home_data).await?;
94+
}
95+
96+
97+
/* Load Recent Watch */
98+
let content_data = &mut home_data.content;
99+
100+
let mut recent_content_data: Vec<ContentData> = vec![];
101+
let recent_from_favorite = get_recent_from_favorite(15).await?;
102+
for item in recent_from_favorite {
103+
let local_manifest = get_local_manifest(item.source.clone(), item.id.to_string()).await?;
104+
match local_manifest.manifest_data {
105+
Some(data) => {
106+
let new_content = ContentData {
107+
source: item.source,
108+
id: item.id.to_string().clone(),
109+
title: data.title,
110+
poster: data.poster,
111+
};
112+
recent_content_data.push(new_content);
113+
}
114+
None => {
115+
let new_content = ContentData {
116+
source: item.source,
117+
id: item.id.to_string().clone(),
118+
title: "?".to_string(),
119+
poster: "".to_string(),
120+
};
121+
recent_content_data.push(new_content);
122+
}
123+
}
124+
}
125+
126+
if recent_content_data.len() > 0 {
127+
content_data.insert(
128+
0,
129+
Content {
130+
label: "Continue watching".to_string(),
131+
data: recent_content_data,
132+
},
133+
);
134+
}
135+
/* --- */
136+
137+
return Ok(home_data);
138+
}
139+
140+
141+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod home;
2+
pub mod view;
3+
pub mod search;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
use tracing::{error};
3+
4+
use crate::sources::anime;
5+
use crate::sources::movie;
6+
use crate::models::search::SearchData;
7+
8+
9+
#[tauri::command]
10+
pub async fn search(source: String, page: usize, search: String) -> Result<Vec<SearchData>, String> {
11+
if source == "anime" {
12+
match anime::search::new(page, &search).await {
13+
Ok(data) => return Ok(data),
14+
Err(e) => {
15+
error!("Error: {}", e);
16+
return Err(e);
17+
}
18+
}
19+
}else if source == "movie" {
20+
match movie::search::new(page, &search).await {
21+
Ok(data) => return Ok(data),
22+
Err(e) => {
23+
error!("[HOME] Error: {}", e);
24+
return Err(e)?;
25+
}
26+
}
27+
}
28+
return Err("Unkown Source".to_string());
29+
}

0 commit comments

Comments
 (0)