mirror of
https://github.com/lifegpc/pixiv_downloader.git
synced 2026-07-08 01:32:41 +08:00
add server feature
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1271,6 +1271,7 @@ dependencies = [
|
||||
"html_parser",
|
||||
"http",
|
||||
"http-content-range",
|
||||
"hyper",
|
||||
"indicatif",
|
||||
"int-enum",
|
||||
"itertools",
|
||||
|
||||
@@ -20,6 +20,7 @@ gettext = "0.4"
|
||||
html_parser = "0.6.3"
|
||||
http = "0.2"
|
||||
http-content-range = "0.1"
|
||||
hyper = { version="0.14", features = ["server"], optional = true }
|
||||
indicatif = "0.17.0-rc.11"
|
||||
int-enum = "0.4"
|
||||
itertools = "0.10"
|
||||
@@ -41,9 +42,10 @@ bindgen = { version = "0.60", optional = true }
|
||||
cmake = { version = "0.1", optional = true }
|
||||
|
||||
[features]
|
||||
all = ["exif", "ugoira"]
|
||||
all = ["exif", "ugoira", "server"]
|
||||
avdict = ["bindgen", "cmake", "flagset"]
|
||||
exif = ["bindgen", "c_fixed_string", "cmake", "link-cplusplus", "utf16string"]
|
||||
server = ["hyper"]
|
||||
ugoira = ["avdict", "bindgen", "cmake", "link-cplusplus"]
|
||||
|
||||
[profile.release-with-debug]
|
||||
|
||||
@@ -10,6 +10,8 @@ pub enum PixivDownloaderError {
|
||||
JoinError(JoinError),
|
||||
#[cfg(feature = "ugoira")]
|
||||
UgoiraError(UgoiraError),
|
||||
#[cfg(feature = "server")]
|
||||
Hyper(hyper::Error),
|
||||
}
|
||||
|
||||
impl From<&str> for PixivDownloaderError {
|
||||
|
||||
22
src/main.rs
22
src/main.rs
@@ -36,6 +36,8 @@ mod parser;
|
||||
mod pixiv_link;
|
||||
mod pixiv_web;
|
||||
mod retry_interval;
|
||||
#[cfg(feature = "server")]
|
||||
mod server;
|
||||
mod settings;
|
||||
mod settings_list;
|
||||
#[cfg(feature = "ugoira")]
|
||||
@@ -126,6 +128,26 @@ impl Main {
|
||||
Command::Download => {
|
||||
return self.download().await;
|
||||
}
|
||||
#[cfg(feature = "server")]
|
||||
Command::Server => {
|
||||
let addr = get_helper().server();
|
||||
match server::service::start_server(&addr) {
|
||||
Ok(server) => {
|
||||
println!("Listening on http://{}", addr);
|
||||
match server.await {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{} {}", gettext("Failed to start the server:"), e);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Command::None => {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,15 @@ use crate::opts::CommandOpts;
|
||||
use crate::retry_interval::parse_retry_interval_from_json;
|
||||
use crate::settings::SettingStore;
|
||||
use std::convert::TryFrom;
|
||||
#[cfg(feature = "server")]
|
||||
use std::net::IpAddr;
|
||||
#[cfg(feature = "server")]
|
||||
use std::net::Ipv4Addr;
|
||||
#[cfg(feature = "server")]
|
||||
use std::net::SocketAddr;
|
||||
use std::ops::Deref;
|
||||
#[cfg(feature = "server")]
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::sync::RwLockReadGuard;
|
||||
@@ -221,6 +229,22 @@ impl OptHelper {
|
||||
self.default_retry_interval.clone()
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
/// Return the server
|
||||
pub fn server(&self) -> SocketAddr {
|
||||
match self.opt.get_ref().server {
|
||||
Some(server) => {
|
||||
return server;
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
if self.settings.get_ref().have("server") {
|
||||
let v = self.settings.get_ref().get("server").unwrap();
|
||||
return SocketAddr::from_str(v.as_str().unwrap()).unwrap();
|
||||
}
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080)
|
||||
}
|
||||
|
||||
/// Return whether to use data from webpage first.
|
||||
pub fn use_webpage(&self) -> bool {
|
||||
if self.opt.get_ref().use_webpage {
|
||||
|
||||
59
src/opts.rs
59
src/opts.rs
@@ -9,6 +9,8 @@ use getopts::HasArg;
|
||||
use getopts::Options;
|
||||
use std::convert::TryFrom;
|
||||
use std::env;
|
||||
#[cfg(feature = "server")]
|
||||
use std::net::SocketAddr;
|
||||
use std::num::ParseIntError;
|
||||
use std::num::TryFromIntError;
|
||||
use std::str::FromStr;
|
||||
@@ -21,6 +23,9 @@ pub enum Command {
|
||||
Config,
|
||||
/// Download an artwork
|
||||
Download,
|
||||
#[cfg(feature = "server")]
|
||||
/// Run as a server
|
||||
Server,
|
||||
/// Already handled when parsing options, just need return 0.
|
||||
None,
|
||||
}
|
||||
@@ -84,6 +89,9 @@ pub struct CommandOpts {
|
||||
pub max_threads: Option<u64>,
|
||||
/// The size of the each part when downloading file.
|
||||
pub part_size: Option<u32>,
|
||||
#[cfg(feature = "server")]
|
||||
/// Server listen address
|
||||
pub server: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
impl CommandOpts {
|
||||
@@ -110,9 +118,26 @@ impl CommandOpts {
|
||||
download_part_retry: None,
|
||||
max_threads: None,
|
||||
part_size: None,
|
||||
#[cfg(feature = "server")]
|
||||
server: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_command<S: AsRef<str> + ?Sized>(cmd: &S) -> Option<Self> {
|
||||
let cmd = cmd.as_ref();
|
||||
if cmd == "download" || cmd == "d" {
|
||||
return Some(CommandOpts::new(Command::Download));
|
||||
}
|
||||
if cmd == "config" {
|
||||
return Some(CommandOpts::new(Command::Config));
|
||||
}
|
||||
#[cfg(feature = "server")]
|
||||
if cmd == "server" || cmd == "s" {
|
||||
return Some(CommandOpts::new(Command::Server));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn config(&self) -> Option<String> {
|
||||
if self._config.is_some() {
|
||||
if check_file_exists(&self._config.as_ref().unwrap()) {
|
||||
@@ -138,8 +163,9 @@ impl CommandOpts {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_mut)]
|
||||
pub fn print_usage(prog: &str, opts: &Options) {
|
||||
let brief = format!(
|
||||
let mut brief = format!(
|
||||
"{}
|
||||
{} download/d [options] <id/url> [<id/url>] {}
|
||||
{} config fix [options] {}
|
||||
@@ -152,6 +178,15 @@ pub fn print_usage(prog: &str, opts: &Options) {
|
||||
prog,
|
||||
gettext("Print all available settings"),
|
||||
);
|
||||
#[cfg(feature = "server")]
|
||||
{
|
||||
brief += format!(
|
||||
"\n{} server/s [options] [address] {}",
|
||||
prog,
|
||||
gettext("Run as a server")
|
||||
)
|
||||
.as_str();
|
||||
}
|
||||
println!("{}", opts.usage(brief.as_str()));
|
||||
}
|
||||
|
||||
@@ -368,14 +403,7 @@ pub fn parse_cmd() -> Option<CommandOpts> {
|
||||
print_usage(&argv[0], &opts);
|
||||
return Some(CommandOpts::new(Command::None));
|
||||
}
|
||||
let cmd = &result.free[0];
|
||||
let mut re = if cmd == "download" || cmd == "d" {
|
||||
Some(CommandOpts::new(Command::Download))
|
||||
} else if cmd == "config" {
|
||||
Some(CommandOpts::new(Command::Config))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut re = CommandOpts::new_with_command(&result.free[0]);
|
||||
if re.is_none() {
|
||||
println!("{}", gettext("Unknown command."));
|
||||
print_usage(&argv[0], &opts);
|
||||
@@ -419,6 +447,19 @@ pub fn parse_cmd() -> Option<CommandOpts> {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "server")]
|
||||
Command::Server => {
|
||||
if result.free.len() >= 2 {
|
||||
let address = &result.free[1];
|
||||
match SocketAddr::from_str(address) {
|
||||
Ok(address) => re.as_mut().unwrap().server = Some(address),
|
||||
Err(e) => {
|
||||
println!("{} {}", gettext("Failed to parse address:"), e);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::None => {}
|
||||
}
|
||||
if result.opt_present("config") {
|
||||
|
||||
4
src/server/mod.rs
Normal file
4
src/server/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
/// Services
|
||||
pub mod service;
|
||||
/// Traits
|
||||
pub mod traits;
|
||||
68
src/server/service.rs
Normal file
68
src/server/service.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use crate::error::PixivDownloaderError;
|
||||
use hyper::server::conn::AddrIncoming;
|
||||
use hyper::server::Server;
|
||||
use hyper::service::Service;
|
||||
use hyper::Body;
|
||||
use hyper::Request;
|
||||
use hyper::Response;
|
||||
use std::future::Future;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
|
||||
pub struct PixivDownloaderSvc {
|
||||
_unused: [u8; 0],
|
||||
}
|
||||
|
||||
impl PixivDownloaderSvc {
|
||||
pub fn new() -> Self {
|
||||
Self { _unused: [] }
|
||||
}
|
||||
}
|
||||
|
||||
impl Service<Request<Body>> for PixivDownloaderSvc {
|
||||
type Response = Response<Body>;
|
||||
type Error = PixivDownloaderError;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
||||
|
||||
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request<Body>) -> Self::Future {
|
||||
Box::pin(async { Ok(Response::builder().body(Body::from("hello world")).unwrap()) })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PixivDownloaderMakeSvc {
|
||||
_unused: [u8; 0],
|
||||
}
|
||||
|
||||
impl PixivDownloaderMakeSvc {
|
||||
pub fn new() -> Self {
|
||||
Self { _unused: [] }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Service<T> for PixivDownloaderMakeSvc {
|
||||
type Response = PixivDownloaderSvc;
|
||||
type Error = hyper::Error;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
||||
|
||||
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, _: T) -> Self::Future {
|
||||
let fut = async move { Ok(PixivDownloaderSvc::new()) };
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the server
|
||||
pub fn start_server(
|
||||
addr: &SocketAddr,
|
||||
) -> Result<Server<AddrIncoming, PixivDownloaderMakeSvc>, hyper::Error> {
|
||||
Ok(Server::try_bind(addr)?.serve(PixivDownloaderMakeSvc::new()))
|
||||
}
|
||||
23
src/server/traits.rs
Normal file
23
src/server/traits.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use crate::error::PixivDownloaderError;
|
||||
use hyper::Body;
|
||||
use hyper::Request;
|
||||
use hyper::Response;
|
||||
use json::JsonValue;
|
||||
|
||||
pub trait ResponseFor<T, R> {
|
||||
fn response(&self, res: Request<T>) -> Result<Response<R>, PixivDownloaderError>;
|
||||
}
|
||||
|
||||
pub trait ResponseJsonFor<T> {
|
||||
fn response_json(&self, res: Request<T>) -> Result<JsonValue, PixivDownloaderError>;
|
||||
}
|
||||
|
||||
impl<T> ResponseFor<T, Body> for T
|
||||
where
|
||||
T: ResponseJsonFor<T>,
|
||||
{
|
||||
fn response(&self, res: Request<T>) -> Result<Response<Body>, PixivDownloaderError> {
|
||||
let re = self.response_json(res)?;
|
||||
Ok(Response::new(Body::from(re.to_string())))
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,10 @@ use crate::opt::author_name_filter::check_author_name_filters;
|
||||
use crate::opt::proxy::check_proxy;
|
||||
use crate::opt::size::parse_u32_size;
|
||||
use json::JsonValue;
|
||||
#[cfg(feature = "server")]
|
||||
use std::net::SocketAddr;
|
||||
#[cfg(feature = "server")]
|
||||
use std::str::FromStr;
|
||||
|
||||
pub fn get_settings_list() -> Vec<SettingDes> {
|
||||
vec![
|
||||
@@ -30,6 +34,8 @@ pub fn get_settings_list() -> Vec<SettingDes> {
|
||||
SettingDes::new("max-threads", gettext("The maximun threads when downloading file."), JsonValueType::Number, Some(check_u64)).unwrap(),
|
||||
SettingDes::new("part-size", gettext("The size of the each part when downloading file."), JsonValueType::Number, Some(check_parse_size_u32)).unwrap(),
|
||||
SettingDes::new("proxy", gettext("Proxy settings."), JsonValueType::Array, Some(check_proxy)).unwrap(),
|
||||
#[cfg(feature = "server")]
|
||||
SettingDes::new("server", gettext("Server address."), JsonValueType::Str, Some(check_socket_addr)).unwrap(),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -38,6 +44,17 @@ fn check_i64(obj: &JsonValue) -> bool {
|
||||
r.is_some()
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn check_socket_addr(obj: &JsonValue) -> bool {
|
||||
match obj.as_str() {
|
||||
Some(s) => match SocketAddr::from_str(s) {
|
||||
Ok(_) => true,
|
||||
Err(_) => false,
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_u64(obj: &JsonValue) -> bool {
|
||||
let r = obj.as_u64();
|
||||
r.is_some()
|
||||
|
||||
Reference in New Issue
Block a user