1 // Copyright 2020-2021 Ian Jackson and contributors to Otter
2 // SPDX-License-Identifier: AGPL-3.0-or-later
3 // There is NO WARRANTY.
6 use super::usebundles::*;
10 //---------- reset ----------
15 #[derive(Default,Debug)]
17 table_file: Option<String>,
23 fn subargs(sa: &mut Args) -> ArgumentParser {
25 let mut ap = ArgumentParser::new();
26 ap.refer(&mut sa.table_file).metavar("TABLE-SPEC[-TOML")
27 .add_option(&["--reset-table"],StoreOption,
28 "reset the players and access too");
29 ap.refer(&mut sa.game_spec).required()
30 .add_argument("GAME-SPEC",Store,
31 "game spec, as found in server, \
32 or local filename if it contains a '/')");
33 ap.refer(&mut sa.bundles).required()
34 .add_argument("BUNDLES",Collect,
35 "Bundle files to use. If any are specified, \
36 all needed bundles must be specified, as any \
37 not mentioned will be cleared from the server.");
38 let mut bundles_only = ap.refer(&mut sa.bundles_only);
39 bundles_only.add_option(&["--bundles-only"],StoreTrue,
40 "insist that server has only the specified BUNDLES \
41 (clearing out the server if no BUNDLES were specified)");
42 bundles_only.add_option(&["--bundles-at-least"],StoreFalse,
43 "don't care if the server has additional bundles uploaded \
48 fn call(SCCA{ ma, args, mut out,.. }:SCCA) -> Result<(),AE> {
49 let args = parse_args::<Args,_>(args, &subargs, &ok_id, None);
50 let instance_name = ma.instance();
51 let mut chan = ma.access_game()?;
54 if let Some(filename) = spec_arg_is_path(&args.game_spec) {
55 let spec_toml = read_spec_from_path(
56 filename, SpecRaw::<GameSpec>::new())?;
58 let spec_toml = bundles::spec_macroexpand(spec_toml, &mut |what,data|{
60 for (lno,l) in data.split('\n').enumerate() {
61 writeln!(out, "spec {} {} {}", what, lno+1, l)?;
66 .context("failed to template expand game spec")?;
68 MGI::ResetFromGameSpec { spec_toml }
70 MGI::ResetFromNamedSpec { spec: args.game_spec.clone() }
73 let mut insns = vec![];
75 if let Some(table_file) = args.table_file {
76 let table_spec = read_spec(&ma, &table_file, SpecParseToml::new())?;
77 let game = chan.game.clone();
78 chan.cmd(&MgmtCommand::CreateGame {
81 }).map(|_|()).or_else(|e| {
82 if let Some(&MgmtError::AlreadyExists) = e.downcast_ref() {
88 insns.extend(setup_table(&ma, &instance_name, &table_spec, true)?);
91 if args.bundles_only || args.bundles.len() != 0 {
92 let n_bundles = args.bundles.len();
93 let progress = ma.progressbar()?;
94 let mut progress = termprogress::Nest::with_total
95 (n_bundles as f32, progress);
96 let bundle_i_msg = |i| Some(format!("{}/{}", i, n_bundles));
98 let local = args.bundles.into_iter().enumerate().map(|(i,file)| {
99 progress.start_phase(0., bundle_i_msg(i), "preparing".into());
100 BundleForUpload::prepare(file, &mut progress)
101 }).collect::<Result<Vec<_>,_>>()?;
103 let resp = chan.cmd(&MgmtCommand::ListBundles { game: ma.instance() })?;
104 let remote = match resp {
105 MR::Bundles { bundles } => bundles,
106 x => throw!(anyhow!("unexpected response to ListBundles: {:?}",x)),
110 let bundles_only = args.bundles_only;
111 match Itertools::zip_longest(
116 use bundles::State::*;
118 Right((id, remote)) => if bundles_only {
119 Err(format!("server has additional bundle(s) eg {} {}",
125 Err(format!("server is missing {} {} {}",
126 local.kind, local.hash, local.file))
128 Both(_local, (id, Uploading)) => {
129 Err(format!("server has incomplete upload :{}", id))
131 Both(local, (id, Loaded(remote))) => {
132 if (local.size, local.hash) !=
133 (remote.size, remote.hash) {
134 Err(format!("server's {} does not match {}", id, &local.file))
140 }).find_map(Result::err).map_or_else(|| Ok(()), Err) {
143 eprintln!("Reusing server's existing bundles");
148 eprintln!("Re-uploading bundles: {}", why);
151 clear_game(&ma, &mut chan)?;
153 for (i, bundle) in local.into_iter().enumerate() {
154 progress.start_phase(PROGFRAC_UPLOAD,
157 bundle.upload(&ma, &mut chan, &mut progress)?;
163 insns.push(reset_insn);
165 chan.alter_game(insns, None)?;
168 eprintln!("reset successful.");
175 "Reset the state of the game table",
179 //---------- set-access ----------
184 #[derive(Default,Debug)]
189 fn subargs(sa: &mut Args) -> ArgumentParser {
191 let mut ap = ArgumentParser::new();
193 ap.refer(&mut sa.table_file).required()
194 .add_argument("TABLE-SPEC[-TOML",Store,
195 "table spec filename");
199 fn call(SCCA{ ma, args,.. }:SCCA) -> Result<(),AE> {
200 let args = parse_args::<Args,_>(args, &subargs, &ok_id, None);
201 let instance_name = ma.instance();
202 let mut chan = ma.access_game()?;
204 let table_spec = read_spec(&ma, &args.table_file, SpecParseToml::new())?;
205 let insns = setup_table(&ma, &instance_name, &table_spec, false)?;
206 chan.alter_game(insns, None)?;
209 eprintln!("access update successful.");
216 "Set the table's access control list",
220 //---------- set-link ----------
226 #[derive(Debug,Default)]
228 kind: Option<LinkKind>,
232 fn subargs(sa: &mut Args) -> ArgumentParser {
234 let mut ap = ArgumentParser::new();
235 ap.refer(&mut sa.kind)
236 .add_argument("LINK-KIND",StoreOption,"link kind");
237 ap.refer(&mut sa.url)
238 .add_argument("URL",StoreOption,"url (or empty for none)");
243 fn call(SCCA{ mut out, ma, args,.. }:SCCA) {
244 let args = parse_args::<Args,_>(args, &subargs, &ok_id, None);
245 let mut chan = ma.access_game()?;
249 let MgmtGameResponseGameInfo { links, .. } = chan.info()?;
250 for (tk, v) in links {
251 let v: Url = (&v).try_into().context("reparse sererr's UrlSpec")?;
254 writeln!(out, "{:<10} {}", tk, &v)?;
258 writeln!(out, "{}", &v)?;
266 let kind = args.kind.unwrap();
267 chan.alter_game(vec![
269 MGI::RemoveLink { kind }
271 MGI::SetLink { kind, url: UrlSpec(url) }
280 "Set one of the info links visible from within the game",
284 //---------- join-game ----------
289 #[derive(Default,Debug)]
294 fn subargs(sa: &mut Args) -> ArgumentParser {
296 let mut ap = ArgumentParser::new();
297 ap.refer(&mut sa.reset_access)
298 .add_option(&["--reset"],StoreTrue,
299 "generate and deliver new player access token");
303 fn call(SCCA{ mut out, ma, args,.. }:SCCA) -> Result<(),AE> {
304 let args = parse_args::<Args,_>(args, &subargs, &ok_id, None);
305 let mut chan = ma.access_game()?;
307 let mut insns = vec![];
308 match chan.has_player(&ma.account)? {
310 let nick = ma.nick.clone()
311 .unwrap_or_else(|| ma.account.default_nick());
312 let details = MgmtPlayerDetails { nick: Some(nick) };
313 insns.push(MGI::JoinGame { details });
315 Some((player, mpi)) => {
316 writeln!(out, "already in game, as player #{} {:?}",
317 player.0.get_idx_version().0, &mpi.nick)?;
318 let MgmtPlayerInfo { nick, account:_ } = mpi;
319 if let Some(new_nick) = &ma.nick {
320 if &nick != new_nick {
321 writeln!(out, "changing nick to {:?}", &new_nick)?;
322 let details = MgmtPlayerDetails { nick: ma.nick.clone() };
323 insns.push(MGI::UpdatePlayer { player, details });
326 if args.reset_access {
327 writeln!(out, "resetting access token (invalidating other URLs)")?;
328 insns.push(MGI::ResetPlayerAccess(player));
330 writeln!(out, "redelivering existing access token")?;
331 insns.push(MGI::RedeliverPlayerAccess(player));
336 fn deliver(out: &mut CookedStdout, token: &AccessTokenReport) {
337 for l in &token.lines {
338 if l.contains(char::is_control) {
339 writeln!(out, "Server token info contains control chars! {:?}", &l)
341 writeln!(out, " {}", &l)
346 for resp in chan.alter_game(insns, None)? {
348 MGR::JoinGame { nick, player, token } => {
349 writeln!(out, "joined game as player #{} {:?}",
350 player.0.get_idx_version().0,
352 deliver(&mut out, &token);
354 MGR::PlayerAccessToken(token) => {
355 deliver(&mut out, &token);
358 _ => throw!(anyhow!("unexpected response to instruction(s)")),
367 "Join a game or reset access token (creating or updating account)",
371 //---------- leave-game ----------
378 fn call(SCCA{ mut out, ma, args,.. }:SCCA) -> Result<(),AE> {
379 let _args = parse_args::<Args,_>(args, &noargs, &ok_id, None);
380 let mut chan = ma.access_game()?;
382 let player = match chan.has_player(&ma.account)? {
384 writeln!(out, "this account is not a player in that game")?;
387 Some((player, _)) => player,
390 chan.alter_game(vec![MGI::LeaveGame(player)], None)?;
401 //---------- clear game ----------
404 fn clear_game(ma: &MainOpts, chan: &mut MgmtChannelForGame) {
405 chan.alter_game(vec![MGI::ClearGame{ }], None)
406 .context("clear table")?;
407 chan.cmd(&MC::ClearBundles { game: ma.instance() })
408 .context("clear bundles")?;
417 fn call(SCCA{ ma, args,.. }:SCCA) {
418 let _args = parse_args::<Args,_>(args, &noargs, &ok_id, None);
419 let mut chan = ma.access_game()?;
420 clear_game(&ma, &mut chan)?;
425 "clear the table and clear out all bundles",
429 //---------- delete-game ----------
436 fn call(SCCA{ ma, args,.. }:SCCA) -> Result<(),AE> {
437 let _args = parse_args::<Args,_>(args, &noargs, &ok_id, None);
438 let mut chan = ma.access_game()?;
439 let game = chan.game.clone();
440 chan.cmd(&MC::DestroyGame { game })?;
446 "Delete a game (throwing all the players out of it)",