Skip to content

Instantly share code, notes, and snippets.

@qryxip
Last active August 24, 2020 17:21
Show Gist options
  • Save qryxip/e53b5d3d817ed7af81fda2c96d36fe36 to your computer and use it in GitHub Desktop.
Save qryxip/e53b5d3d817ed7af81fda2c96d36fe36 to your computer and use it in GitHub Desktop.
//! ```cargo
//! [package]
//! name = "invesigate-dropbox"
//! version = "0.0.0"
//! authors = ["Ryo Yamashita <qryxip@gmail.com>"]
//! edition = "2018"
//! publish = false
//!
//! [dependencies]
//! anyhow = "1.0.32"
//! atty = "0.2.14"
//! maplit = "1.0.2"
//! reqwest = { version = "0.10.7", features = ["blocking", "json"] }
//! serde = { version = "1.0.115", features = ["derive"] }
//! serde_json = "1.0.57"
//! structopt = "0.3.16"
//! termcolor = "1.1.0"
//! ```
use anyhow::{anyhow, bail, Context as _};
use maplit::btreeset;
use reqwest::Method;
use serde::Deserialize;
use serde_json::json;
use std::{
collections::BTreeSet,
convert, env, fmt,
io::{self, Write as _},
time::Duration,
};
use structopt::StructOpt;
use termcolor::{BufferedStandardStream, Color, ColorChoice, ColorSpec, WriteColor as _};
#[derive(StructOpt)]
struct Opt {}
fn main() -> anyhow::Result<()> {
Opt::from_args();
let mut shell = Shell::new();
let client = client()?;
let access_token = access_token()?;
let dir_names = list_folder(&mut shell, &client, &access_token, "")?;
let mut different = btreeset!();
let mut arc_abcd = btreeset!();
let mut arc_cdef = btreeset!();
let mut arc_abcdef = btreeset!();
for dir_name in dir_names {
let contest_ids = match &*dir_name {
"ARC060" => vec!["arc060".to_owned(), "abc044".to_owned()],
"ARC061" => vec!["arc061".to_owned(), "abc045".to_owned()],
"ARC062" => vec!["arc062".to_owned(), "abc046".to_owned()],
"ARC063" => vec!["arc063".to_owned(), "abc047".to_owned()],
"ARC064" => vec!["arc064".to_owned(), "abc048".to_owned()],
"ARC065" => vec!["arc065".to_owned(), "abc049".to_owned()],
"ARC066" => vec!["arc066".to_owned(), "abc050".to_owned()],
"ARC067" => vec!["arc067".to_owned(), "abc052".to_owned()],
"ARC068" => vec!["arc068".to_owned(), "abc053".to_owned()],
"ARC069" => vec!["arc069".to_owned(), "abc055".to_owned()],
"ARC070" => vec!["arc070".to_owned(), "abc056".to_owned()],
"ARC071" => vec!["arc071".to_owned(), "abc058".to_owned()],
"ARC072" => vec!["arc072".to_owned(), "abc059".to_owned()],
"ARC073" => vec!["arc073".to_owned(), "abc060".to_owned()],
"ARC074" => vec!["arc074".to_owned(), "abc062".to_owned()],
"ARC075" => vec!["arc075".to_owned(), "abc063".to_owned()],
"ARC076" => vec!["arc076".to_owned(), "abc065".to_owned()],
"ARC077" => vec!["arc077".to_owned(), "abc066".to_owned()],
"ARC078" => vec!["arc078".to_owned(), "abc067".to_owned()],
"ARC079" => vec!["arc079".to_owned(), "abc068".to_owned()],
"ARC080" => vec!["arc080".to_owned(), "abc069".to_owned()],
"ARC081" => vec!["arc081".to_owned(), "abc071".to_owned()],
"ARC082" => vec!["arc082".to_owned(), "abc072".to_owned()],
"ARC083" => vec!["arc083".to_owned(), "abc074".to_owned()],
"ARC084" => vec!["arc084".to_owned(), "abc077".to_owned()],
"ARC085" => vec!["arc085".to_owned(), "abc078".to_owned()],
"ARC086" => vec!["arc086".to_owned(), "abc081".to_owned()],
"ARC087" => vec!["arc087".to_owned(), "abc082".to_owned()],
"ARC088" => vec!["arc088".to_owned(), "abc083".to_owned()],
"ARC089" => vec!["arc089".to_owned(), "abc086".to_owned()],
"ARC090" => vec!["arc090".to_owned(), "abc087".to_owned()],
"ARC091" => vec!["arc091".to_owned(), "abc090".to_owned()],
"ARC092" => vec!["arc092".to_owned(), "abc091".to_owned()],
"ARC093" => vec!["arc093".to_owned(), "abc092".to_owned()],
"ARC094" => vec!["arc094".to_owned(), "abc093".to_owned()],
"ARC095" => vec!["arc095".to_owned(), "abc094".to_owned()],
"ARC096" => vec!["arc096".to_owned(), "abc095".to_owned()],
"ARC097" => vec!["arc097".to_owned(), "abc097".to_owned()],
"ARC098" => vec!["arc098".to_owned(), "abc098".to_owned()],
"ARC099" => vec!["arc099".to_owned(), "abc101".to_owned()],
"ARC100" => vec!["arc100".to_owned(), "abc102".to_owned()],
"ARC101" => vec!["arc101".to_owned(), "abc107".to_owned()],
"ARC102" => vec!["arc102".to_owned(), "abc108".to_owned()],
"ARC103" => vec!["arc103".to_owned(), "abc111".to_owned()],
"ARC058_ABC042" => vec!["arc058".to_owned(), "abc042".to_owned()],
"ARC059_ABC043" => vec!["arc059".to_owned(), "abc043".to_owned()],
"2019ddccqual" => vec!["ddcc2019-qual".to_owned()],
"2019exa" => vec!["exawizards2019".to_owned()],
"2019nikkei_qual" => vec!["nikkei2019-qual".to_owned()],
"2019yahoo_qual" => vec!["yahoo-procon2019-qual".to_owned()],
"2020_dwango_qual" => vec!["dwacon6th-prelims".to_owned()],
"2020_hitachi" => vec!["hitachi2020".to_owned()],
"2020_panasonic" => vec!["panasonic2020".to_owned()],
"CodeFestival2016EliminationTournament" => vec![
"cf16-tournament-round1-open".to_owned(),
"cf16-tournament-round2-open".to_owned(),
"cf16-tournament-round3-open".to_owned(),
],
"CodeFestival2016Exhibition" => vec!["cf16-exhibition-open".to_owned()],
"CodeFestival2016Final" => vec!["cf16-final-open".to_owned()],
"CodeFestival2016GrandFinal" => vec!["cf16-exhibition-final-open".to_owned()],
"CodeFestival2016QualA" => vec!["code-festival-2016-quala".to_owned()],
"CodeFestival2016QualB" => vec!["code-festival-2016-qualb".to_owned()],
"CodeFestival2016QualC" => vec!["code-festival-2016-qualc".to_owned()],
"CodeFestival2016Relay" => vec!["cf16-relay-open".to_owned()],
"CodeFestival2017Exhibition" => vec!["cf17-exhibition-open".to_owned()],
"CodeFestival2017Final" => vec!["cf17-final-open".to_owned()],
"CodeFestival2017QualA" => vec!["code-festival-2017-quala".to_owned()],
"CodeFestival2017QualB" => vec!["code-festival-2017-qualb".to_owned()],
"CodeFestival2017QualC" => vec!["code-festival-2017-qualc".to_owned()],
"CodeFestival2017Relay" => vec!["cf17-relay-open".to_owned()],
"CodeFestival2017Tournament" => vec![
"cf17-tournament-round1-open".to_owned(),
"cf17-tournament-round2-open".to_owned(),
"cf17-tournament-round3-open".to_owned(),
],
"MUJIN2017" => vec!["mujin-pc-2017".to_owned()],
"diverta2_2019" => vec!["diverta2019-2".to_owned()],
"diverta_2019" => vec!["diverta2019".to_owned()],
"jsc2019" => vec!["jsc2019-qual".to_owned()],
"msolutions2019" => vec!["m-solutions2019".to_owned()],
"nikkeiqual_2019" => vec!["nikkei2019-qual".to_owned()],
"tenka1_2017" => vec!["tenka1-2017".to_owned()],
"tenka1_2018" => vec!["tenka1-2018".to_owned()],
"tenka1_2019" => vec!["tenka1-2019".to_owned()],
dir_name => vec![dir_name.to_ascii_lowercase()],
};
for contest_id in contest_ids {
if !exists(&mut shell, &client, &contest_id)? {
different.insert((dir_name.clone(), contest_id.clone()));
}
}
if dir_name.starts_with("ARC") && dir_name.len() == 6 {
let problems = list_folder(
&mut shell,
&client,
&access_token,
&format!("/{}", dir_name),
)?;
if problems.iter().eq(&["A", "B", "C", "D"]) {
arc_abcd.insert(dir_name);
} else if problems.iter().eq(&["C", "D", "E", "F"]) {
arc_cdef.insert(dir_name);
} else if problems.iter().eq(&["A", "B", "C", "D", "E", "F"]) {
arc_abcdef.insert(dir_name);
} else {
bail!("?????: {:?}", problems);
}
}
}
if different.is_empty() {
shell.info("found all")?;
}
for (dir_name, contest_id) in different {
shell.info(format!("not found: {:?} -> {:?}", dir_name, contest_id))?;
}
if !arc_abcd.is_empty() {
for name in arc_abcd {
shell.info(format!("ARC - A, B, C, D: {:?}", name))?;
}
}
if !arc_cdef.is_empty() {
for name in arc_cdef {
shell.info(format!("ARC - C, D, E, F: {:?}", name))?;
}
}
if !arc_abcdef.is_empty() {
for name in arc_abcdef {
shell.info(format!("ARC - A, B, C, D, E, F: {:?}", name))?;
}
}
Ok(())
}
fn client() -> reqwest::Result<reqwest::blocking::Client> {
const USER_AGENT: &str = "qryxip@gmail.com";
const TIMEOUT: Option<Duration> = Some(Duration::from_secs(30));
reqwest::blocking::ClientBuilder::new()
.user_agent(USER_AGENT)
.timeout(TIMEOUT)
.build()
}
fn access_token() -> anyhow::Result<String> {
env::var("DROPBOX_ACCESS_TOKEN").with_context(|| "could not read `$DROPBOX_ACCESS_TOKEN`")
}
fn list_folder(
shell: &mut Shell,
client: &reqwest::blocking::Client,
access_token: &str,
path: &str,
) -> anyhow::Result<BTreeSet<String>> {
static SHARED_LINK_URL: &str =
"https://www.dropbox.com/sh/arnpe0ef5wds8cv/AAAk_SECQ2Nc6SVGii3rHX6Fa?dl=0";
#[derive(Deserialize)]
#[serde(untagged)]
enum ListFolderOutcome {
Ok(ListFolder),
Err(serde_json::Value),
}
#[derive(Deserialize)]
struct ListFolder {
entries: Vec<ListFolderEntry>,
}
#[derive(Deserialize)]
struct ListFolderEntry {
name: String,
}
let res = request_with_message(
shell,
client,
Method::POST,
"https://api.dropboxapi.com/2/files/list_folder",
|req| {
req.bearer_auth(access_token)
.json(&json!({ "shared_link": { "url": SHARED_LINK_URL }, "path": path }))
},
)?;
if res.status() == 200 {
let ListFolder { entries } = res.json()?;
Ok(entries
.into_iter()
.map(|ListFolderEntry { name }| name)
.collect())
} else {
let msg = res.text()?;
let msg = serde_json::to_string_pretty(&msg).unwrap_or(msg);
Err(anyhow!("{}", msg).context("API error"))
}
}
fn exists(
shell: &mut Shell,
client: &reqwest::blocking::Client,
contest_id: &str,
) -> anyhow::Result<bool> {
let status = request_with_message(
shell,
client,
Method::GET,
&format!("https://atcoder.jp/contests/{}", contest_id),
convert::identity,
)?
.status();
match status.as_u16() {
200 => Ok(true),
404 => Ok(false),
_ => bail!("{}", status),
}
}
fn request_with_message(
shell: &mut Shell,
client: &reqwest::blocking::Client,
method: Method,
url: &str,
modify_req: impl FnOnce(reqwest::blocking::RequestBuilder) -> reqwest::blocking::RequestBuilder,
) -> anyhow::Result<reqwest::blocking::Response> {
let req = modify_req(client.request(method.clone(), url));
shell.network(format!("{}: {}", method, url))?;
let res = req.send()?;
shell.network(res.status())?;
Ok(res)
}
struct Shell(BufferedStandardStream);
impl Shell {
fn new() -> Self {
Self(BufferedStandardStream::stderr(
if atty::is(atty::Stream::Stderr) {
ColorChoice::Auto
} else {
ColorChoice::Never
},
))
}
fn info(&mut self, message: impl fmt::Display) -> io::Result<()> {
self.print("info:", message, Color::Cyan)
}
fn network(&mut self, message: impl fmt::Display) -> io::Result<()> {
self.print("network:", message, Color::Magenta)
}
fn print(
&mut self,
status: impl fmt::Display,
message: impl fmt::Display,
color: Color,
) -> io::Result<()> {
self.0
.set_color(ColorSpec::new().set_bold(true).set_fg(Some(color)))?;
write!(self.0, "{}", status)?;
self.0.reset()?;
writeln!(self.0, " {}", message)?;
self.0.flush()
}
}
@qryxip
Copy link
Author

qryxip commented Aug 24, 2020

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment