mirror of
https://github.com/lifegpc/pixiv_downloader.git
synced 2026-07-06 04:01:35 +08:00
Update pd_file
This commit is contained in:
1
src/downloader/mod.rs
Normal file
1
src/downloader/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod pd_file;
|
||||
45
src/downloader/pd_file/enums.rs
Normal file
45
src/downloader/pd_file/enums.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use int_enum::IntEnum;
|
||||
|
||||
/// The status of the downloaded file.
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, IntEnum)]
|
||||
pub enum PdFileStatus {
|
||||
/// The download is already started but the target size is unknown.
|
||||
Started = 0,
|
||||
/// The download is started and the tagret size is known.
|
||||
Downloading = 1,
|
||||
/// The download is completed.
|
||||
Downloaded = 2,
|
||||
}
|
||||
|
||||
impl PdFileStatus {
|
||||
#[inline]
|
||||
/// Returns true if the download is completed.
|
||||
pub fn is_completed(&self) -> bool {
|
||||
*self == PdFileStatus::Downloaded
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of the downloader.
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, IntEnum)]
|
||||
pub enum PdFileType {
|
||||
/// Download in single thread mode.
|
||||
SingleThread = 0,
|
||||
/// Download in multiple thread mode.
|
||||
MultiThread = 1,
|
||||
}
|
||||
|
||||
impl PdFileType {
|
||||
#[inline]
|
||||
/// Returns true if is multiple thread mode.
|
||||
pub fn is_multi(&self) -> bool {
|
||||
*self == PdFileType::MultiThread
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enums() {
|
||||
assert_eq!(PdFileStatus::Downloading.int_value().to_le_bytes(), [1]);
|
||||
assert_eq!(PdFileType::MultiThread.int_value().to_le_bytes(), [1]);
|
||||
}
|
||||
30
src/downloader/pd_file/error.rs
Normal file
30
src/downloader/pd_file/error.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use crate::gettext;
|
||||
use std::convert::From;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Debug, derive_more::From)]
|
||||
pub enum PdFileError {
|
||||
IoError(std::io::Error),
|
||||
String(String),
|
||||
}
|
||||
|
||||
impl Display for PdFileError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::IoError(e) => {
|
||||
f.write_str(gettext("Errors occured when operating files: "))?;
|
||||
e.fmt(f)?;
|
||||
}
|
||||
Self::String(e) => {
|
||||
f.write_str(e)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for PdFileError {
|
||||
fn from(value: &str) -> Self {
|
||||
PdFileError::String(String::from(value))
|
||||
}
|
||||
}
|
||||
222
src/downloader/pd_file/file.rs
Normal file
222
src/downloader/pd_file/file.rs
Normal file
@@ -0,0 +1,222 @@
|
||||
use crate::downloader::pd_file::error::PdFileError;
|
||||
use crate::downloader::pd_file::enums::PdFileStatus;
|
||||
use crate::downloader::pd_file::enums::PdFileType;
|
||||
use crate::downloader::pd_file::version::PdFileVersion;
|
||||
use crate::ext::rw_lock::GetRwLock;
|
||||
use crate::ext::try_err::TryErr;
|
||||
use crate::ext::try_err::TryErr2;
|
||||
use crate::gettext;
|
||||
use int_enum::IntEnum;
|
||||
use std::convert::AsRef;
|
||||
use std::fs::File;
|
||||
#[cfg(test)]
|
||||
use std::fs::metadata;
|
||||
use std::fs::remove_file;
|
||||
use std::io::Seek;
|
||||
use std::io::SeekFrom;
|
||||
use std::io::Write;
|
||||
use std::ops::Drop;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::RwLock;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
lazy_static! {
|
||||
#[doc(hidden)]
|
||||
static ref MAGIC_WORDS: Vec<u8> = vec![0x50, 0x44, 0xff, 0xff];
|
||||
}
|
||||
|
||||
/// The pd file
|
||||
pub struct PdFile {
|
||||
/// The version of the current file.
|
||||
version: PdFileVersion,
|
||||
/// Needed to save to file.
|
||||
need_saved: AtomicBool,
|
||||
/// The file handle of the pd file.
|
||||
file: RwLock<Option<File>>,
|
||||
/// The file path of the pd file.
|
||||
file_path: RwLock<Option<PathBuf>>,
|
||||
/// The file name
|
||||
file_name: RwLock<Option<String>>,
|
||||
/// The status of the downloaded file.
|
||||
status: RwLock<PdFileStatus>,
|
||||
/// The type of the downloader.
|
||||
ftype: RwLock<PdFileType>,
|
||||
/// The target size of the file. If unknown, set this to 0.
|
||||
file_size: AtomicU64,
|
||||
/// The size of the downloaded data.
|
||||
downloaded_file_size: AtomicU64,
|
||||
/// The size of the each part. Ignored in single thread mode.
|
||||
part_size: AtomicU32,
|
||||
}
|
||||
|
||||
impl PdFile {
|
||||
/// Create a new instance of the [PdFile]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
version: PdFileVersion::new(1, 0),
|
||||
need_saved: AtomicBool::new(false),
|
||||
file: RwLock::new(None),
|
||||
file_path: RwLock::new(None),
|
||||
file_name: RwLock::new(None),
|
||||
status: RwLock::new(PdFileStatus::Started),
|
||||
ftype: RwLock::new(PdFileType::SingleThread),
|
||||
file_size: AtomicU64::new(0),
|
||||
downloaded_file_size: AtomicU64::new(0),
|
||||
part_size: AtomicU32::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Close the file.
|
||||
/// This function will return error if write failed.
|
||||
/// If you want to force close the file. Please use [Self::force_close()].
|
||||
pub fn close(&self) -> Result<(), PdFileError> {
|
||||
if self.is_need_saved() {
|
||||
self.write()?;
|
||||
}
|
||||
self.force_close();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Force close the file
|
||||
pub fn force_close(&self) {
|
||||
let mut f = self.file.get_mut();
|
||||
match f.as_mut() {
|
||||
Some(_) => { f.take(); }
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Returns true if the download is completed.
|
||||
pub fn is_completed(&self) -> bool {
|
||||
self.status.get_ref().is_completed()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Returns true if needed to save to file.
|
||||
fn is_need_saved(&self) -> bool {
|
||||
self.need_saved.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Create a new file and prepare to write data to it.
|
||||
/// If file alreay exists, will remove it first.
|
||||
/// * `path` - The path to the pd file.
|
||||
pub fn open_with_create_file<P: AsRef<Path> + ?Sized>(&self, path: &P) -> Result<(), PdFileError> {
|
||||
let p = path.as_ref();
|
||||
if p.exists() {
|
||||
remove_file(p)?;
|
||||
}
|
||||
let f = File::create(p)?;
|
||||
self.file.get_mut().replace(f);
|
||||
self.file_path.get_mut().replace(PathBuf::from(p));
|
||||
self.need_saved.store(true, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove file in [Self::file_path]
|
||||
fn remove_pd_file(&self) -> Result<(), PdFileError> {
|
||||
match self.file_path.get_ref().as_ref() {
|
||||
Some(pb) => {
|
||||
if pb.exists() {
|
||||
remove_file(pb)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
None => { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove file in [Self::file_path] and if error occered print that error.
|
||||
fn remove_pd_file_with_err_msg(&self) {
|
||||
match self.remove_pd_file() {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("{} {}", gettext("Failed to remove file: "), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the file name
|
||||
/// * `file_name` - The file name. Should not be empty.
|
||||
pub fn set_file_name<S: AsRef<str> + ?Sized>(&self, file_name: &S) -> Result<(), PdFileError> {
|
||||
let fname = file_name.as_ref();
|
||||
if fname.is_empty() {
|
||||
Err(gettext("File name should not be empty."))?
|
||||
} else {
|
||||
self.file_name.get_mut().replace(String::from(fname));
|
||||
self.need_saved.store(true, Ordering::Relaxed);
|
||||
// Rewrite all datas.
|
||||
self.write()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Write all data to the file.
|
||||
pub fn write(&self) -> Result<(), PdFileError> {
|
||||
let mut f = self.file.get_mut();
|
||||
let mut f = f.as_mut().try_err(gettext("The file is not opened."))?;
|
||||
f.seek(SeekFrom::Start(0))?;
|
||||
f.write_all(&MAGIC_WORDS)?;
|
||||
self.version.write_to(&mut f)?;
|
||||
let file_name = self.file_name.get_ref().try_err2(gettext("File name is not set."))?;
|
||||
let file_name = file_name.as_bytes();
|
||||
f.write_all(&(file_name.len() as u32).to_le_bytes())?;
|
||||
f.write_all(&self.status.get_ref().int_value().to_le_bytes())?;
|
||||
let ftype = self.ftype.get_ref();
|
||||
f.write_all(&ftype.int_value().to_le_bytes())?;
|
||||
f.write_all(&self.file_size.load(Ordering::Relaxed).to_le_bytes())?;
|
||||
f.write_all(&self.downloaded_file_size.load(Ordering::Relaxed).to_le_bytes())?;
|
||||
let part_size = if ftype.is_multi() {
|
||||
self.part_size.load(Ordering::Relaxed)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
f.write_all(&part_size.to_le_bytes())?;
|
||||
f.write_all(file_name)?;
|
||||
self.need_saved.store(false, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PdFile {
|
||||
fn drop(&mut self) {
|
||||
if self.is_completed() {
|
||||
self.force_close();
|
||||
self.remove_pd_file_with_err_msg();
|
||||
return;
|
||||
}
|
||||
if self.is_need_saved() {
|
||||
match self.write() {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("{}", e);
|
||||
self.force_close();
|
||||
self.remove_pd_file_with_err_msg();
|
||||
}
|
||||
}
|
||||
};
|
||||
self.force_close();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn check_file_size<P: AsRef<Path> + ?Sized>(path: &P, size: u64) -> Result<(), PdFileError> {
|
||||
let m = metadata(path)?;
|
||||
assert!(m.len() == size);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pd_file() -> Result<(), PdFileError> {
|
||||
{
|
||||
let f = PdFile::new();
|
||||
f.open_with_create_file("test/a.pd")?;
|
||||
f.set_file_name("a")?;
|
||||
}
|
||||
check_file_size("test/a.pd", 33)?;
|
||||
Ok(())
|
||||
}
|
||||
8
src/downloader/pd_file/mod.rs
Normal file
8
src/downloader/pd_file/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
/// The error type of the pd file.
|
||||
pub mod error;
|
||||
/// The pd file
|
||||
pub mod file;
|
||||
/// The enums of the pd file.
|
||||
pub mod enums;
|
||||
/// Version of the pd file
|
||||
pub mod version;
|
||||
129
src/downloader/pd_file/version.rs
Normal file
129
src/downloader/pd_file/version.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
use std::cmp::Ord;
|
||||
use std::cmp::Ordering;
|
||||
use std::cmp::PartialEq;
|
||||
use std::cmp::PartialOrd;
|
||||
use std::convert::AsRef;
|
||||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
/// Version of the pd file
|
||||
pub struct PdFileVersion {
|
||||
/// Major verson
|
||||
major: u8,
|
||||
/// Minor version
|
||||
minor: u8,
|
||||
}
|
||||
|
||||
impl PdFileVersion {
|
||||
/// Create a new instance of the [PdFileVersion]
|
||||
/// * `major` - major version
|
||||
/// * `minor` - minor version
|
||||
pub fn new(major: u8, minor: u8) -> Self {
|
||||
Self { major, minor }
|
||||
}
|
||||
|
||||
/// Create a new instance of the [PdFileVersion] from bytes.
|
||||
/// * `bytes` - The data
|
||||
/// * `offset` - The offset of the needed data
|
||||
///
|
||||
/// Returns a new instance if succeed otherwise a Error because the data is less than 2 bytes.
|
||||
pub fn from_bytes<T: AsRef<[u8]> + ?Sized>(bytes: &T, offset: usize) -> Result<Self, ()> {
|
||||
let value = bytes.as_ref();
|
||||
if (value.len() - offset) < 2 {
|
||||
Err(())
|
||||
} else {
|
||||
Ok(Self::new(value[offset], value[offset + 1]))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get version bytes
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut data = Vec::new();
|
||||
data.push(self.major);
|
||||
data.push(self.minor);
|
||||
data
|
||||
}
|
||||
|
||||
/// Write version bytes to writer.
|
||||
/// * `writer` - The writer which implement the [Write] trait
|
||||
///
|
||||
/// Returns io Result.
|
||||
pub fn write_to<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
|
||||
writer.write_all(&self.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for PdFileVersion {
|
||||
type Error = ();
|
||||
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
||||
Self::from_bytes(value, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for PdFileVersion {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let r = self.major.cmp(&other.major);
|
||||
if r.is_eq() {
|
||||
self.minor.cmp(&other.minor)
|
||||
} else {
|
||||
r
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for PdFileVersion {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
let r = self.major.cmp(&other.major);
|
||||
if r.is_eq() {
|
||||
Some(self.minor.cmp(&other.minor))
|
||||
} else {
|
||||
Some(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Note
|
||||
/// If [Self::from_bytes()] returns error, assert it to false.
|
||||
impl<T: AsRef<[u8]> + ?Sized> PartialEq<T> for PdFileVersion {
|
||||
fn eq(&self, other: &T) -> bool {
|
||||
match Self::from_bytes(other, 0) {
|
||||
Ok(v) => { v == *self }
|
||||
Err(_) => { false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Note
|
||||
/// If [Self::from_bytes()] returns error, assert it to false.
|
||||
impl<T: AsRef<[u8]> + ?Sized> PartialOrd<T> for PdFileVersion {
|
||||
fn partial_cmp(&self, other: &T) -> Option<Ordering> {
|
||||
match Self::from_bytes(other, 0) {
|
||||
Ok(v) => { Some(self.cmp(&v)) }
|
||||
Err(_) => { None }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pd_file_version() {
|
||||
assert!(PdFileVersion::new(1, 0) != PdFileVersion::new(0, 1));
|
||||
assert!(PdFileVersion::new(1, 10) != PdFileVersion::new(11, 0));
|
||||
assert!(PdFileVersion::new(1, 10) == PdFileVersion::new(1, 10));
|
||||
assert!(PdFileVersion::from_bytes(&vec![1, 10], 0).unwrap() == PdFileVersion::new(1, 10));
|
||||
assert_eq!(PdFileVersion::new(1, 10).to_bytes(), vec![1, 10]);
|
||||
assert!(PdFileVersion::new(2, 1) > PdFileVersion::new(1, 3));
|
||||
assert!(PdFileVersion::new(1, 3) > PdFileVersion::new(1, 2));
|
||||
assert!(PdFileVersion::new(3, 2) >= PdFileVersion::new(3, 2));
|
||||
assert!(PdFileVersion::new(1, 4) < PdFileVersion::new(1, 11));
|
||||
assert!(PdFileVersion::new(1, 3) < PdFileVersion::new(2, 1));
|
||||
assert!(PdFileVersion::new(1, 2) == [1u8, 2]);
|
||||
assert!(PdFileVersion::new(1, 3) == vec![1u8, 3, 2]);
|
||||
assert!(PdFileVersion::new(1, 2) == [1, 2, 3]);
|
||||
assert!(PdFileVersion::new(1, 3) == vec![1, 3, 2]);
|
||||
assert!(PdFileVersion::new(1, 2) != [1]);
|
||||
assert!(PdFileVersion::new(2, 3) > [1, 10]);
|
||||
assert!(PdFileVersion::new(3, 1) < [10, 1]);
|
||||
assert!(PdFileVersion::new(3, 3) >= [3, 3]);
|
||||
assert!(!(PdFileVersion::new(2, 3) > [1]))
|
||||
}
|
||||
@@ -1,14 +1,29 @@
|
||||
/// Try with custom error message
|
||||
pub trait TryErr2<T, E> {
|
||||
/// try with custom error message
|
||||
fn try_err2(&self, err: E) -> Result<T, E>;
|
||||
}
|
||||
|
||||
/// Try with custom error message
|
||||
pub trait TryErr<T, E> {
|
||||
/// try with custom error message
|
||||
fn try_err(&self, err: E) -> Result<T, E>;
|
||||
fn try_err(self, err: E) -> Result<T, E>;
|
||||
}
|
||||
|
||||
impl<T: ToOwned + ToOwned<Owned = T>, E> TryErr<T, E> for Option<T> {
|
||||
fn try_err(&self, v: E) -> Result<T, E> {
|
||||
impl<T: ToOwned + ToOwned<Owned = T>, E> TryErr2<T, E> for Option<T> {
|
||||
fn try_err2(&self, v: E) -> Result<T, E> {
|
||||
match self {
|
||||
Some(r) => { Ok(r.to_owned()) }
|
||||
None => { Err(v) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> TryErr<T, E> for Option<T> {
|
||||
fn try_err(self, err: E) -> Result<T, E> {
|
||||
match self {
|
||||
Some(v) => { Ok(v) }
|
||||
None => { Err(err) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ mod avdict;
|
||||
mod cookies;
|
||||
mod data;
|
||||
mod download;
|
||||
mod downloader;
|
||||
mod dur;
|
||||
#[cfg(feature = "exif")]
|
||||
/// Used to read/modify image's exif data
|
||||
|
||||
Reference in New Issue
Block a user