implement kinda working readdir

This commit is contained in:
OMGeeky
2024-04-14 23:24:09 +02:00
parent 2f72a8a7c7
commit 50b8210cdb
17 changed files with 298 additions and 79 deletions

14
Cargo.lock generated
View File

@@ -510,6 +510,7 @@ dependencies = [
"google-drive3",
"lazy_static",
"serde",
"serde_json",
"tarpc",
"thiserror",
"tokio",
@@ -526,12 +527,14 @@ dependencies = [
"futures",
"futures-sink",
"gdriver-common",
"lazy_static",
"libc",
"serde",
"tarpc",
"thiserror",
"tokio",
"tracing",
"uzers",
]
[[package]]
@@ -539,6 +542,7 @@ name = "gdriver-common"
version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"confique",
"directories",
"futures",
@@ -1985,6 +1989,16 @@ dependencies = [
"percent-encoding 2.3.1",
]
[[package]]
name = "uzers"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d283dc7e8c901e79e32d077866eaf599156cbf427fffa8289aecc52c5c3f63"
dependencies = [
"libc",
"log",
]
[[package]]
name = "valuable"
version = "0.1.0"

View File

@@ -12,10 +12,11 @@ serde.workspace = true
tarpc.workspace = true
futures.workspace = true
chrono.workspace = true
lazy_static = "1.4.0"
lazy_static.workspace = true
thiserror = "1.0.56"
google-drive3 = "5.0.4"
const_format = "0.2"
serde_json = "1.0.115"
[dependencies.gdriver-common]
path = "../gdriver-common"

View File

@@ -3,6 +3,8 @@ use std::collections::HashMap;
use crate::drive::google_drive::GoogleDrive;
use crate::path_resolver::PathResolver;
use chrono::{DateTime, Utc};
use gdriver_common::drive_structure::meta::{write_metadata_file, write_metadata_file_to_path};
use gdriver_common::ipc::gdriver_service::ReadDirResult;
use crate::prelude::*;
mod google_drive;
@@ -36,19 +38,29 @@ impl Drive {
#[instrument(skip(self))]
pub async fn get_all_file_metas(&mut self) -> Result<()> {
if self.offline_mode {
info!("Offline mode, skipping get_all_file_metas");
//TODO: load from local storage
return Ok(());
}
let has_existing_token = self.google_drive.has_local_change_token().await;
//TODO: show an error when offline and no local data exists
if !has_existing_token {
//only get start token & data if we shouldn't have it
//only get start token & data if this is the first time & we don't have it
self.google_drive.get_change_start_token().await?;
let x = self.google_drive.get_all_file_metas().await?;
dbg!(&x);
let files = self.google_drive.get_all_file_metas().await?;
self.path_resolver.reset()?;
for file in files {
for parent in file.parents.clone() {
let relation_data = ReadDirResult {
id: file.id.clone().into(),
name: file.name.clone(),
kind: file.kind.clone(),
};
self.path_resolver
.add_relationship(parent.into(), relation_data)?;
}
let meta = file.into_meta()?;
write_metadata_file(&meta)?;
}
} else {
//TODO: get file metas from local storage
self.path_resolver.load_from_disk()?;
}
Ok(())

View File

@@ -1,6 +1,8 @@
use crate::prelude::*;
use chrono::{DateTime, Utc};
use const_format::formatcp;
use gdriver_common::drive_structure::meta::{FileKind, FileState, Metadata};
use gdriver_common::time_utils::datetime_to_timestamp;
use gdriver_common::{ipc::gdriver_service::SETTINGS, prelude::*};
use google_drive3::api::File;
use google_drive3::{
@@ -14,32 +16,60 @@ use std::any::type_name;
use std::fmt::{Debug, Display, Formatter};
use tokio::fs;
const FIELDS_FILE: &'static str = "id, name, size, mimeType, kind, md5Checksum, parents, trashed, createdTime, modifiedTime, viewedByMeTime";
const FIELDS_FILE: &'static str = "id, name, size, mimeType, kind, md5Checksum, parents, trashed, createdTime, modifiedTime, viewedByMeTime, capabilities";
#[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Serialize, Deserialize, Clone, Hash)]
pub struct FileData {
pub id: String,
pub name: String,
pub size: Option<i64>,
pub mime_type: String,
pub kind: String,
pub kind: FileKind,
pub md5_checksum: Option<String>,
pub parents: Option<Vec<String>>,
pub parents: Vec<String>,
pub trashed: Option<bool>,
pub created_time: Option<DateTime<Utc>>,
pub modified_time: Option<DateTime<Utc>>,
pub viewed_by_me_time: Option<DateTime<Utc>>,
}
impl FileData {
pub(crate) fn into_meta(self) -> Result<Metadata> {
let last_modified = datetime_to_timestamp(self.modified_time.unwrap_or_default())?;
Ok(Metadata {
id: self.id.into(),
kind: self.kind,
size: self.size.unwrap_or_default() as u64,
last_accessed: datetime_to_timestamp(self.viewed_by_me_time.unwrap_or_default())?,
last_modified,
extra_attributes: Default::default(),
state: FileState::MetadataOnly,
permissions: 0, //TODO: parse permissions
last_metadata_changed: last_modified,
})
}
}
impl FileData {
fn convert_from_api_file(file: File) -> Self {
let kind = file.kind.unwrap_or_default();
info!(
"Converting file with id {:?} with parent: {:?}",
file.id, file.parents
);
let kind = match kind.as_str() {
"drive#file" => FileKind::File,
"drive#folder" => FileKind::Directory,
"drive#link" => FileKind::Symlink,
_ => todo!("Handle kind: {}", kind),
};
Self {
id: file.id.unwrap_or_default(),
name: file.name.unwrap_or_default(),
size: file.size,
mime_type: file.mime_type.unwrap_or_default(),
kind: file.kind.unwrap_or_default(),
kind,
md5_checksum: file.md5_checksum,
parents: file.parents,
parents: file.parents.unwrap_or(vec![ROOT_ID.0.clone()]),
trashed: file.trashed,
created_time: file.created_time,
modified_time: file.modified_time,
@@ -55,6 +85,7 @@ const FIELDS_CHANGE: &str = formatcp!(
pub struct GoogleDrive {
hub: DriveHub<HttpsConnector<HttpConnector>>,
changes_start_page_token: Option<String>,
root_alt_id: DriveId,
}
impl GoogleDrive {
@@ -79,6 +110,10 @@ impl GoogleDrive {
body.files
.unwrap_or_default()
.into_iter()
.map(|mut f| {
self.map_in_file(Some(&mut f));
f
})
.map(FileData::convert_from_api_file),
);
} else {
@@ -117,13 +152,31 @@ impl GoogleDrive {
);
let hub = DriveHub::new(http_client, auth);
let drive = GoogleDrive {
let mut drive = GoogleDrive {
hub,
changes_start_page_token: None,
root_alt_id: ROOT_ID.clone(),
};
trace!("Successfully initialized {}", drive);
info!("Updating ROOT alt");
drive.update_alt_root().await?;
info!("Updated ROOT alt to {}", drive.root_alt_id);
trace!("Successfully initialized {:?}", drive);
Ok(drive)
}
async fn update_alt_root(&mut self) -> Result<()> {
let (response, body) = self
.hub
.files()
.get(ROOT_ID.as_ref())
.param("fields", "id")
.doit()
.await?;
if response.status().is_success() {
self.root_alt_id = body.id.unwrap_or(ROOT_ID.to_string()).into();
}
Ok(())
}
#[instrument]
pub(crate) async fn ping(&self) -> Result<()> {
let (response, body) = self
@@ -183,9 +236,13 @@ impl GoogleDrive {
return Err("Could not get changes".into());
}
}
changes
.iter_mut()
.for_each(|change| self.map_id_in_change(change));
trace!("Got {} changes", changes.len());
Ok(changes)
}
async fn set_change_start_token(&mut self, token: String) -> Result<()> {
info!("Setting start page token: {}", token);
fs::write(SETTINGS.get_changes_file_path(), token.clone()).await?;
@@ -245,13 +302,41 @@ impl GoogleDrive {
}
}
//endregion
}
//region map alt_root_id to ROOT_ID
fn map_id_in_change(&self, i: &mut Change) {
i.file_id.as_mut().map(|id| self.map_id(id));
let file = i.file.as_mut();
self.map_in_file(file);
}
fn map_in_file(&self, file: Option<&mut File>) {
file.map(|f| {
f.parents.as_mut().map(|parents| {
parents
.iter_mut()
.inspect(|i| println!("parent: {i}"))
.for_each(|id| self.map_id(id))
});
f.id.as_mut().map(|id| self.map_id(id));
});
}
fn map_id(&self, id: &mut String) {
if self.root_alt_id.0.eq(id) {
info!("replacing {id} with {}", ROOT_ID.as_ref());
*id = ROOT_ID.0.clone();
} else {
info!("{id} did not match {}", self.root_alt_id);
}
}
//endregion
}
//region debug & display traits
impl Debug for GoogleDrive {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct(type_name::<GoogleDrive>())
.field("changes_start_page_token", &self.changes_start_page_token)
.field("root_alt_id", &self.root_alt_id)
.finish()
}
}

View File

@@ -24,7 +24,7 @@ async fn main() -> Result<()> {
SETTINGS.initialize_dirs()?;
let root_meta_file = SETTINGS.get_metadata_file_path(&ROOT_ID);
let root_meta = meta::Metadata::root();
meta::write_metadata_file(&root_meta_file, &root_meta)?;
meta::write_metadata_file_to_path(&root_meta_file, &root_meta)?;
// sample::main().await?;
service::start().await?;

View File

@@ -1,12 +1,14 @@
use crate::drive::Drive;
use crate::prelude::*;
use gdriver_common::ipc::gdriver_service::ReadDirResult;
use gdriver_common::ipc::gdriver_service::{ReadDirResult, SETTINGS};
use gdriver_common::path_resolve_error::PathResolveError;
use gdriver_common::prelude::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::File;
use std::path::Path;
#[derive(Eq, PartialEq, Debug, Clone)]
#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
pub struct PathResolver {
/// A map of children to their parents
parents: HashMap<DriveId, Vec<DriveId>>,
@@ -14,6 +16,12 @@ pub struct PathResolver {
children: HashMap<DriveId, Vec<ReadDirResult>>,
}
impl PathResolver {
pub(crate) fn get_children(&self, id: &DriveId) -> Result<&Vec<ReadDirResult>> {
self.children.get(id).ok_or("Item with ID not found".into())
}
}
impl PathResolver {
pub fn new() -> Self {
Self {
@@ -47,11 +55,14 @@ impl PathResolver {
None
}
async fn update_from_drive(&mut self, drive: &Drive) -> Result<()> {
todo!()
pub fn reset(&mut self) -> Result<()> {
self.parents.clear();
self.children.clear();
self.write_to_disk()?;
Ok(())
}
/// Add a relationship between a parent and a child
pub(crate) fn add_relationship(&mut self, parent: DriveId, entry: ReadDirResult) {
/// Add a relationship between a parent and a child and write to disk
pub(crate) fn add_relationship(&mut self, parent: DriveId, entry: ReadDirResult) -> Result<()> {
match self.parents.get_mut(&entry.id) {
Some(x) => x.push(parent.clone()),
None => {
@@ -64,14 +75,39 @@ impl PathResolver {
self.children.insert(parent.clone(), vec![entry.clone()]);
}
}
self.write_to_disk()?;
Ok(())
}
/// Remove the relationship between a parent and a child
pub(crate) fn remove_relationship(&mut self, parent: DriveId, entry: ReadDirResult) {
/// Remove the relationship between a parent and a child and write to disk
pub(crate) fn remove_relationship(
&mut self,
parent: DriveId,
entry: ReadDirResult,
) -> Result<()> {
self.parents
.get_mut(&entry.id)
.map(|x| x.retain(|e| e != &parent));
self.children
.get_mut(&parent)
.map(|x| x.retain(|e| e.id != entry.id));
self.write_to_disk()?;
Ok(())
}
pub fn write_to_disk(&self) -> Result<()> {
let path = SETTINGS.get_path_resolver_file_path();
let reader = File::create(path)?;
Ok(serde_json::to_writer_pretty(reader, self)?)
}
pub fn read_from_disk() -> Result<Self> {
let path = SETTINGS.get_path_resolver_file_path();
let reader = File::open(path)?;
Ok(serde_json::from_reader(reader)?)
}
pub fn load_from_disk(&mut self) -> Result<()> {
let other = Self::read_from_disk()?;
self.parents = other.parents;
self.children = other.children;
Ok(())
}
}

View File

@@ -87,16 +87,24 @@ impl GDriverService for GdriverServer {
context: Context,
id: DriveId,
) -> StdResult<Vec<ReadDirResult>, GetFileListError> {
Err(GetFileListError::Other)
self.list_files_in_directory_with_offset(context, id, 0)
.await
}
#[instrument(skip(self, _context))]
async fn list_files_in_directory_with_offset(
self,
context: Context,
_context: Context,
id: DriveId,
offset: u64,
offset: usize,
) -> StdResult<Vec<ReadDirResult>, GetFileListError> {
Err(GetFileListError::Other)
let drive = self.drive.lock().await;
info!("Listing files in dir");
let children = drive
.path_resolver
.get_children(&id)
.map_err(|_| GetFileListError::NotFound)?
.clone();
Ok(children.into_iter().skip(offset).collect())
}
async fn mark_file_as_deleted(

View File

@@ -10,6 +10,7 @@ tarpc.workspace = true
tokio.workspace = true
tracing.workspace = true
serde.workspace = true
lazy_static.workspace = true
anyhow = "1.0"
futures-sink = "0.3.30"
fuser = "0.14.0"
@@ -17,6 +18,7 @@ bimap = "0.6"
libc = "0.2.152"
futures = "0.3"
thiserror = "1.0.56"
uzers = "0.11"
[dependencies.gdriver-common]
path = "../gdriver-common"

View File

@@ -10,6 +10,7 @@ use gdriver_common::drive_structure::drive_id::ROOT_ID;
use gdriver_common::ipc::gdriver_service::errors::GDriverServiceError;
use gdriver_common::ipc::gdriver_service::GDriverServiceClient;
use gdriver_common::ipc::gdriver_service::SETTINGS;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::ffi::{OsStr, OsString};
@@ -22,6 +23,15 @@ mod macros;
//TODO2: Decide if this is a good TTL
const TTL: Duration = Duration::from_secs(2);
const GROUP_NAME: &str = "gdriver2";
lazy_static! {
pub static ref USER_ID: u32 = uzers::get_current_uid();
pub static ref GDRIVER_GROUP_ID: u32 = uzers::get_group_by_name(GROUP_NAME)
.expect(&format!(
"Please create the group '{GROUP_NAME}' and add the user to it."
))
.gid();
}
type Inode = u64;
@@ -321,7 +331,7 @@ mod utils {
let res = send_request!(fs.gdriver_client.list_files_in_directory_with_offset(
current_context(),
id,
offset as u64
offset as usize
))?
.map_err(GDriverServiceError::from)?;
Ok(res)

View File

@@ -1,10 +1,13 @@
use crate::filesystem::{GDRIVER_GROUP_ID, USER_ID};
use crate::prelude::*;
use fuser::FileType;
use gdriver_common::drive_structure::meta::{read_metadata_file, FileKind, Metadata};
use gdriver_common::drive_structure::meta::{read_metadata_file, FileKind, Metadata, TIMESTAMP};
use gdriver_common::time_utils;
use gdriver_common::time_utils::time_from_system_time;
use std::collections::BTreeMap;
use std::os::raw::c_int;
use std::path::Path;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use std::time::SystemTime;
use tarpc::serde::{Deserialize, Serialize};
type Inode = u64;
@@ -78,36 +81,17 @@ fn time_now() -> (i64, u32) {
time_from_system_time(&SystemTime::now())
}
fn system_time_from_time(secs: i64, nsecs: u32) -> SystemTime {
if secs >= 0 {
UNIX_EPOCH + Duration::new(secs as u64, nsecs)
} else {
UNIX_EPOCH - Duration::new((-secs) as u64, nsecs)
}
}
fn time_from_system_time(system_time: &SystemTime) -> (i64, u32) {
// Convert to signed 64-bit time with epoch at 0
match system_time.duration_since(UNIX_EPOCH) {
Ok(duration) => (duration.as_secs() as i64, duration.subsec_nanos()),
Err(before_epoch_error) => (
-(before_epoch_error.duration().as_secs() as i64),
before_epoch_error.duration().subsec_nanos(),
),
}
}
#[derive(Serialize, Deserialize)]
pub(crate) struct InodeAttributes {
pub inode: Inode,
pub open_file_handles: u64, // Ref count of open file handles to this inode
pub size: u64,
pub last_accessed: (i64, u32),
pub last_modified: (i64, u32),
pub last_metadata_changed: (i64, u32),
pub last_accessed: TIMESTAMP,
pub last_modified: TIMESTAMP,
pub last_metadata_changed: TIMESTAMP,
pub kind: FileKind,
// Permissions and special mode bits
pub mode: u16,
pub permissions: u16,
pub hardlinks: u32,
pub uid: u32,
pub gid: u32,
@@ -126,11 +110,11 @@ pub(crate) fn read_inode_attributes_from_metadata(
last_modified: metadata.last_modified,
last_metadata_changed: metadata.last_metadata_changed,
kind: metadata.kind,
mode: metadata.mode,
hardlinks: metadata.hardlinks,
uid: metadata.uid,
gid: metadata.gid,
xattrs: metadata.xattrs,
permissions: metadata.permissions,
hardlinks: 0,
uid: *USER_ID,
gid: *GDRIVER_GROUP_ID,
xattrs: metadata.extra_attributes,
}
}
pub(crate) fn read_inode_attributes_from_meta_file(
@@ -152,15 +136,21 @@ impl From<InodeAttributes> for fuser::FileAttr {
ino: attrs.inode,
size: attrs.size,
blocks: (attrs.size + BLOCK_SIZE - 1) / BLOCK_SIZE,
atime: system_time_from_time(attrs.last_accessed.0, attrs.last_accessed.1),
mtime: system_time_from_time(attrs.last_modified.0, attrs.last_modified.1),
ctime: system_time_from_time(
atime: time_utils::system_time_from_timestamp(
attrs.last_accessed.0,
attrs.last_accessed.1,
),
mtime: time_utils::system_time_from_timestamp(
attrs.last_modified.0,
attrs.last_modified.1,
),
ctime: time_utils::system_time_from_timestamp(
attrs.last_metadata_changed.0,
attrs.last_metadata_changed.1,
),
crtime: SystemTime::UNIX_EPOCH,
kind: attrs.kind.into_ft(),
perm: attrs.mode,
perm: attrs.permissions,
nlink: attrs.hardlinks,
uid: attrs.uid,
gid: attrs.gid,

View File

@@ -13,6 +13,7 @@ type Result<T> = StdResult<T, Box<dyn Error>>;
#[tokio::main]
async fn main() -> Result<()> {
gdriver_common::tracing_setup::init_tracing();
check_setup()?;
// service::start().await?;
let mount_options = &[MountOption::RW];
let (tx, rx) = channel(1);
@@ -26,6 +27,16 @@ async fn main() -> Result<()> {
.await?;
Ok(())
}
fn check_setup() -> Result<()> {
// let _ = std::env::var("GOOGLE_APPLICATION_CREDENTIALS")
// .map_err(|_| "GOOGLE_APPLICATION_CREDENTIALS env var not set")?;
let _ = &*filesystem::GDRIVER_GROUP_ID;
let _ = &*filesystem::USER_ID;
Ok(())
}
pub mod prelude;
mod sample;

View File

@@ -12,6 +12,7 @@ tarpc.workspace = true
tokio.workspace = true
futures.workspace = true
lazy_static.workspace = true
chrono.workspace = true
confique = { version = "0.2" }
thiserror = "1.0"
anyhow = "1.0"

View File

@@ -1,38 +1,38 @@
use crate::ipc::gdriver_service::SETTINGS;
use crate::prelude::*;
use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, fs::File, path::Path};
/// Timestamp is a tuple of (seconds, nanoseconds)
///
/// This is a duration since the Unix epoch in seconds + nanoseconds.
pub type TIMESTAMP = (i64, u32);
#[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Serialize, Deserialize, Clone, Hash)]
pub struct Metadata {
pub id: DriveId,
pub state: FileState,
pub size: u64,
pub last_accessed: TIMESTAMP,
pub last_modified: TIMESTAMP,
pub last_metadata_changed: TIMESTAMP,
pub kind: FileKind,
pub mode: u16,
pub hardlinks: u32,
pub uid: u32,
pub gid: u32,
pub xattrs: BTreeMap<Vec<u8>, Vec<u8>>,
pub permissions: u16,
pub extra_attributes: BTreeMap<Vec<u8>, Vec<u8>>,
}
impl Metadata {
pub fn root() -> Self {
Self {
id: ROOT_ID.clone(),
state: FileState::Root,
size: 0,
last_accessed: (0, 0),
last_modified: (0, 0),
last_metadata_changed: (0, 0),
kind: FileKind::Directory,
mode: 0,
hardlinks: 0,
uid: 0,
gid: 0,
xattrs: Default::default(),
permissions: 0,
extra_attributes: Default::default(),
}
}
}
@@ -42,7 +42,11 @@ pub fn read_metadata_file(path: &Path) -> Result<Metadata> {
let reader = File::open(path)?;
Ok(serde_json::from_reader(reader)?)
}
pub fn write_metadata_file(path: &Path, metadata: &Metadata) -> Result<()> {
pub fn write_metadata_file(metadata: &Metadata) -> Result<()> {
let path = SETTINGS.get_metadata_file_path(&metadata.id);
write_metadata_file_to_path(&path, metadata)
}
pub fn write_metadata_file_to_path(path: &Path, metadata: &Metadata) -> Result<()> {
debug!("Writing metadata file: {:?}", path);
let reader = File::create(path)?;
Ok(serde_json::to_writer(reader, metadata)?)

View File

@@ -24,7 +24,7 @@ pub trait GDriverService {
) -> StdResult<Vec<ReadDirResult>, GetFileListError>;
async fn list_files_in_directory_with_offset(
id: DriveId,
offset: u64,
offset: usize,
) -> StdResult<Vec<ReadDirResult>, GetFileListError>;
async fn mark_file_as_deleted(id: DriveId) -> StdResult<(), MarkFileAsDeletedError>;
async fn mark_file_for_keeping_local(
@@ -148,6 +148,8 @@ pub mod errors {
pub enum GetFileListError {
#[error("Other")]
Other,
#[error("Element with ID not found")]
NotFound,
}
#[derive(Debug, Serialize, Deserialize, thiserror::Error)]

View File

@@ -45,6 +45,9 @@ impl GDriverSettings {
pub fn get_changes_file_path(&self) -> PathBuf {
self.data_path.join("changes.txt")
}
pub fn get_path_resolver_file_path(&self) -> PathBuf {
self.data_path.join("relations.json")
}
pub fn get_metadata_file_path(&self, id: &DriveId) -> PathBuf {
self.metadata_path.join(id.as_ref()).with_extension("meta")

View File

@@ -7,4 +7,5 @@ pub mod drive_structure;
pub mod ipc;
pub mod path_resolve_error;
pub mod project_dirs;
pub mod time_utils;
pub mod tracing_setup;

View File

@@ -0,0 +1,39 @@
use crate::drive_structure::meta::TIMESTAMP;
use crate::prelude;
use chrono::offset::Utc;
use chrono::DateTime;
use chrono::TimeZone;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub fn system_time_to_timestamp(time: SystemTime) -> prelude::Result<TIMESTAMP> {
let secs = time.duration_since(UNIX_EPOCH)?.as_secs() as i64;
let nsecs = time.duration_since(UNIX_EPOCH)?.subsec_nanos();
Ok((secs, nsecs))
}
pub fn datetime_to_timestamp(time: DateTime<Utc>) -> prelude::Result<TIMESTAMP> {
let unix_epoch: DateTime<Utc> = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap();
let timestamp = time.signed_duration_since(unix_epoch);
let secs = timestamp.num_seconds();
let nsecs = timestamp.subsec_nanos() as u32;
Ok((secs, nsecs))
}
pub fn system_time_from_timestamp(secs: i64, nsecs: u32) -> SystemTime {
if secs >= 0 {
UNIX_EPOCH + Duration::new(secs as u64, nsecs)
} else {
UNIX_EPOCH - Duration::new((-secs) as u64, nsecs)
}
}
pub fn time_from_system_time(system_time: &SystemTime) -> (i64, u32) {
// Convert to signed 64-bit time with epoch at 0
match system_time.duration_since(UNIX_EPOCH) {
Ok(duration) => (duration.as_secs() as i64, duration.subsec_nanos()),
Err(before_epoch_error) => (
-(before_epoch_error.duration().as_secs() as i64),
before_epoch_error.duration().subsec_nanos(),
),
}
}