use chrono::{NaiveDateTime, Utc};
use dashmap::DashMap;
use dragonfly_client_config::dfdaemon::Config;
use dragonfly_client_core::{Error, Result};
use dragonfly_client_util::{digest, http::headermap_to_hashmap};
use reqwest::header::HeaderMap;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use tracing::{error, info, instrument};
use crate::storage_engine::{rocksdb::RocksdbStorageEngine, DatabaseObject, StorageEngineOwned};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Task {
pub id: String,
pub piece_length: Option<u64>,
pub content_length: Option<u64>,
pub response_header: HashMap<String, String>,
pub uploading_count: i64,
pub uploaded_count: u64,
pub updated_at: NaiveDateTime,
pub created_at: NaiveDateTime,
pub prefetched_at: Option<NaiveDateTime>,
pub failed_at: Option<NaiveDateTime>,
pub finished_at: Option<NaiveDateTime>,
}
impl DatabaseObject for Task {
const NAMESPACE: &'static str = "task";
}
impl Task {
pub fn is_started(&self) -> bool {
self.finished_at.is_none()
}
pub fn is_uploading(&self) -> bool {
self.uploading_count > 0
}
pub fn is_expired(&self, ttl: Duration) -> bool {
self.updated_at + ttl < Utc::now().naive_utc()
}
pub fn is_prefetched(&self) -> bool {
self.prefetched_at.is_some()
}
pub fn is_failed(&self) -> bool {
self.failed_at.is_some()
}
pub fn is_finished(&self) -> bool {
self.finished_at.is_some()
}
pub fn is_empty(&self) -> bool {
match self.content_length() {
Some(content_length) => content_length == 0,
None => false,
}
}
pub fn piece_length(&self) -> Option<u64> {
self.piece_length
}
pub fn content_length(&self) -> Option<u64> {
self.content_length
}
pub fn piece_count(&self) -> Option<u64> {
self.content_length()
.zip(self.piece_length())
.map(|(content_length, piece_length)| content_length.div_ceil(piece_length))
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct PersistentTask {
pub id: String,
pub persistent: bool,
pub ttl: Duration,
pub piece_length: u64,
pub content_length: u64,
pub uploading_count: i64,
pub uploaded_count: u64,
pub updated_at: NaiveDateTime,
pub created_at: NaiveDateTime,
pub failed_at: Option<NaiveDateTime>,
pub finished_at: Option<NaiveDateTime>,
}
impl DatabaseObject for PersistentTask {
const NAMESPACE: &'static str = "persistent_task";
}
impl PersistentTask {
pub fn is_started(&self) -> bool {
self.finished_at.is_none()
}
pub fn is_uploading(&self) -> bool {
self.uploading_count > 0
}
pub fn is_expired(&self) -> bool {
self.created_at + self.ttl < Utc::now().naive_utc()
}
pub fn is_failed(&self) -> bool {
self.failed_at.is_some()
}
pub fn is_finished(&self) -> bool {
self.finished_at.is_some()
}
pub fn is_empty(&self) -> bool {
self.content_length == 0
}
pub fn is_persistent(&self) -> bool {
self.persistent
}
pub fn piece_length(&self) -> u64 {
self.piece_length
}
pub fn content_length(&self) -> u64 {
self.content_length
}
pub fn piece_count(&self) -> u64 {
self.content_length.div_ceil(self.piece_length)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct PersistentCacheTask {
pub id: String,
pub persistent: bool,
pub ttl: Duration,
pub piece_length: u64,
pub content_length: u64,
pub uploading_count: i64,
pub uploaded_count: u64,
pub updated_at: NaiveDateTime,
pub created_at: NaiveDateTime,
pub failed_at: Option<NaiveDateTime>,
pub finished_at: Option<NaiveDateTime>,
}
impl DatabaseObject for PersistentCacheTask {
const NAMESPACE: &'static str = "persistent_cache_task";
}
impl PersistentCacheTask {
pub fn is_started(&self) -> bool {
self.finished_at.is_none()
}
pub fn is_uploading(&self) -> bool {
self.uploading_count > 0
}
pub fn is_expired(&self) -> bool {
self.created_at + self.ttl < Utc::now().naive_utc()
}
pub fn is_failed(&self) -> bool {
self.failed_at.is_some()
}
pub fn is_finished(&self) -> bool {
self.finished_at.is_some()
}
pub fn is_empty(&self) -> bool {
self.content_length == 0
}
pub fn is_persistent(&self) -> bool {
self.persistent
}
pub fn piece_length(&self) -> u64 {
self.piece_length
}
pub fn content_length(&self) -> u64 {
self.content_length
}
pub fn piece_count(&self) -> u64 {
self.content_length.div_ceil(self.piece_length)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CacheTask {
pub id: String,
pub piece_length: Option<u64>,
pub content_length: Option<u64>,
pub response_header: HashMap<String, String>,
pub uploading_count: i64,
pub uploaded_count: u64,
pub updated_at: NaiveDateTime,
pub created_at: NaiveDateTime,
pub failed_at: Option<NaiveDateTime>,
pub finished_at: Option<NaiveDateTime>,
}
impl DatabaseObject for CacheTask {
const NAMESPACE: &'static str = "cache_task";
}
impl CacheTask {
pub fn is_started(&self) -> bool {
self.finished_at.is_none()
}
pub fn is_uploading(&self) -> bool {
self.uploading_count > 0
}
pub fn is_expired(&self, ttl: Duration) -> bool {
self.updated_at + ttl < Utc::now().naive_utc()
}
pub fn is_failed(&self) -> bool {
self.failed_at.is_some()
}
pub fn is_finished(&self) -> bool {
self.finished_at.is_some()
}
pub fn is_empty(&self) -> bool {
match self.content_length() {
Some(content_length) => content_length == 0,
None => false,
}
}
pub fn piece_length(&self) -> Option<u64> {
self.piece_length
}
pub fn content_length(&self) -> Option<u64> {
self.content_length
}
pub fn piece_count(&self) -> Option<u64> {
self.content_length()
.zip(self.piece_length())
.map(|(content_length, piece_length)| content_length.div_ceil(piece_length))
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Piece {
pub number: u32,
pub offset: u64,
pub length: u64,
pub digest: String,
pub parent_id: Option<String>,
pub uploading_count: u64,
pub uploaded_count: u64,
pub updated_at: NaiveDateTime,
pub created_at: NaiveDateTime,
pub finished_at: Option<NaiveDateTime>,
}
impl DatabaseObject for Piece {
const NAMESPACE: &'static str = "piece";
}
impl Piece {
pub fn is_started(&self) -> bool {
self.finished_at.is_none()
}
pub fn is_finished(&self) -> bool {
self.finished_at.is_some()
}
pub fn cost(&self) -> Option<Duration> {
match self
.finished_at
.map(|finished_at| finished_at - self.created_at)
{
Some(cost) => match cost.to_std() {
Ok(cost) => Some(cost),
Err(err) => {
error!("convert cost error: {:?}", err);
None
}
},
None => None,
}
}
pub fn prost_cost(&self) -> Option<prost_wkt_types::Duration> {
match self.cost() {
Some(cost) => match prost_wkt_types::Duration::try_from(cost) {
Ok(cost) => Some(cost),
Err(err) => {
error!("convert cost error: {:?}", err);
None
}
},
None => None,
}
}
pub fn calculate_digest(&self) -> String {
let mut hasher = crc32fast::Hasher::new();
hasher.update(&self.number.to_be_bytes());
hasher.update(&self.offset.to_be_bytes());
hasher.update(&self.length.to_be_bytes());
hasher.update(self.digest.as_bytes());
let encoded = hasher.finalize().to_string();
digest::Digest::new(digest::Algorithm::Crc32, encoded).to_string()
}
}
#[derive(Clone, Default)]
struct UploadStats {
pub uploading_count: i64,
pub uploaded_count: u64,
}
pub struct Metadata<E = RocksdbStorageEngine>
where
E: StorageEngineOwned,
{
db: E,
upload_stats: DashMap<String, UploadStats>,
persistent_task_upload_stats: DashMap<String, UploadStats>,
persistent_cache_task_upload_stats: DashMap<String, UploadStats>,
}
impl<E: StorageEngineOwned> Metadata<E> {
#[instrument(level = "debug", skip_all)]
pub fn prepare_download_task(&self, id: &str) -> Result<(Task, bool)> {
let task = match self.db.get::<Task>(id.as_bytes())? {
Some(mut task) => {
if task.content_length().is_some()
&& task.piece_length().is_some()
&& !task.is_failed()
{
return Ok((task, true));
} else {
task.updated_at = Utc::now().naive_utc();
task.failed_at = None;
task
}
}
None => {
Task {
id: id.to_string(),
updated_at: Utc::now().naive_utc(),
created_at: Utc::now().naive_utc(),
..Default::default()
}
}
};
self.db.put(id.as_bytes(), &task)?;
Ok((task, false))
}
#[instrument(level = "debug", skip_all)]
pub fn download_task_started(
&self,
id: &str,
piece_length: u64,
content_length: u64,
response_header: Option<HeaderMap>,
) -> Result<Task> {
let response_header = response_header
.as_ref()
.map(headermap_to_hashmap)
.unwrap_or_default();
let task = match self.db.get::<Task>(id.as_bytes())? {
Some(mut task) => {
task.updated_at = Utc::now().naive_utc();
task.failed_at = None;
task.content_length = Some(content_length);
task.piece_length = Some(piece_length);
task.response_header = response_header;
task
}
None => Task {
id: id.to_string(),
piece_length: Some(piece_length),
content_length: Some(content_length),
response_header,
updated_at: Utc::now().naive_utc(),
created_at: Utc::now().naive_utc(),
..Default::default()
},
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn download_task_finished(&self, id: &str) -> Result<Task> {
let task = match self.db.get::<Task>(id.as_bytes())? {
Some(mut task) => {
task.updated_at = Utc::now().naive_utc();
task.failed_at = None;
task.finished_at = Some(Utc::now().naive_utc());
task
}
None => return Err(Error::TaskNotFound(id.to_string())),
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn download_task_failed(&self, id: &str) -> Result<Task> {
let task = match self.db.get::<Task>(id.as_bytes())? {
Some(mut task) => {
task.updated_at = Utc::now().naive_utc();
task.failed_at = Some(Utc::now().naive_utc());
task
}
None => return Err(Error::TaskNotFound(id.to_string())),
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn prefetch_task_started(&self, id: &str) -> Result<Task> {
let task = match self.db.get::<Task>(id.as_bytes())? {
Some(mut task) => {
if task.is_prefetched() {
return Err(Error::InvalidState("prefetched".to_string()));
}
task.updated_at = Utc::now().naive_utc();
task.prefetched_at = Some(Utc::now().naive_utc());
task.failed_at = None;
task
}
None => return Err(Error::TaskNotFound(id.to_string())),
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn prefetch_task_failed(&self, id: &str) -> Result<Task> {
let task = match self.db.get::<Task>(id.as_bytes())? {
Some(mut task) => {
task.updated_at = Utc::now().naive_utc();
task.prefetched_at = None;
task.failed_at = Some(Utc::now().naive_utc());
task
}
None => return Err(Error::TaskNotFound(id.to_string())),
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn upload_task_started(&self, id: &str) {
self.upload_stats
.entry(id.to_string())
.or_default()
.uploading_count += 1;
}
#[instrument(level = "debug", skip_all)]
pub fn upload_task_finished(&self, id: &str) {
let mut stats = self.upload_stats.entry(id.to_string()).or_default();
stats.uploading_count = stats.uploading_count.saturating_sub(1);
stats.uploaded_count += 1;
}
#[instrument(level = "debug", skip_all)]
pub fn upload_task_failed(&self, id: &str) {
let mut stats = self.upload_stats.entry(id.to_string()).or_default();
stats.uploading_count = stats.uploading_count.saturating_sub(1);
}
fn fill_upload_stats(&self, task: &mut Task) {
if let Some(stats) = self.upload_stats.get(&task.id) {
task.uploading_count = stats.uploading_count;
task.uploaded_count += stats.uploaded_count;
}
}
#[instrument(level = "debug", skip_all)]
pub fn get_task(&self, id: &str) -> Result<Option<Task>> {
Ok(self.db.get::<Task>(id.as_bytes())?.map(|mut task| {
self.fill_upload_stats(&mut task);
task
}))
}
#[instrument(level = "debug", skip_all)]
pub fn is_task_exists(&self, id: &str) -> Result<bool> {
self.db.exists::<Task>(id.as_bytes())
}
#[instrument(level = "debug", skip_all)]
pub fn get_tasks(&self) -> Result<Vec<Task>> {
let tasks = self
.db
.iter_raw::<Task>()?
.map(|ele| {
let (_, value) = ele?;
Ok(value)
})
.collect::<Result<Vec<Box<[u8]>>>>()?;
tasks
.iter()
.map(|task| {
let mut task = Task::deserialize_from(task)?;
self.fill_upload_stats(&mut task);
Ok(task)
})
.collect()
}
#[instrument(level = "debug", skip_all)]
pub fn delete_task(&self, id: &str) -> Result<()> {
info!("delete task metadata {}", id);
self.upload_stats.remove(id);
self.db.delete::<Task>(id.as_bytes())
}
#[instrument(level = "debug", skip_all)]
pub fn create_persistent_task_started(
&self,
id: &str,
ttl: Duration,
piece_length: u64,
content_length: u64,
) -> Result<PersistentTask> {
let task = PersistentTask {
id: id.to_string(),
persistent: true,
ttl,
piece_length,
content_length,
updated_at: Utc::now().naive_utc(),
created_at: Utc::now().naive_utc(),
..Default::default()
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn create_persistent_task_finished(&self, id: &str) -> Result<PersistentTask> {
let task = match self.db.get::<PersistentTask>(id.as_bytes())? {
Some(mut task) => {
task.updated_at = Utc::now().naive_utc();
task.failed_at = None;
if task.finished_at.is_none() {
task.finished_at = Some(Utc::now().naive_utc());
}
task
}
None => return Err(Error::TaskNotFound(id.to_string())),
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn download_persistent_task_started(
&self,
id: &str,
ttl: Duration,
persistent: bool,
piece_length: u64,
content_length: u64,
created_at: NaiveDateTime,
) -> Result<PersistentTask> {
let task = match self.db.get::<PersistentTask>(id.as_bytes())? {
Some(mut task) => {
task.ttl = ttl;
task.persistent = persistent;
task.piece_length = piece_length;
task.updated_at = Utc::now().naive_utc();
task.failed_at = None;
task
}
None => PersistentTask {
id: id.to_string(),
persistent,
ttl,
piece_length,
content_length,
updated_at: Utc::now().naive_utc(),
created_at,
..Default::default()
},
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn download_persistent_task_finished(&self, id: &str) -> Result<PersistentTask> {
let task = match self.db.get::<PersistentTask>(id.as_bytes())? {
Some(mut task) => {
task.updated_at = Utc::now().naive_utc();
task.failed_at = None;
if task.finished_at.is_none() {
task.finished_at = Some(Utc::now().naive_utc());
}
task
}
None => return Err(Error::TaskNotFound(id.to_string())),
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn download_persistent_task_failed(&self, id: &str) -> Result<PersistentTask> {
let task = match self.db.get::<PersistentTask>(id.as_bytes())? {
Some(mut task) => {
task.updated_at = Utc::now().naive_utc();
task.failed_at = Some(Utc::now().naive_utc());
task
}
None => return Err(Error::TaskNotFound(id.to_string())),
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn upload_persistent_task_started(&self, id: &str) {
self.persistent_task_upload_stats
.entry(id.to_string())
.or_default()
.uploading_count += 1;
}
#[instrument(level = "debug", skip_all)]
pub fn upload_persistent_task_finished(&self, id: &str) {
let mut stats = self
.persistent_task_upload_stats
.entry(id.to_string())
.or_default();
stats.uploading_count = stats.uploading_count.saturating_sub(1);
stats.uploaded_count += 1;
}
#[instrument(level = "debug", skip_all)]
pub fn upload_persistent_task_failed(&self, id: &str) {
let mut stats = self
.persistent_task_upload_stats
.entry(id.to_string())
.or_default();
stats.uploading_count = stats.uploading_count.saturating_sub(1);
}
fn fill_persistent_task_upload_stats(&self, task: &mut PersistentTask) {
if let Some(stats) = self.persistent_task_upload_stats.get(&task.id) {
task.uploading_count = stats.uploading_count;
task.uploaded_count += stats.uploaded_count;
}
}
#[instrument(level = "debug", skip_all)]
pub fn persist_persistent_task(&self, id: &str) -> Result<PersistentTask> {
let task = match self.db.get::<PersistentTask>(id.as_bytes())? {
Some(mut task) => {
task.persistent = true;
task.updated_at = Utc::now().naive_utc();
task
}
None => return Err(Error::TaskNotFound(id.to_string())),
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn get_persistent_task(&self, id: &str) -> Result<Option<PersistentTask>> {
Ok(self
.db
.get::<PersistentTask>(id.as_bytes())?
.map(|mut task| {
self.fill_persistent_task_upload_stats(&mut task);
task
}))
}
#[instrument(level = "debug", skip_all)]
pub fn is_persistent_task_exists(&self, id: &str) -> Result<bool> {
self.db.exists::<PersistentTask>(id.as_bytes())
}
#[instrument(level = "debug", skip_all)]
pub fn get_persistent_tasks(&self) -> Result<Vec<PersistentTask>> {
let iter = self.db.iter::<PersistentTask>()?;
iter.map(|ele| {
ele.map(|(_, mut task)| {
self.fill_persistent_task_upload_stats(&mut task);
task
})
})
.collect()
}
#[instrument(level = "debug", skip_all)]
pub fn delete_persistent_task(&self, id: &str) -> Result<()> {
info!("delete persistent task metadata {}", id);
self.persistent_task_upload_stats.remove(id);
self.db.delete::<PersistentTask>(id.as_bytes())
}
#[instrument(level = "debug", skip_all)]
pub fn create_persistent_cache_task_started(
&self,
id: &str,
ttl: Duration,
piece_length: u64,
content_length: u64,
) -> Result<PersistentCacheTask> {
let task = PersistentCacheTask {
id: id.to_string(),
persistent: true,
ttl,
piece_length,
content_length,
updated_at: Utc::now().naive_utc(),
created_at: Utc::now().naive_utc(),
..Default::default()
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn create_persistent_cache_task_finished(&self, id: &str) -> Result<PersistentCacheTask> {
let task = match self.db.get::<PersistentCacheTask>(id.as_bytes())? {
Some(mut task) => {
task.updated_at = Utc::now().naive_utc();
task.failed_at = None;
if task.finished_at.is_none() {
task.finished_at = Some(Utc::now().naive_utc());
}
task
}
None => return Err(Error::TaskNotFound(id.to_string())),
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn download_persistent_cache_task_started(
&self,
id: &str,
ttl: Duration,
persistent: bool,
piece_length: u64,
content_length: u64,
created_at: NaiveDateTime,
) -> Result<PersistentCacheTask> {
let task = match self.db.get::<PersistentCacheTask>(id.as_bytes())? {
Some(mut task) => {
task.ttl = ttl;
task.persistent = persistent;
task.piece_length = piece_length;
task.updated_at = Utc::now().naive_utc();
task.failed_at = None;
task
}
None => PersistentCacheTask {
id: id.to_string(),
persistent,
ttl,
piece_length,
content_length,
updated_at: Utc::now().naive_utc(),
created_at,
..Default::default()
},
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn download_persistent_cache_task_finished(&self, id: &str) -> Result<PersistentCacheTask> {
let task = match self.db.get::<PersistentCacheTask>(id.as_bytes())? {
Some(mut task) => {
task.updated_at = Utc::now().naive_utc();
task.failed_at = None;
if task.finished_at.is_none() {
task.finished_at = Some(Utc::now().naive_utc());
}
task
}
None => return Err(Error::TaskNotFound(id.to_string())),
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn download_persistent_cache_task_failed(&self, id: &str) -> Result<PersistentCacheTask> {
let task = match self.db.get::<PersistentCacheTask>(id.as_bytes())? {
Some(mut task) => {
task.updated_at = Utc::now().naive_utc();
task.failed_at = Some(Utc::now().naive_utc());
task
}
None => return Err(Error::TaskNotFound(id.to_string())),
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn upload_persistent_cache_task_started(&self, id: &str) {
self.persistent_cache_task_upload_stats
.entry(id.to_string())
.or_default()
.uploading_count += 1;
}
#[instrument(level = "debug", skip_all)]
pub fn upload_persistent_cache_task_finished(&self, id: &str) {
let mut stats = self
.persistent_cache_task_upload_stats
.entry(id.to_string())
.or_default();
stats.uploading_count = stats.uploading_count.saturating_sub(1);
stats.uploaded_count += 1;
}
#[instrument(level = "debug", skip_all)]
pub fn upload_persistent_cache_task_failed(&self, id: &str) {
let mut stats = self
.persistent_cache_task_upload_stats
.entry(id.to_string())
.or_default();
stats.uploading_count = stats.uploading_count.saturating_sub(1);
}
fn fill_persistent_cache_task_upload_stats(&self, task: &mut PersistentCacheTask) {
if let Some(stats) = self.persistent_cache_task_upload_stats.get(&task.id) {
task.uploading_count = stats.uploading_count;
task.uploaded_count += stats.uploaded_count;
}
}
#[instrument(level = "debug", skip_all)]
pub fn persist_persistent_cache_task(&self, id: &str) -> Result<PersistentCacheTask> {
let task = match self.db.get::<PersistentCacheTask>(id.as_bytes())? {
Some(mut task) => {
task.persistent = true;
task.updated_at = Utc::now().naive_utc();
task
}
None => return Err(Error::TaskNotFound(id.to_string())),
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn get_persistent_cache_task(&self, id: &str) -> Result<Option<PersistentCacheTask>> {
Ok(self
.db
.get::<PersistentCacheTask>(id.as_bytes())?
.map(|mut task| {
self.fill_persistent_cache_task_upload_stats(&mut task);
task
}))
}
#[instrument(level = "debug", skip_all)]
pub fn is_persistent_cache_task_exists(&self, id: &str) -> Result<bool> {
self.db.exists::<PersistentCacheTask>(id.as_bytes())
}
#[instrument(level = "debug", skip_all)]
pub fn get_persistent_cache_tasks(&self) -> Result<Vec<PersistentCacheTask>> {
let iter = self.db.iter::<PersistentCacheTask>()?;
iter.map(|ele| {
ele.map(|(_, mut task)| {
self.fill_persistent_cache_task_upload_stats(&mut task);
task
})
})
.collect()
}
#[instrument(level = "debug", skip_all)]
pub fn delete_persistent_cache_task(&self, id: &str) -> Result<()> {
info!("delete persistent cache task metadata {}", id);
self.persistent_cache_task_upload_stats.remove(id);
self.db.delete::<PersistentCacheTask>(id.as_bytes())
}
#[instrument(level = "debug", skip_all)]
pub fn download_cache_task_started(
&self,
id: &str,
piece_length: u64,
content_length: u64,
response_header: Option<HeaderMap>,
) -> Result<CacheTask> {
let response_header = response_header
.as_ref()
.map(headermap_to_hashmap)
.unwrap_or_default();
let task = match self.db.get::<CacheTask>(id.as_bytes())? {
Some(mut task) => {
task.updated_at = Utc::now().naive_utc();
task.failed_at = None;
task.content_length = Some(content_length);
task.piece_length = Some(piece_length);
task.response_header = response_header;
task
}
None => CacheTask {
id: id.to_string(),
piece_length: Some(piece_length),
content_length: Some(content_length),
response_header,
updated_at: Utc::now().naive_utc(),
created_at: Utc::now().naive_utc(),
..Default::default()
},
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn download_cache_task_finished(&self, id: &str) -> Result<CacheTask> {
let task = match self.db.get::<CacheTask>(id.as_bytes())? {
Some(mut task) => {
task.updated_at = Utc::now().naive_utc();
task.failed_at = None;
task.finished_at = Some(Utc::now().naive_utc());
task
}
None => return Err(Error::TaskNotFound(id.to_string())),
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn download_cache_task_failed(&self, id: &str) -> Result<CacheTask> {
let task = match self.db.get::<CacheTask>(id.as_bytes())? {
Some(mut task) => {
task.updated_at = Utc::now().naive_utc();
task.failed_at = Some(Utc::now().naive_utc());
task
}
None => return Err(Error::TaskNotFound(id.to_string())),
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn upload_cache_task_started(&self, id: &str) -> Result<CacheTask> {
let task = match self.db.get::<CacheTask>(id.as_bytes())? {
Some(mut task) => {
task.uploading_count += 1;
task.updated_at = Utc::now().naive_utc();
task
}
None => return Err(Error::TaskNotFound(id.to_string())),
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn upload_cache_task_finished(&self, id: &str) -> Result<CacheTask> {
let task = match self.db.get::<CacheTask>(id.as_bytes())? {
Some(mut task) => {
task.uploading_count -= 1;
task.uploaded_count += 1;
task.updated_at = Utc::now().naive_utc();
task
}
None => return Err(Error::TaskNotFound(id.to_string())),
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn upload_cache_task_failed(&self, id: &str) -> Result<CacheTask> {
let task = match self.db.get::<CacheTask>(id.as_bytes())? {
Some(mut task) => {
task.uploading_count -= 1;
task.updated_at = Utc::now().naive_utc();
task
}
None => return Err(Error::TaskNotFound(id.to_string())),
};
self.db.put(id.as_bytes(), &task)?;
Ok(task)
}
#[instrument(level = "debug", skip_all)]
pub fn get_cache_task(&self, id: &str) -> Result<Option<CacheTask>> {
self.db.get(id.as_bytes())
}
#[instrument(level = "debug", skip_all)]
pub fn is_cache_task_exists(&self, id: &str) -> Result<bool> {
self.db.exists::<CacheTask>(id.as_bytes())
}
#[instrument(level = "debug", skip_all)]
pub fn get_cache_tasks(&self) -> Result<Vec<CacheTask>> {
let tasks = self
.db
.iter_raw::<CacheTask>()?
.map(|ele| {
let (_, value) = ele?;
Ok(value)
})
.collect::<Result<Vec<Box<[u8]>>>>()?;
tasks
.iter()
.map(|task| CacheTask::deserialize_from(task))
.collect()
}
#[instrument(level = "debug", skip_all)]
pub fn delete_cache_task(&self, id: &str) -> Result<()> {
info!("delete cache task metadata {}", id);
self.db.delete::<CacheTask>(id.as_bytes())
}
#[instrument(level = "debug", skip_all)]
pub fn create_persistent_piece(
&self,
piece_id: &str,
number: u32,
offset: u64,
length: u64,
digest: &str,
) -> Result<Piece> {
let piece = Piece {
number,
offset,
length,
digest: digest.to_string(),
parent_id: None,
updated_at: Utc::now().naive_utc(),
created_at: Utc::now().naive_utc(),
finished_at: Some(Utc::now().naive_utc()),
..Default::default()
};
self.db.put(piece_id.as_bytes(), &piece)?;
Ok(piece)
}
#[instrument(level = "debug", skip_all)]
pub fn create_persistent_cache_piece(
&self,
piece_id: &str,
number: u32,
offset: u64,
length: u64,
digest: &str,
) -> Result<Piece> {
let piece = Piece {
number,
offset,
length,
digest: digest.to_string(),
parent_id: None,
updated_at: Utc::now().naive_utc(),
created_at: Utc::now().naive_utc(),
finished_at: Some(Utc::now().naive_utc()),
..Default::default()
};
self.db.put(piece_id.as_bytes(), &piece)?;
Ok(piece)
}
#[instrument(level = "debug", skip_all)]
pub fn download_piece_started(&self, piece_id: &str, number: u32) -> Result<Piece> {
let piece = Piece {
number,
updated_at: Utc::now().naive_utc(),
created_at: Utc::now().naive_utc(),
..Default::default()
};
self.db.put(piece_id.as_bytes(), &piece)?;
Ok(piece)
}
#[instrument(level = "debug", skip_all)]
pub fn download_piece_finished(
&self,
piece_id: &str,
offset: u64,
length: u64,
digest: &str,
parent_id: Option<String>,
) -> Result<Piece> {
let piece = match self.db.get::<Piece>(piece_id.as_bytes())? {
Some(mut piece) => {
piece.offset = offset;
piece.length = length;
piece.digest = digest.to_string();
piece.parent_id = parent_id;
piece.updated_at = Utc::now().naive_utc();
piece.finished_at = Some(Utc::now().naive_utc());
piece
}
None => return Err(Error::PieceNotFound(piece_id.to_string())),
};
self.db.put(piece_id.as_bytes(), &piece)?;
Ok(piece)
}
#[instrument(level = "debug", skip_all)]
pub fn download_piece_failed(&self, piece_id: &str) -> Result<()> {
if let Some(piece) = self.get_piece(piece_id)? {
if piece.is_finished() {
return Ok(());
}
}
self.delete_piece(piece_id)
}
#[instrument(level = "debug", skip_all)]
pub fn wait_for_piece_finished_failed(&self, piece_id: &str) -> Result<()> {
if let Some(piece) = self.get_piece(piece_id)? {
if piece.is_finished() {
return Ok(());
}
}
self.delete_piece(piece_id)
}
pub fn get_piece(&self, piece_id: &str) -> Result<Option<Piece>> {
self.db.get(piece_id.as_bytes())
}
#[instrument(level = "debug", skip_all)]
pub fn is_piece_exists(&self, piece_id: &str) -> Result<bool> {
self.db.exists::<Piece>(piece_id.as_bytes())
}
#[instrument(level = "debug", skip_all)]
pub fn get_pieces(&self, task_id: &str) -> Result<Vec<Piece>> {
let pieces = self
.db
.prefix_iter_raw::<Piece>(task_id.as_bytes())?
.map(|ele| {
let (_, value) = ele?;
Ok(value)
})
.collect::<Result<Vec<Box<[u8]>>>>()?;
pieces
.iter()
.map(|piece| Piece::deserialize_from(piece))
.collect()
}
#[instrument(level = "debug", skip_all)]
pub fn delete_piece(&self, piece_id: &str) -> Result<()> {
info!("delete piece metadata {}", piece_id);
self.db.delete::<Piece>(piece_id.as_bytes())
}
#[instrument(level = "debug", skip_all)]
pub fn delete_pieces(&self, task_id: &str) -> Result<()> {
let piece_ids = self
.db
.prefix_iter_raw::<Piece>(task_id.as_bytes())?
.map(|ele| {
let (key, _) = ele?;
Ok(key)
})
.collect::<Result<Vec<Box<[u8]>>>>()?;
let piece_ids_refs = piece_ids
.iter()
.map(|id| {
let id_ref = id.as_ref();
info!(
"delete piece metadata {} in batch",
std::str::from_utf8(id_ref).unwrap_or_default(),
);
id_ref
})
.collect::<Vec<&[u8]>>();
self.db.batch_delete::<Piece>(piece_ids_refs)?;
Ok(())
}
#[inline]
pub fn piece_id(&self, task_id: &str, number: u32) -> String {
format!("{task_id}-{number}")
}
}
impl Metadata<RocksdbStorageEngine> {
#[instrument(level = "debug", skip_all)]
pub fn new(
config: Arc<Config>,
dir: &Path,
log_dir: &PathBuf,
) -> Result<Metadata<RocksdbStorageEngine>> {
let db = RocksdbStorageEngine::open(
dir,
log_dir,
&[
Task::NAMESPACE,
Piece::NAMESPACE,
PersistentTask::NAMESPACE,
PersistentCacheTask::NAMESPACE,
CacheTask::NAMESPACE,
],
config.storage.keep,
)?;
Ok(Metadata {
db,
upload_stats: DashMap::new(),
persistent_task_upload_stats: DashMap::new(),
persistent_cache_task_upload_stats: DashMap::new(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_calculate_digest() {
let piece = Piece {
number: 1,
offset: 0,
length: 1024,
digest: "crc32:1929153120".to_string(),
..Default::default()
};
let digest = piece.calculate_digest();
assert_eq!(digest, "crc32:3299754941");
}
#[test]
fn should_create_metadata() {
let dir = tempdir().unwrap();
let log_dir = dir.path().join("log");
let metadata = Metadata::new(Arc::new(Config::default()), dir.path(), &log_dir).unwrap();
assert!(metadata.get_tasks().unwrap().is_empty());
assert!(metadata
.get_pieces("d3c4e940ad06c47fc36ac67801e6f8e36cb400e2391708620bc7e865b102062c")
.unwrap()
.is_empty());
}
#[test]
fn test_task_lifecycle() {
let dir = tempdir().unwrap();
let log_dir = dir.path().join("log");
let metadata = Metadata::new(Arc::new(Config::default()), dir.path(), &log_dir).unwrap();
let task_id = "d3c4e940ad06c47fc36ac67801e6f8e36cb400e2391708620bc7e865b102062c";
metadata
.download_task_started(task_id, 1024, 1024, None)
.unwrap();
let task = metadata
.get_task(task_id)
.unwrap()
.expect("task should exist after download_task_started");
assert_eq!(task.id, task_id);
assert_eq!(task.piece_length, Some(1024));
assert_eq!(task.content_length, Some(1024));
assert!(task.response_header.is_empty());
assert_eq!(task.uploading_count, 0);
assert_eq!(task.uploaded_count, 0);
assert!(!task.is_finished());
metadata.download_task_finished(task_id).unwrap();
let task = metadata.get_task(task_id).unwrap().unwrap();
assert!(task.is_finished());
metadata.upload_task_started(task_id);
let task = metadata.get_task(task_id).unwrap().unwrap();
assert_eq!(task.uploading_count, 1);
metadata.upload_task_finished(task_id);
let task = metadata.get_task(task_id).unwrap().unwrap();
assert_eq!(task.uploading_count, 0);
assert_eq!(task.uploaded_count, 1);
metadata.upload_task_started(task_id);
let task = metadata.get_task(task_id).unwrap().unwrap();
assert_eq!(task.uploading_count, 1);
metadata.upload_task_failed(task_id);
let task = metadata.get_task(task_id).unwrap().unwrap();
assert_eq!(task.uploading_count, 0);
assert_eq!(task.uploaded_count, 1);
let task_id = "a535b115f18d96870f0422ac891f91dd162f2f391e4778fb84279701fcd02dd1";
metadata
.download_task_started(task_id, 1024, 0, None)
.unwrap();
let tasks = metadata.get_tasks().unwrap();
assert_eq!(tasks.len(), 2);
metadata.delete_task(task_id).unwrap();
let task = metadata.get_task(task_id).unwrap();
assert!(task.is_none());
}
#[test]
fn test_cache_task_lifecycle() {
let dir = tempdir().unwrap();
let log_dir = dir.path().join("log");
let metadata = Metadata::new(Arc::new(Config::default()), dir.path(), &log_dir).unwrap();
let task_id = "d3c4e940ad06c47fc36ac67801e6f8e36cb400e2391708620bc7e865b102062c";
metadata
.download_cache_task_started(task_id, 1024, 1024, None)
.unwrap();
let task = metadata
.get_cache_task(task_id)
.unwrap()
.expect("task should exist after download_cache_task_started");
assert_eq!(task.id, task_id);
assert_eq!(task.piece_length, Some(1024));
assert_eq!(task.content_length, Some(1024));
assert!(task.response_header.is_empty());
assert_eq!(task.uploading_count, 0);
assert_eq!(task.uploaded_count, 0);
assert!(!task.is_finished());
metadata.download_cache_task_finished(task_id).unwrap();
let task = metadata.get_cache_task(task_id).unwrap().unwrap();
assert!(task.is_finished());
metadata.upload_cache_task_started(task_id).unwrap();
let task = metadata.get_cache_task(task_id).unwrap().unwrap();
assert_eq!(task.uploading_count, 1);
metadata.upload_cache_task_finished(task_id).unwrap();
let task = metadata.get_cache_task(task_id).unwrap().unwrap();
assert_eq!(task.uploading_count, 0);
assert_eq!(task.uploaded_count, 1);
let task = metadata.upload_cache_task_started(task_id).unwrap();
assert_eq!(task.uploading_count, 1);
let task = metadata.upload_cache_task_failed(task_id).unwrap();
assert_eq!(task.uploading_count, 0);
assert_eq!(task.uploaded_count, 1);
let task_id = "a535b115f18d96870f0422ac891f91dd162f2f391e4778fb84279701fcd02dd1";
metadata
.download_cache_task_started(task_id, 1024, 0, None)
.unwrap();
let tasks = metadata.get_cache_tasks().unwrap();
assert_eq!(tasks.len(), 2);
metadata.delete_cache_task(task_id).unwrap();
let task = metadata.get_cache_task(task_id).unwrap();
assert!(task.is_none());
}
#[test]
fn test_piece_lifecycle() {
let dir = tempdir().unwrap();
let log_dir = dir.path().join("log");
let metadata = Metadata::new(Arc::new(Config::default()), dir.path(), &log_dir).unwrap();
let task_id = "d3c4e940ad06c47fc36ac67801e6f8e36cb400e2391708620bc7e865b102062c";
let piece_id = metadata.piece_id(task_id, 1);
metadata
.download_piece_started(piece_id.as_str(), 1)
.unwrap();
let piece = metadata.get_piece(piece_id.as_str()).unwrap().unwrap();
assert_eq!(piece.number, 1);
metadata
.download_piece_finished(piece_id.as_str(), 0, 1024, "digest1", None)
.unwrap();
let piece = metadata.get_piece(piece_id.as_str()).unwrap().unwrap();
assert_eq!(piece.length, 1024);
assert_eq!(piece.digest, "digest1");
metadata
.download_piece_started(metadata.piece_id(task_id, 2).as_str(), 2)
.unwrap();
metadata
.download_piece_started(metadata.piece_id(task_id, 3).as_str(), 3)
.unwrap();
let pieces = metadata.get_pieces(task_id).unwrap();
assert_eq!(pieces.len(), 3);
let piece_id = metadata.piece_id(task_id, 2);
metadata
.download_piece_started(piece_id.as_str(), 2)
.unwrap();
metadata
.download_piece_started(metadata.piece_id(task_id, 3).as_str(), 3)
.unwrap();
metadata.download_piece_failed(piece_id.as_str()).unwrap();
let piece = metadata.get_piece(piece_id.as_str()).unwrap();
assert!(piece.is_none());
metadata.delete_pieces(task_id).unwrap();
let pieces = metadata.get_pieces(task_id).unwrap();
assert!(pieces.is_empty());
}
#[test]
fn test_download_piece_failed_keeps_finished_piece() {
let dir = tempdir().unwrap();
let log_dir = dir.path().join("log");
let metadata = Metadata::new(Arc::new(Config::default()), dir.path(), &log_dir).unwrap();
let task_id = "e4c4e940ad06c47fc36ac67801e6f8e36cb400e2391708620bc7e865b102062d";
let piece_id = metadata.piece_id(task_id, 1);
metadata
.download_piece_started(piece_id.as_str(), 1)
.unwrap();
metadata
.download_piece_finished(piece_id.as_str(), 0, 1024, "digest1", None)
.unwrap();
metadata.download_piece_failed(piece_id.as_str()).unwrap();
let piece = metadata.get_piece(piece_id.as_str()).unwrap().unwrap();
assert!(piece.is_finished());
metadata
.wait_for_piece_finished_failed(piece_id.as_str())
.unwrap();
let piece = metadata.get_piece(piece_id.as_str()).unwrap().unwrap();
assert!(piece.is_finished());
}
}