2021-07-09 04:37:08 +00:00
|
|
|
use anyhow::Result;
|
2021-06-03 16:30:04 +00:00
|
|
|
use compress_tools::{list_archive_files, uncompress_archive_file};
|
|
|
|
use dotenv::dotenv;
|
|
|
|
use skyrim_cell_dump::parse_plugin;
|
|
|
|
use sqlx::postgres::PgPoolOptions;
|
|
|
|
use std::convert::TryInto;
|
|
|
|
use std::env;
|
|
|
|
use std::fs::OpenOptions;
|
|
|
|
use std::io::Seek;
|
|
|
|
use std::io::SeekFrom;
|
2021-07-09 04:37:08 +00:00
|
|
|
use std::time::Duration;
|
|
|
|
use tempfile::tempdir;
|
2021-06-03 16:30:04 +00:00
|
|
|
use tokio::io::{AsyncReadExt, AsyncSeekExt};
|
2021-06-14 02:30:40 +00:00
|
|
|
use tokio::time::sleep;
|
2021-07-03 20:00:18 +00:00
|
|
|
use unrar::Archive;
|
2021-06-03 16:30:04 +00:00
|
|
|
use zip::write::{FileOptions, ZipWriter};
|
|
|
|
|
2021-07-09 01:19:16 +00:00
|
|
|
mod models;
|
2021-07-09 04:37:08 +00:00
|
|
|
mod nexus_api;
|
|
|
|
mod nexus_scraper;
|
2021-07-09 01:19:16 +00:00
|
|
|
|
|
|
|
use models::cell::insert_cell;
|
|
|
|
use models::file::{insert_file, File};
|
|
|
|
use models::game::insert_game;
|
|
|
|
use models::game_mod::{get_mod_by_nexus_mod_id, insert_mod, Mod};
|
|
|
|
use models::plugin::insert_plugin;
|
|
|
|
use models::plugin_cell::insert_plugin_cell;
|
2021-07-09 04:37:08 +00:00
|
|
|
use nexus_api::{GAME_ID, GAME_NAME};
|
2021-06-14 02:30:40 +00:00
|
|
|
|
2021-07-03 20:00:18 +00:00
|
|
|
async fn process_plugin<W>(
|
2021-07-04 04:01:59 +00:00
|
|
|
plugin_buf: &mut [u8],
|
2021-07-03 20:00:18 +00:00
|
|
|
pool: &sqlx::Pool<sqlx::Postgres>,
|
|
|
|
plugin_archive: &mut ZipWriter<W>,
|
|
|
|
db_file: &File,
|
|
|
|
mod_obj: &Mod,
|
|
|
|
file_name: &str,
|
|
|
|
) -> Result<()>
|
2021-07-04 04:01:59 +00:00
|
|
|
where
|
|
|
|
W: std::io::Write + std::io::Seek,
|
2021-07-03 20:00:18 +00:00
|
|
|
{
|
|
|
|
let plugin = parse_plugin(&plugin_buf)?;
|
|
|
|
let hash = seahash::hash(&plugin_buf);
|
|
|
|
let plugin_row = insert_plugin(
|
|
|
|
&pool,
|
2021-07-09 04:37:08 +00:00
|
|
|
&db_file.name,
|
2021-07-03 20:00:18 +00:00
|
|
|
hash as i64,
|
|
|
|
db_file.id,
|
|
|
|
Some(plugin.header.version as f64),
|
|
|
|
plugin.header.author,
|
|
|
|
plugin.header.description,
|
|
|
|
Some(
|
|
|
|
&plugin
|
|
|
|
.header
|
|
|
|
.masters
|
|
|
|
.iter()
|
|
|
|
.map(|s| s.to_string())
|
|
|
|
.collect::<Vec<String>>(),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
for cell in plugin.cells {
|
|
|
|
let cell_row = insert_cell(
|
|
|
|
&pool,
|
|
|
|
cell.form_id.try_into().unwrap(),
|
|
|
|
cell.x,
|
|
|
|
cell.y,
|
|
|
|
cell.is_persistent,
|
|
|
|
)
|
|
|
|
.await?;
|
2021-07-04 04:01:59 +00:00
|
|
|
insert_plugin_cell(&pool, plugin_row.id, cell_row.id, cell.editor_id).await?;
|
2021-07-03 20:00:18 +00:00
|
|
|
}
|
|
|
|
plugin_archive.start_file(
|
|
|
|
format!(
|
|
|
|
"{}/{}/{}/{}",
|
2021-07-09 04:37:08 +00:00
|
|
|
GAME_NAME, mod_obj.nexus_mod_id, db_file.nexus_file_id, file_name
|
2021-07-03 20:00:18 +00:00
|
|
|
),
|
|
|
|
FileOptions::default(),
|
|
|
|
)?;
|
2021-07-04 04:01:59 +00:00
|
|
|
|
|
|
|
let mut reader = std::io::Cursor::new(&plugin_buf);
|
|
|
|
std::io::copy(&mut reader, plugin_archive)?;
|
2021-07-03 20:00:18 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-07-09 04:37:08 +00:00
|
|
|
fn initialize_plugins_archive(mod_id: i32, file_id: i32) -> Result<()> {
|
|
|
|
let mut plugins_archive = ZipWriter::new(
|
|
|
|
OpenOptions::new()
|
|
|
|
.write(true)
|
|
|
|
.create(true)
|
|
|
|
.open("plugins.zip")?,
|
|
|
|
);
|
|
|
|
plugins_archive.add_directory(
|
|
|
|
format!("{}/{}/{}", GAME_NAME, mod_id, file_id),
|
|
|
|
FileOptions::default(),
|
|
|
|
)?;
|
|
|
|
plugins_archive.finish()?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-06-03 16:30:04 +00:00
|
|
|
#[tokio::main]
|
|
|
|
pub async fn main() -> Result<()> {
|
|
|
|
dotenv().ok();
|
|
|
|
let pool = PgPoolOptions::new()
|
|
|
|
.max_connections(5)
|
|
|
|
.connect(&env::var("DATABASE_URL")?)
|
|
|
|
.await?;
|
|
|
|
let game = insert_game(&pool, GAME_NAME, GAME_ID as i32).await?;
|
|
|
|
let client = reqwest::Client::new();
|
|
|
|
|
2021-06-14 02:30:40 +00:00
|
|
|
let mut page: i32 = 1;
|
2021-07-07 03:29:09 +00:00
|
|
|
let mut has_next_page = true;
|
2021-06-14 02:30:40 +00:00
|
|
|
|
2021-07-07 03:29:09 +00:00
|
|
|
while has_next_page {
|
2021-07-09 04:37:08 +00:00
|
|
|
let mod_list_resp = nexus_scraper::get_mod_list_page(&client, page).await?;
|
|
|
|
let scraped = mod_list_resp.scrape_mods()?;
|
2021-07-07 03:29:09 +00:00
|
|
|
|
2021-07-09 04:37:08 +00:00
|
|
|
has_next_page = scraped.has_next_page;
|
|
|
|
let mut mods = Vec::new();
|
|
|
|
for scraped_mod in scraped.mods {
|
|
|
|
if let None = get_mod_by_nexus_mod_id(&pool, scraped_mod.nexus_mod_id).await? {
|
2021-07-07 03:29:09 +00:00
|
|
|
mods.push(
|
2021-07-09 04:37:08 +00:00
|
|
|
insert_mod(
|
|
|
|
&pool,
|
|
|
|
scraped_mod.name,
|
|
|
|
scraped_mod.nexus_mod_id,
|
|
|
|
scraped_mod.author,
|
|
|
|
scraped_mod.category,
|
|
|
|
scraped_mod.desc,
|
|
|
|
game.id,
|
|
|
|
)
|
|
|
|
.await?,
|
2021-07-07 03:29:09 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dbg!(mods.len());
|
2021-06-14 02:30:40 +00:00
|
|
|
|
2021-07-09 04:37:08 +00:00
|
|
|
for db_mod in mods {
|
|
|
|
dbg!(&db_mod.name);
|
|
|
|
let files_resp = nexus_api::files::get(&client, db_mod.nexus_mod_id).await?;
|
|
|
|
// TODO: download other files than just MAIN files
|
|
|
|
// let files = files.into_iter().filter(|file| {
|
|
|
|
// if let Some(category_name) = file.get("category_name") {
|
|
|
|
// category_name.as_str() == Some("MAIN")
|
|
|
|
// } else {
|
|
|
|
// false
|
|
|
|
// }
|
|
|
|
// });
|
|
|
|
if let Some(duration) = files_resp.wait {
|
2021-06-14 02:30:40 +00:00
|
|
|
sleep(duration).await;
|
|
|
|
}
|
|
|
|
|
2021-07-09 04:37:08 +00:00
|
|
|
for api_file in files_resp.files()? {
|
2021-06-14 02:30:40 +00:00
|
|
|
let db_file = insert_file(
|
|
|
|
&pool,
|
2021-07-09 04:37:08 +00:00
|
|
|
api_file.name,
|
|
|
|
api_file.file_name,
|
|
|
|
api_file.file_id as i32,
|
|
|
|
db_mod.id,
|
|
|
|
api_file.category,
|
|
|
|
api_file.version,
|
|
|
|
api_file.mod_version,
|
|
|
|
api_file.uploaded_at,
|
2021-06-14 02:30:40 +00:00
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
|
2021-07-09 04:37:08 +00:00
|
|
|
let download_link_resp =
|
|
|
|
nexus_api::download_link::get(&client, db_mod.nexus_mod_id, api_file.file_id)
|
|
|
|
.await?;
|
|
|
|
let mut tokio_file = download_link_resp.download_file(&client).await?;
|
2021-06-14 02:30:40 +00:00
|
|
|
|
2021-07-09 04:37:08 +00:00
|
|
|
initialize_plugins_archive(db_mod.nexus_mod_id, db_file.nexus_file_id)?;
|
|
|
|
let mut plugins_archive = ZipWriter::new_append(
|
2021-06-14 02:30:40 +00:00
|
|
|
OpenOptions::new()
|
|
|
|
.read(true)
|
|
|
|
.write(true)
|
|
|
|
.open("plugins.zip")?,
|
|
|
|
)?;
|
2021-07-09 04:37:08 +00:00
|
|
|
|
2021-06-14 02:30:40 +00:00
|
|
|
let mut initial_bytes = [0; 8];
|
|
|
|
tokio_file.seek(SeekFrom::Start(0)).await?;
|
|
|
|
tokio_file.read_exact(&mut initial_bytes).await?;
|
|
|
|
let kind = infer::get(&initial_bytes).expect("unknown file type of file download");
|
2021-07-04 04:01:59 +00:00
|
|
|
dbg!(kind.mime_type());
|
2021-07-07 03:29:09 +00:00
|
|
|
|
|
|
|
tokio_file.seek(SeekFrom::Start(0)).await?;
|
|
|
|
let mut file = tokio_file.try_clone().await?.into_std().await;
|
|
|
|
let mut plugin_file_paths = Vec::new();
|
|
|
|
|
|
|
|
for file_name in list_archive_files(&file)? {
|
|
|
|
if file_name.ends_with(".esp")
|
|
|
|
|| file_name.ends_with(".esm")
|
|
|
|
|| file_name.ends_with(".esl")
|
|
|
|
{
|
|
|
|
plugin_file_paths.push(file_name);
|
2021-07-04 04:01:59 +00:00
|
|
|
}
|
2021-07-07 03:29:09 +00:00
|
|
|
}
|
2021-07-03 20:00:18 +00:00
|
|
|
|
2021-07-07 03:29:09 +00:00
|
|
|
for file_name in plugin_file_paths.iter() {
|
|
|
|
file.seek(SeekFrom::Start(0))?;
|
|
|
|
dbg!(file_name);
|
|
|
|
let mut buf = Vec::default();
|
|
|
|
match uncompress_archive_file(&mut file, &mut buf, file_name) {
|
|
|
|
Ok(_) => {
|
2021-07-04 04:01:59 +00:00
|
|
|
process_plugin(
|
|
|
|
&mut buf,
|
|
|
|
&pool,
|
2021-07-09 04:37:08 +00:00
|
|
|
&mut plugins_archive,
|
2021-07-04 04:01:59 +00:00
|
|
|
&db_file,
|
2021-07-09 04:37:08 +00:00
|
|
|
&db_mod,
|
2021-07-04 04:01:59 +00:00
|
|
|
file_name,
|
|
|
|
)
|
|
|
|
.await?;
|
2021-06-03 16:30:04 +00:00
|
|
|
}
|
2021-07-07 03:29:09 +00:00
|
|
|
Err(error) => {
|
|
|
|
dbg!(error);
|
|
|
|
if kind.mime_type() == "application/x-rar-compressed"
|
|
|
|
|| kind.mime_type() == "application/vnd.rar"
|
|
|
|
{
|
2021-07-09 04:37:08 +00:00
|
|
|
// Use unrar to uncompress the entire .rar file to avoid a bug with compress_tools panicking when uncompressing
|
|
|
|
// certain .rar files: https://github.com/libarchive/libarchive/issues/373
|
2021-07-07 03:29:09 +00:00
|
|
|
tokio_file.seek(SeekFrom::Start(0)).await?;
|
|
|
|
let mut file = tokio_file.try_clone().await?.into_std().await;
|
|
|
|
let temp_dir = tempdir()?;
|
|
|
|
let temp_file_path = temp_dir.path().join("download.rar");
|
|
|
|
let mut temp_file = std::fs::File::create(&temp_file_path)?;
|
|
|
|
std::io::copy(&mut file, &mut temp_file)?;
|
|
|
|
|
|
|
|
let mut plugin_file_paths = Vec::new();
|
|
|
|
let list =
|
|
|
|
Archive::new(temp_file_path.to_string_lossy().to_string())
|
|
|
|
.list();
|
|
|
|
if let Ok(list) = list {
|
|
|
|
for entry in list {
|
|
|
|
if let Ok(entry) = entry {
|
|
|
|
if entry.filename.ends_with(".esp")
|
|
|
|
|| entry.filename.ends_with(".esm")
|
|
|
|
|| entry.filename.ends_with(".esl")
|
|
|
|
{
|
|
|
|
plugin_file_paths.push(entry.filename);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if plugin_file_paths.len() > 0 {
|
|
|
|
let extract =
|
|
|
|
Archive::new(temp_file_path.to_string_lossy().to_string())
|
|
|
|
.extract_to(
|
|
|
|
temp_dir.path().to_string_lossy().to_string(),
|
|
|
|
);
|
|
|
|
extract
|
|
|
|
.expect("failed to extract")
|
|
|
|
.process()
|
|
|
|
.expect("failed to extract");
|
|
|
|
for file_name in plugin_file_paths.iter() {
|
|
|
|
dbg!(file_name);
|
|
|
|
let mut plugin_buf =
|
|
|
|
std::fs::read(temp_dir.path().join(file_name))?;
|
|
|
|
process_plugin(
|
|
|
|
&mut plugin_buf,
|
|
|
|
&pool,
|
2021-07-09 04:37:08 +00:00
|
|
|
&mut plugins_archive,
|
2021-07-07 03:29:09 +00:00
|
|
|
&db_file,
|
2021-07-09 04:37:08 +00:00
|
|
|
&db_mod,
|
2021-07-07 03:29:09 +00:00
|
|
|
file_name,
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
temp_dir.close()?;
|
|
|
|
}
|
|
|
|
}
|
2021-06-03 16:30:04 +00:00
|
|
|
}
|
2021-07-07 03:29:09 +00:00
|
|
|
}
|
2021-06-03 16:30:04 +00:00
|
|
|
|
2021-07-09 04:37:08 +00:00
|
|
|
plugins_archive.finish()?;
|
|
|
|
if let Some(duration) = download_link_resp.wait {
|
2021-06-14 02:30:40 +00:00
|
|
|
sleep(duration).await;
|
|
|
|
}
|
|
|
|
}
|
2021-06-03 16:30:04 +00:00
|
|
|
}
|
2021-06-14 02:30:40 +00:00
|
|
|
|
|
|
|
page += 1;
|
2021-07-07 03:29:09 +00:00
|
|
|
dbg!(page);
|
|
|
|
dbg!(has_next_page);
|
2021-07-09 04:37:08 +00:00
|
|
|
sleep(Duration::new(1, 0)).await;
|
2021-06-03 16:30:04 +00:00
|
|
|
}
|
2021-06-14 02:30:40 +00:00
|
|
|
|
2021-06-03 16:30:04 +00:00
|
|
|
Ok(())
|
|
|
|
}
|