add server feature

This commit is contained in:
2022-07-07 04:58:35 +00:00
committed by GitHub
parent 360b57aec6
commit f78e7a1538
10 changed files with 214 additions and 10 deletions

1
Cargo.lock generated
View File

@@ -1271,6 +1271,7 @@ dependencies = [
"html_parser",
"http",
"http-content-range",
"hyper",
"indicatif",
"int-enum",
"itertools",

View File

@@ -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]

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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
View File

@@ -0,0 +1,4 @@
/// Services
pub mod service;
/// Traits
pub mod traits;

68
src/server/service.rs Normal file
View 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
View 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())))
}
}

View File

@@ -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()