Update pd_file

This commit is contained in:
2022-05-16 22:34:19 +08:00
parent 65da50ffe5
commit 63e39f14c0
8 changed files with 454 additions and 3 deletions

1
src/downloader/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod pd_file;

View 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]);
}

View 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))
}
}

View 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(())
}

View 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;

View 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]))
}

View File

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

View File

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