2 Commits

Author SHA1 Message Date
9e3a9e3dbd fix error path
All checks were successful
continuous-integration/drone/push Build is passing
2025-08-25 18:09:14 -07:00
c3b52781bd DownloadCreationsHistory
All checks were successful
continuous-integration/drone/push Build is passing
2025-08-25 18:04:40 -07:00

View File

@@ -1169,10 +1169,10 @@ async fn get_creations_pages(
loop{
let mut page=context.get_creations_page(&config).await?;
asset_list.append(&mut page.data);
config.cursor=page.nextPageCursor;
if config.cursor.is_none(){
if page.nextPageCursor.is_none(){
break;
}
config.cursor=page.nextPageCursor;
}
Ok(())
}
@@ -1185,34 +1185,15 @@ async fn download_creations_pages_from_checkpoint(context:&CookieContext,owner:r
let (mut asset_list,mut config)=if continue_from_cursor{
// load state from files
let (versions,cursor)=tokio::join!(
let (versions,cursor)=tokio::try_join!(
tokio::fs::read(versions_path.as_path()),
tokio::fs::read_to_string(cursor_path.as_path()),
);
// allow versions to not exist
let (versions,cursor)=match (versions,cursor){
// continue downloading
(Ok(versions),Ok(cursor))=>(serde_json::from_slice(&versions)?,Some(cursor)),
// already downloaded
(Ok(versions),Err(e)) if matches!(e.kind(),std::io::ErrorKind::NotFound)=>return Ok(serde_json::from_slice(&versions)?),
// not downloaded
(Err(e),result) if matches!(e.kind(),std::io::ErrorKind::NotFound)=>{
match result{
Ok(_)=>{},
Err(e) if matches!(e.kind(),std::io::ErrorKind::NotFound)=>{},
Err(e)=>Err(e)?,
}
(Vec::new(),None)
},
// other errors
(Ok(_),Err(e))=>Err(e)?,
(Err(e),_)=>Err(e)?,
};
)?;
(
versions,
serde_json::from_slice(&versions)?,
rbx_asset::cookie::CreationsPageRequest{
owner,
cursor,
cursor:Some(cursor),
}
)
}else{
@@ -1233,14 +1214,6 @@ async fn download_creations_pages_from_checkpoint(context:&CookieContext,owner:r
println!("writing cursor state...");
// there was a problem, write out cursor
tokio::fs::write(cursor_path,cursor).await?;
}else{
// no cursor
if let Err(e)=tokio::fs::remove_file(cursor_path).await{
match e.kind(){
std::io::ErrorKind::NotFound=>println!("Cannot delete cursor: file not found"),
_=>Err(e)?,
}
}
}
Ok(())
};
@@ -1327,6 +1300,7 @@ async fn download_user_inventory_json(cookie:Cookie,user_id:u64,output_folder:Pa
Ok(())
}
/// Download all versions of all assets created by a group or user. The output is written to a folder structure in the output directory.
#[derive(Args)]
struct DownloadCreationsHistorySubcommand{
@@ -1373,6 +1347,14 @@ impl DownloadCreationsHistorySubcommand{
).await
}
}
#[derive(Debug)]
struct NoLocations;
impl std::fmt::Display for NoLocations{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f,"{self}")
}
}
impl std::error::Error for NoLocations{}
async fn download_creations_history(cookie:Cookie,api_key:ApiKey,owner:rbx_asset::cookie::Owner,output_folder:PathBuf,r#continue:bool)->AResult<()>{
let cookie_context=CookieContext::new(cookie);
@@ -1394,15 +1376,6 @@ async fn download_creations_history(cookie:Cookie,api_key:ApiKey,owner:rbx_asset
.try_collect().await?
};
#[expect(dead_code)]
#[derive(Debug)]
enum Error<'a>{
NoLocations(Job<'a>),
GetVersionLocationError(rbx_asset::cloud::GetError),
GetError(rbx_asset::cloud::GetError),
Io(std::io::Error),
}
#[derive(Clone,Copy,Debug)]
struct Job<'a>{
path:&'a PathBuf,
asset_id:u64,
@@ -1440,29 +1413,21 @@ async fn download_creations_history(cookie:Cookie,api_key:ApiKey,owner:rbx_asset
println!("Completed jobs list. Number of jobs: {}",job_list.len());
futures::stream::iter(job_list).map(async|job|{
let mut dest=job.path.to_owned();
dest.push(format!("{}_v{}.rbxl",job.asset_id,job.asset_version));
//if the file already exists, don't try downloading it again
if tokio::fs::try_exists(dest.as_path()).await.map_err(Error::Io)?{
return Ok(());
}
let location=cloud_context.get_asset_version_location(rbx_asset::cloud::GetAssetVersionRequest{
asset_id:job.asset_id,
version:job.asset_version,
}).await.map_err(Error::GetVersionLocationError)?;
let location=location.location.ok_or(Error::NoLocations(job))?;
let downloaded=cloud_context.get_asset(&location).await.map_err(Error::GetError)?;
tokio::fs::write(dest,downloaded.to_vec().map_err(Error::Io)?).await.map_err(Error::Io)?;
Ok(())
}).await?;
let location=location.location.ok_or(NoLocations)?;
let downloaded=cloud_context.get_asset(&location).await?;
Ok((job,downloaded))
})
.buffer_unordered(CONCURRENT_REQUESTS)
.for_each(async|result|{
match result{
Ok(())=>{},
Err(Error::NoLocations(job))=>println!("Job failed due to no locations: asset_id={} version={}",job.asset_id,job.asset_version),
Err(e)=>println!("Error: {e:?}"),
}
}).await;
.try_for_each(async|(job,downloaded)|{
let mut dest=job.path.to_owned();
dest.push(format!("{}_v{}.rbxl",job.asset_id,job.asset_version));
tokio::fs::write(dest,downloaded.to_vec()?).await?;
Ok::<_,anyhow::Error>(())
}).await?;
println!("All jobs complete.");