250 lines
7.7 KiB
Rust
250 lines
7.7 KiB
Rust
use std::collections::HashMap;
|
|
/*
|
|
* Copyright (C) 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use sqlx::types::time::OffsetDateTime;
|
|
use tokio::fs;
|
|
use tokio::io::AsyncWriteExt;
|
|
use uuid::Uuid;
|
|
|
|
use crate::api::v1::admin::campaigns::runners::get_results;
|
|
use crate::api::v1::admin::campaigns::SurveyResponse;
|
|
use crate::{errors::ServiceResult, AppData, Settings};
|
|
|
|
const CAMPAIGN_INFO_FILE: &str = "campaign.json";
|
|
|
|
const BENCHMARK_FILE: &str = "benchmark.csv";
|
|
|
|
pub struct Archiver {
|
|
base_path: String,
|
|
}
|
|
|
|
impl Archiver {
|
|
pub fn new(s: &Settings) -> Self {
|
|
Archiver {
|
|
base_path: s.archive.base_path.clone(),
|
|
}
|
|
}
|
|
fn campaign_path(&self, id: &Uuid) -> PathBuf {
|
|
Path::new(&self.base_path).join(&id.to_string())
|
|
}
|
|
|
|
async fn create_dir_util(p: &PathBuf) -> ServiceResult<()> {
|
|
if p.exists() {
|
|
if !p.is_dir() {
|
|
fs::remove_file(&p).await.unwrap();
|
|
fs::create_dir_all(&p).await.unwrap();
|
|
}
|
|
} else {
|
|
fs::create_dir_all(&p).await.unwrap();
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn archive_path_now(&self, id: &Uuid) -> PathBuf {
|
|
let unix_time = OffsetDateTime::now_utc().unix_timestamp();
|
|
self.campaign_path(id).join(unix_time.to_string())
|
|
}
|
|
|
|
fn campaign_file_path(&self, id: &Uuid) -> PathBuf {
|
|
self.archive_path_now(id).join(CAMPAIGN_INFO_FILE)
|
|
}
|
|
|
|
fn benchmark_file_path(&self, id: &Uuid) -> PathBuf {
|
|
self.archive_path_now(id).join(BENCHMARK_FILE)
|
|
}
|
|
|
|
async fn write_campaign_file(&self, c: &Campaign) -> ServiceResult<()> {
|
|
let archive_path = self.archive_path_now(&c.id);
|
|
Self::create_dir_util(&archive_path).await?;
|
|
let campaign_file_path = self.campaign_file_path(&c.id);
|
|
let contents = serde_json::to_string(c).unwrap();
|
|
// fs::write(campaign_file_path, contents).await.unwrap();
|
|
let mut file = fs::File::create(&campaign_file_path).await.unwrap();
|
|
file.write(contents.as_bytes()).await.unwrap();
|
|
file.flush().await.unwrap();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn write_benchmark_file(
|
|
&self,
|
|
c: &Campaign,
|
|
data: &AppData,
|
|
) -> ServiceResult<()> {
|
|
let archive_path = self.archive_path_now(&c.id);
|
|
Self::create_dir_util(&archive_path).await?;
|
|
|
|
let benchmark_file_path = self.benchmark_file_path(&c.id);
|
|
struct Username {
|
|
name: String,
|
|
}
|
|
let owner = sqlx::query_as!(
|
|
Username,
|
|
"SELECT
|
|
survey_admins.name
|
|
FROM
|
|
survey_admins
|
|
INNER JOIN survey_campaigns ON
|
|
survey_admins.ID = survey_campaigns.user_id
|
|
WHERE
|
|
survey_campaigns.ID = $1
|
|
",
|
|
&c.id
|
|
)
|
|
.fetch_one(&data.db)
|
|
.await?;
|
|
|
|
let mut page = 0;
|
|
let limit = 50;
|
|
let file = fs::OpenOptions::new()
|
|
.read(true)
|
|
.append(true)
|
|
.create(true)
|
|
.open(&benchmark_file_path)
|
|
.await
|
|
.unwrap();
|
|
let mut wri = csv_async::AsyncWriter::from_writer(file);
|
|
|
|
loop {
|
|
let mut resp =
|
|
get_results(&owner.name, &c.id, data, page, limit, None).await?;
|
|
|
|
for r in resp.drain(0..) {
|
|
let csv_resp = to_hashmap(r, c);
|
|
let keys: Vec<&str> = csv_resp.keys().map(|k| k.as_str()).collect();
|
|
wri.write_record(&keys).await.unwrap();
|
|
|
|
let values: Vec<&str> = csv_resp.values().map(|v| v.as_str()).collect();
|
|
wri.write_record(&values).await.unwrap();
|
|
wri.flush().await.unwrap();
|
|
|
|
//wri.serialize(csv_resp).await.unwrap();
|
|
wri.flush().await.unwrap();
|
|
}
|
|
page += 1;
|
|
|
|
wri.flush().await.unwrap();
|
|
if resp.len() < limit {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn archive(&self, data: &AppData) -> ServiceResult<()> {
|
|
let mut db_campaigns = sqlx::query_as!(
|
|
InnerCampaign,
|
|
"SELECT ID, name, difficulties, created_at FROM survey_campaigns"
|
|
)
|
|
.fetch_all(&data.db)
|
|
.await?;
|
|
for c in db_campaigns.drain(0..) {
|
|
let campaign: Campaign = c.into();
|
|
self.write_campaign_file(&campaign).await?;
|
|
self.write_benchmark_file(&campaign, data).await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn to_hashmap(s: SurveyResponse, c: &Campaign) -> HashMap<String, String> {
|
|
let mut map = HashMap::with_capacity(7 + c.difficulties.len());
|
|
map.insert("user".into(), s.user.id.to_string());
|
|
map.insert("device_user_provided".into(), s.device_user_provided);
|
|
map.insert(
|
|
"device_software_recognised".into(),
|
|
s.device_software_recognised,
|
|
);
|
|
map.insert(
|
|
"threads".into(),
|
|
s.threads.map_or_else(|| "-".into(), |v| v.to_string()),
|
|
);
|
|
map.insert("submitted_at".into(), s.submitted_at.to_string());
|
|
map.insert("submission_type".into(), s.submission_type.to_string());
|
|
for d in c.difficulties.iter() {
|
|
let bench = s
|
|
.benches
|
|
.iter()
|
|
.find(|b| b.difficulty == *d as i32)
|
|
.map_or_else(|| "-".into(), |v| v.duration.to_string());
|
|
map.insert(format!("Difficulty: {d}"), bench);
|
|
}
|
|
map
|
|
}
|
|
|
|
//#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
|
//pub struct CSVSurveyResp {
|
|
// pub user: Uuid,
|
|
// pub device_user_provided: String,
|
|
// pub device_software_recognised: String,
|
|
// pub id: usize,
|
|
// pub threads: Option<usize>,
|
|
// pub submitted_at: i64,
|
|
// pub submission_type: SubmissionType,
|
|
// pub benches: String,
|
|
//}
|
|
//
|
|
//impl From<SurveyResponse> for CSVSurveyResp {
|
|
// fn from(s: SurveyResponse) -> Self {
|
|
// let mut benches = String::default();
|
|
// for b in s.benches.iter() {
|
|
// benches = format!("{benches} ({})", b.to_csv_resp());
|
|
// }
|
|
// Self {
|
|
// user: s.user.id,
|
|
// device_software_recognised: s.device_software_recognised,
|
|
// device_user_provided: s.device_user_provided,
|
|
// id: s.id,
|
|
// threads: s.threads,
|
|
// submission_type: s.submission_type,
|
|
// benches,
|
|
// submitted_at: s.submitted_at,
|
|
// }
|
|
// }
|
|
//}
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
struct InnerCampaign {
|
|
id: Uuid,
|
|
name: String,
|
|
difficulties: Vec<i32>,
|
|
created_at: OffsetDateTime,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
|
pub struct Campaign {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
pub difficulties: Vec<u32>,
|
|
pub created_at: i64,
|
|
}
|
|
|
|
impl From<InnerCampaign> for Campaign {
|
|
fn from(i: InnerCampaign) -> Self {
|
|
Self {
|
|
id: i.id,
|
|
name: i.name,
|
|
difficulties: i.difficulties.iter().map(|d| *d as u32).collect(),
|
|
created_at: i.created_at.unix_timestamp(),
|
|
}
|
|
}
|
|
}
|