1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
use crate::{
client::clips::ClipsHttpClient,
models::{
clips::{
hosted::HostedClip,
local::LocalClip,
unified::{SelectedClip, SelectedClipType},
},
error::WayclipError,
query::{FullQueryWeb, PageQuery, StringQueryWeb},
},
settings::UserSettings,
};
use itertools::Itertools;
use regex::Regex;
use std::ffi::OsStr;
/// An empty struct acting as a wrapper, containing query-related methods
pub struct ClipsQuery;
impl ClipsQuery {
/// Use the HTTP client and web query to loop until we get all clips that user has hosted
pub async fn get_all_hosted_clips() -> Result<Vec<HostedClip>, WayclipError> {
// Load in the settings and construct the HTTP client
let settings = UserSettings::load()?;
let mut clips_client = ClipsHttpClient::new(settings.api.url)?;
let mut all_clips = Vec::new();
let mut page_num = 0;
loop {
// Fetch 1 page at a time, with 100 clips each
let body = FullQueryWeb {
page_query: PageQuery {
page_size: 100,
page_num,
},
vec_query: None,
order_query: None,
string_query: None,
range_query: None,
};
let response = clips_client.query_me_clips(body).await?;
let num_pages = response.num_pages;
let fetched_count = response.current_page_items.len();
all_clips.extend(response.current_page_items);
// Keep going until we run out of pages or nothing is returned
page_num += 1;
if page_num >= num_pages || fetched_count == 0 {
break;
}
}
Ok(all_clips)
}
/// Use the HTTP client and web query to search for a specific clip on the remote
/// Clip identifier could be either file name, clip name, or even a regex expression.
pub async fn query_hosted_clips(
clip_identifier: &str,
regex: bool,
) -> Result<Vec<HostedClip>, WayclipError> {
let settings = UserSettings::load()?;
let mut clips_client = ClipsHttpClient::new(settings.api.url)?;
// Query builder so we can change the column easily
let build_query = |column: String| FullQueryWeb {
page_query: PageQuery {
page_size: 100,
page_num: 0,
},
vec_query: None,
order_query: None,
string_query: Some(StringQueryWeb {
column,
like: None,
equal: if !regex {
Some(clip_identifier.to_string())
} else {
None
},
start: None,
end: None,
regex: if regex {
Some(clip_identifier.to_string())
} else {
None
},
}),
range_query: None,
};
// `clip_id` has to match what is specified in DB
let id_query = build_query("clip_id".to_string());
let mut results = clips_client
.query_me_clips(id_query)
.await?
.current_page_items;
// `clip_name` has to match what is specified in DB
let name_query = build_query("clip_name".to_string());
let name_results = clips_client
.query_me_clips(name_query)
.await?
.current_page_items;
// Join & dedup
for clip in name_results {
if !results.iter().any(|c| c.clip_id == clip.clip_id) {
results.push(clip);
}
}
Ok(results)
}
/// We made an iterator so we dont have to repeat ourselves so much when searching from local
/// clips
fn scan_clips(
settings: &UserSettings,
) -> Result<impl Iterator<Item = (LocalClip, String)> + '_, WayclipError> {
let entries = settings.output.metadata_directory.0.read_dir()?;
let iterator = entries.flatten().filter_map(|entry| {
let path = entry.path();
// Now, we only process json files
if !path.is_file() || path.extension().and_then(OsStr::to_str) != Some("json") {
return None;
}
match LocalClip::from_metadata(&path) {
Ok(clip) => {
let file_name = clip
.video_path
.file_name()
.and_then(OsStr::to_str)
.unwrap_or_default()
.to_string();
Some((clip, file_name))
}
Err(e) => {
log::warn!("Could not load clip metadata at {}: {e}", path.display());
None
}
}
});
Ok(iterator)
}
/// Method just to return every single local clip stored
pub async fn get_all_local_clips() -> Result<Vec<LocalClip>, WayclipError> {
let settings = UserSettings::load()?;
// Use the already existing iterator
let clips = Self::scan_clips(&settings)?.map(|(clip, _)| clip).collect();
Ok(clips)
}
/// This query will be used to find all clips stored locally based on a clip_identifier.
/// Clip identifier could be either file name, clip name, or even a regex expression.
pub async fn query_local_clips(
clip_identifier: &str,
regex: bool,
) -> Result<Vec<LocalClip>, WayclipError> {
let settings = UserSettings::load()?;
// See if we need to do regex work
let compiled_regex = match regex {
true => Some(Regex::new(clip_identifier)?),
false => None,
};
// Use the already existing iterator
let clips = Self::scan_clips(&settings)?
.filter(|(clip, file_name)| match &compiled_regex {
Some(regex) => regex.is_match(&clip.name) || regex.is_match(file_name),
None => clip.name == clip_identifier || file_name == clip_identifier,
})
.map(|(clip, _)| clip)
.collect();
Ok(clips)
}
/// Local method to find a clip by its identifier
/// Clip identifier could be either file name or a clip name
pub async fn get_local_clip(clip_identifier: &str) -> Result<LocalClip, WayclipError> {
let settings = UserSettings::load()?;
// Use the already existing iterator
let clip = Self::scan_clips(&settings)?
// Use find so we can get 1 result
.find(|(clip, file_name)| {
clip.name == clip_identifier
|| file_name == clip_identifier
|| clip.uploaded_id.as_deref() == Some(clip_identifier)
})
.map(|(clip, _)| clip)
.ok_or_else(|| WayclipError::NotFound("No clip was found".into()))?;
Ok(clip)
}
/// Gets all local clips, merges their tags and removes duplicates
/// This method is used as a 'pseduo-autocomplete' when filling out tags.
/// All your tags will be a suggestion that you can just TAB into
pub async fn get_all_tags() -> Result<Vec<String>, WayclipError> {
Ok(Self::get_all_local_clips()
.await?
.clone()
.into_iter()
.flat_map(|c| c.clip_tags.into_iter().map(|t| t.name))
.unique()
.collect::<Vec<_>>())
}
/// Gets all local clips, collects their names and removes duplicates
pub async fn get_all_names() -> Result<Vec<String>, WayclipError> {
Ok(Self::get_all_local_clips()
.await?
.clone()
.into_iter()
.map(|c| c.name)
.unique()
.collect::<Vec<_>>())
}
/// Handles searching both local & hosted
/// Clip identifier could be either file name, clip name, or even a regex expression.
/// `clip_type` has to be provided to know which sector to look for specifically
/// TODO: Can be merged with `get_from_all_clips`
pub async fn query_all_clips(
clip_identifier: &str,
regex: bool,
clip_type: SelectedClipType,
) -> Result<Vec<SelectedClip>, WayclipError> {
match clip_type {
SelectedClipType::Local => {
let local = Self::query_local_clips(clip_identifier, regex).await?;
Ok(local.into_iter().map(SelectedClip::Local).collect())
}
SelectedClipType::Hosted => {
let hosted = Self::query_hosted_clips(clip_identifier, regex).await?;
Ok(hosted.into_iter().map(SelectedClip::Hosted).collect())
}
_ => Err(WayclipError::Validation("No such option".into())),
}
}
/// Method to get a single clip from both local & hosted
/// Clip identifier could be either file name, clip name, or even a regex expression.
pub async fn get_from_all_clips(
clip_identifier: &str,
clip_type: SelectedClipType,
) -> Result<SelectedClip, WayclipError> {
let all_clips = Self::query_all_clips(clip_identifier, false, clip_type).await?;
// Cheat and aquire first clip
let clip = all_clips
.first()
.ok_or_else(|| WayclipError::NotFound("No clip was found".into()))?;
Ok(clip.to_owned())
}
}