mirror of
https://github.com/lifegpc/pixiv_downloader.git
synced 2026-06-06 05:49:01 +08:00
fix support for language switch
update document
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
use json::JsonValue;
|
||||
use std::ops::Deref;
|
||||
use std::sync::RwLockReadGuard;
|
||||
use std::sync::RwLockWriteGuard;
|
||||
|
||||
pub trait ToJson {
|
||||
fn to_json(&self) -> Option<JsonValue>;
|
||||
@@ -28,6 +31,27 @@ impl<T: ToJson> ToJson for &T {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToJson> ToJson for Option<T> {
|
||||
fn to_json(&self) -> Option<JsonValue> {
|
||||
match self {
|
||||
Some(d) => { d.to_json() }
|
||||
None => { None }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToJson> ToJson for RwLockReadGuard<'_, T> {
|
||||
fn to_json(&self) -> Option<JsonValue> {
|
||||
self.deref().to_json()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToJson> ToJson for RwLockWriteGuard<'_, T> {
|
||||
fn to_json(&self) -> Option<JsonValue> {
|
||||
self.deref().to_json()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FromJson where Self: Sized {
|
||||
type Err;
|
||||
fn from_json<T: ToJson>(v: T) -> Result<Self, Self::Err>;
|
||||
|
||||
@@ -4,5 +4,6 @@ pub mod flagset;
|
||||
pub mod json;
|
||||
#[cfg(any(feature = "exif", feature = "avdict", feature = "ugoira"))]
|
||||
pub mod rawhandle;
|
||||
pub mod rw_lock;
|
||||
pub mod try_err;
|
||||
pub mod use_or_not;
|
||||
|
||||
35
src/ext/rw_lock.rs
Normal file
35
src/ext/rw_lock.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use spin_on::spin_on;
|
||||
use std::sync::RwLock;
|
||||
use std::sync::RwLockReadGuard;
|
||||
use std::sync::RwLockWriteGuard;
|
||||
use std::time::Duration;
|
||||
|
||||
pub trait GetRwLock {
|
||||
type Target;
|
||||
fn get_ref(&self) -> RwLockReadGuard<Self::Target>;
|
||||
fn get_mut(&self) -> RwLockWriteGuard<Self::Target>;
|
||||
}
|
||||
|
||||
impl<T: Sized> GetRwLock for RwLock<T> {
|
||||
type Target = T;
|
||||
fn get_ref<'a>(&'a self) -> RwLockReadGuard<'a, Self::Target> {
|
||||
loop {
|
||||
match self.try_read() {
|
||||
Ok(f) => { return f; }
|
||||
Err(_) => {
|
||||
spin_on(tokio::time::sleep(Duration::new(0, 1_000_000)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn get_mut<'a>(&'a self) -> RwLockWriteGuard<'a, Self::Target> {
|
||||
loop {
|
||||
match self.try_write() {
|
||||
Ok(f) => { return f; }
|
||||
Err(_) => {
|
||||
spin_on(tokio::time::sleep(Duration::new(0, 1_000_000)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::ext::rw_lock::GetRwLock;
|
||||
use crate::gettext;
|
||||
use crate::opthelper::OptHelper;
|
||||
use crate::parser::metadata::MetaDataParser;
|
||||
@@ -21,7 +22,10 @@ pub struct PixivWebClient {
|
||||
pub helper: OptHelper,
|
||||
/// true if in is initialized
|
||||
inited: Arc<AtomicBool>,
|
||||
/// pixiv global data
|
||||
data: RwLock<Option<JsonValue>>,
|
||||
/// Get basic params
|
||||
params: RwLock<Option<JsonValue>>,
|
||||
}
|
||||
|
||||
impl PixivWebClient {
|
||||
@@ -31,6 +35,7 @@ impl PixivWebClient {
|
||||
helper: OptHelper::new(m.cmd.as_ref().unwrap().clone(), m.settings.as_ref().unwrap().clone()),
|
||||
inited: Arc::new(AtomicBool::new(false)),
|
||||
data: RwLock::new(None),
|
||||
params: RwLock::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +84,10 @@ impl PixivWebClient {
|
||||
let l = self.helper.language();
|
||||
if l.is_some() {
|
||||
self.client.set_header("Accept-Language", l.as_ref().unwrap());
|
||||
self.params.get_mut().replace(json::object! { "lang": l.as_ref().unwrap().replace("-", "_").as_str() });
|
||||
} else {
|
||||
self.client.set_header("Accept-Language", "ja");
|
||||
self.params.get_mut().replace(json::object! { "lang": "ja" });
|
||||
}
|
||||
self.client.set_verbose(self.helper.verbose());
|
||||
let retry = self.helper.retry();
|
||||
@@ -103,7 +110,7 @@ impl PixivWebClient {
|
||||
|
||||
pub fn check_login(&self) -> bool {
|
||||
self.auto_init();
|
||||
let r = self.client.get("https://www.pixiv.net/", None);
|
||||
let r = self.client.get_with_param("https://www.pixiv.net/", self.params.get_ref(), None);
|
||||
if r.is_none() {
|
||||
return false;
|
||||
}
|
||||
@@ -215,7 +222,7 @@ impl PixivWebClient {
|
||||
|
||||
pub fn get_artwork_ajax(&self, id: u64) -> Option<JsonValue> {
|
||||
self.auto_init();
|
||||
let r = self.client.get(format!("https://www.pixiv.net/ajax/illust/{}", id), None);
|
||||
let r = self.client.get_with_param(format!("https://www.pixiv.net/ajax/illust/{}", id), self.params.get_ref(), None);
|
||||
if r.is_none() {
|
||||
return None;
|
||||
}
|
||||
@@ -229,7 +236,7 @@ impl PixivWebClient {
|
||||
|
||||
pub fn get_artwork(&self, id: u64) -> Option<JsonValue> {
|
||||
self.auto_init();
|
||||
let r = self.client.get(format!("https://www.pixiv.net/artworks/{}", id), None);
|
||||
let r = self.client.get_with_param(format!("https://www.pixiv.net/artworks/{}", id), self.params.get_ref(), None);
|
||||
if r.is_none() {
|
||||
return None;
|
||||
}
|
||||
@@ -259,7 +266,7 @@ impl PixivWebClient {
|
||||
|
||||
pub fn get_illust_pages(&self, id: u64) -> Option<JsonValue> {
|
||||
self.auto_init();
|
||||
let r = self.client.get(format!("https://www.pixiv.net/ajax/illust/{}/pages", id), None);
|
||||
let r = self.client.get_with_param(format!("https://www.pixiv.net/ajax/illust/{}/pages", id), self.params.get_ref(), None);
|
||||
if r.is_none() {
|
||||
return None;
|
||||
}
|
||||
@@ -273,7 +280,7 @@ impl PixivWebClient {
|
||||
|
||||
pub fn get_ugoira(&self, id: u64) -> Option<JsonValue> {
|
||||
self.auto_init();
|
||||
let r = self.client.get(format!("https://www.pixiv.net/ajax/illust/{}/ugoira_meta", id), None);
|
||||
let r = self.client.get_with_param(format!("https://www.pixiv.net/ajax/illust/{}/ugoira_meta", id), self.params.get_ref(), None);
|
||||
if r.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ extern crate spin_on;
|
||||
|
||||
use crate::cookies::Cookie;
|
||||
use crate::cookies::CookieJar;
|
||||
use crate::ext::json::ToJson;
|
||||
use crate::gettext;
|
||||
use crate::list::NonTailList;
|
||||
use crate::opthelper::OptHelper;
|
||||
@@ -28,7 +29,9 @@ use std::sync::atomic::AtomicU64;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Convert data to HTTP headers map
|
||||
pub trait ToHeaders {
|
||||
/// return HTTP headers map
|
||||
fn to_headers(&self) -> Option<HashMap<String, String>>;
|
||||
}
|
||||
|
||||
@@ -82,10 +85,13 @@ pub fn gen_cookie_header<U: IntoUrl>(c: &WebClient, url: U) -> String {
|
||||
|
||||
/// A Web Client
|
||||
pub struct WebClient {
|
||||
/// Basic Web Client
|
||||
client: Client,
|
||||
/// HTTP Headers
|
||||
headers: RwLock<HashMap<String, String>>,
|
||||
/// Cookies
|
||||
cookies: RwLock<CookieJar>,
|
||||
/// Verbose logging
|
||||
verbose: Arc<AtomicBool>,
|
||||
/// Retry times, 0 means disable
|
||||
retry: Arc<AtomicU64>,
|
||||
@@ -94,6 +100,7 @@ pub struct WebClient {
|
||||
}
|
||||
|
||||
impl WebClient {
|
||||
/// Create a new instance of client
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
client: Client::new(),
|
||||
@@ -204,6 +211,8 @@ impl WebClient {
|
||||
self.verbose.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Used to handle Set-Cookie header in an [Response]
|
||||
/// * `r` - reference to an [Response]
|
||||
pub fn handle_set_cookie(&self, r: &Response) {
|
||||
let u = r.url();
|
||||
let h = r.headers();
|
||||
@@ -229,6 +238,12 @@ impl WebClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read cookies from file.
|
||||
/// * `file_name`: File name
|
||||
///
|
||||
/// returns true if readed successfully.
|
||||
/// # Note
|
||||
/// If read failed, will clean all entries in the current [CookieJar]
|
||||
pub fn read_cookies(&self, file_name: &str) -> bool {
|
||||
let mut c = self.get_cookies_as_mut();
|
||||
let r = c.read(file_name);
|
||||
@@ -238,10 +253,19 @@ impl WebClient {
|
||||
r
|
||||
}
|
||||
|
||||
/// Save cookies to file
|
||||
/// * `file_name`: File name
|
||||
///
|
||||
/// returns true if saved successfully.
|
||||
pub fn save_cookies(&self, file_name: &str) -> bool {
|
||||
self.get_cookies_as_mut().save(file_name)
|
||||
}
|
||||
|
||||
/// Set new HTTP header
|
||||
/// * `key` - The key of the new HTTP header
|
||||
/// * `value` - The value of the new HTTP value
|
||||
///
|
||||
/// Returns the old HTTP header value if presented.
|
||||
pub fn set_header(&self, key: &str, value: &str) -> Option<String> {
|
||||
self.get_headers_as_mut().insert(String::from(key), String::from(value))
|
||||
}
|
||||
@@ -254,36 +278,42 @@ impl WebClient {
|
||||
pub fn set_verbose(&self, verbose: bool) {
|
||||
self.verbose.store(verbose, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Send GET requests with parameters
|
||||
/// * `param` - GET parameters. Should be a JSON object/array. If value in map is not a string, will dump it
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// let client = WebClient::new();
|
||||
/// client.verbose = true;
|
||||
/// client.get_with_param("https://test.com/a", json::object!{"data": "param1"});
|
||||
/// client.get_with_param("https://test.com/a", json::object!{"daa": {"ad": "test"}});
|
||||
/// client.get_with_param("https://test.com/a", json::array![["daa", "param1"]]);
|
||||
/// client.set_verbose(true);
|
||||
/// client.get_with_param("https://test.com/a", json::object!{"data": "param1"}, None);
|
||||
/// client.get_with_param("https://test.com/a", json::object!{"daa": {"ad": "test"}}, None);
|
||||
/// client.get_with_param("https://test.com/a", json::array![["daa", "param1"]], None);
|
||||
/// ```
|
||||
/// It will GET `https://test.com/a?data=param1`, `https://test.com/a?daa=%7B%22ad%22%3A%22test%22%7D`, `https://test.com/a?daa=param1`
|
||||
pub fn get_with_param<U: IntoUrl + Clone>(&self, url: U, param: JsonValue) -> Option<Response> {
|
||||
pub fn get_with_param<U: IntoUrl + Clone, J: ToJson, H: ToHeaders + Clone>(&self, url: U, param: J, headers: H) -> Option<Response> {
|
||||
let u = url.into_url();
|
||||
if u.is_err() {
|
||||
println!("{} \"{}\"", gettext("Can not parse URL:"), u.unwrap_err());
|
||||
return None;
|
||||
}
|
||||
let mut u = u.unwrap();
|
||||
if !param.is_object() && !param.is_array() {
|
||||
let obj = param.to_json();
|
||||
if obj.is_none() {
|
||||
return self.get(u, headers);
|
||||
}
|
||||
let obj = obj.unwrap();
|
||||
if !obj.is_object() && !obj.is_array() {
|
||||
println!(
|
||||
"{} \"{}\"",
|
||||
gettext("Parameters should be object or array:"),
|
||||
param
|
||||
obj
|
||||
);
|
||||
return None;
|
||||
}
|
||||
{
|
||||
let mut query = u.query_pairs_mut();
|
||||
if param.is_object() {
|
||||
for (k, v) in param.entries() {
|
||||
if obj.is_object() {
|
||||
for (k, v) in obj.entries() {
|
||||
let s: String;
|
||||
if v.is_string() {
|
||||
s = String::from(v.as_str().unwrap());
|
||||
@@ -293,7 +323,7 @@ impl WebClient {
|
||||
query.append_pair(k, s.as_str());
|
||||
}
|
||||
} else {
|
||||
for v in param.members() {
|
||||
for v in obj.members() {
|
||||
if !v.is_object() {
|
||||
println!("{} \"{}\"", gettext("Parameters should be array:"), v);
|
||||
return None;
|
||||
@@ -323,9 +353,10 @@ impl WebClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.get(u.as_str(), None)
|
||||
self.get(u.as_str(), headers)
|
||||
}
|
||||
|
||||
/// Send Get Requests
|
||||
pub fn get<U: IntoUrl + Clone, H: ToHeaders + Clone>(&self, url: U, headers: H) -> Option<Response> {
|
||||
let mut count = 0u64;
|
||||
let retry = self.get_retry();
|
||||
@@ -350,6 +381,7 @@ impl WebClient {
|
||||
None
|
||||
}
|
||||
|
||||
/// Send Get Requests
|
||||
pub async fn aget<U: IntoUrl + Clone, H: ToHeaders + Clone>(&self, url: U, headers: H) -> Option<Response> {
|
||||
let mut count = 0u64;
|
||||
let retry = self.get_retry();
|
||||
@@ -371,7 +403,7 @@ impl WebClient {
|
||||
None
|
||||
}
|
||||
|
||||
/// Send GET requests
|
||||
/// Send GET requests without retry
|
||||
pub fn _get<U: IntoUrl, H: ToHeaders>(&self, url: U, headers: H) -> Option<Response> {
|
||||
let r = self._aget(url, headers);
|
||||
let r = r.send();
|
||||
@@ -391,6 +423,7 @@ impl WebClient {
|
||||
Some(r)
|
||||
}
|
||||
|
||||
/// Send GET requests without retry
|
||||
pub async fn _aget2<U: IntoUrl, H: ToHeaders>(&self, url: U, headers: H) -> Option<Response> {
|
||||
let r = self._aget(url, headers);
|
||||
let r = r.send().await;
|
||||
@@ -409,6 +442,7 @@ impl WebClient {
|
||||
Some(r)
|
||||
}
|
||||
|
||||
/// Generate a requests
|
||||
pub fn _aget<U: IntoUrl, H: ToHeaders>(&self, url: U, headers: H) -> RequestBuilder {
|
||||
let s = url.as_str();
|
||||
if self.get_verbose() {
|
||||
@@ -432,6 +466,13 @@ impl WebClient {
|
||||
r
|
||||
}
|
||||
|
||||
/// Download a stream
|
||||
/// * `file_name` - File name
|
||||
/// * `r` - Response
|
||||
/// * `opt` - Options
|
||||
/// * `progess_bars` - Multiple progress bars
|
||||
///
|
||||
/// Note: If file already exists, will remove existing file first.
|
||||
pub async fn adownload_stream<S: AsRef<OsStr> + ?Sized>(file_name: &S, r: Response, opt: &OptHelper, progress_bars: Option<Arc<MultiProgress>>) -> Result<(), ()> {
|
||||
let content_length = r.content_length();
|
||||
let use_progress_bar = match &content_length {
|
||||
@@ -513,6 +554,8 @@ impl WebClient {
|
||||
/// Download a stream
|
||||
/// * `file_name` - File name
|
||||
/// * `r` - Response
|
||||
/// * `opt` - Options
|
||||
///
|
||||
/// Note: If file already exists, will remove existing file first.
|
||||
pub fn download_stream<S: AsRef<OsStr> + ?Sized>(file_name: &S, r: Response, opt: &OptHelper) -> Result<(), ()> {
|
||||
let content_length = r.content_length();
|
||||
|
||||
Reference in New Issue
Block a user