chiark / gitweb /
bundles: Implement finding specs in bundles
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Wed, 19 May 2021 23:24:49 +0000 (00:24 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Thu, 20 May 2021 00:41:42 +0000 (01:41 +0100)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
src/bundles.rs
src/global.rs

index b21835326cf8a78e7c8614f151522eef6758dede..c43196c361edd245306be4aa7889c98b81f7e78b 100644 (file)
@@ -34,6 +34,9 @@ pub struct InstanceBundles {
   bundles: Vec<Option<Note>>,
 }
 
+pub type FileInBundleId = (Id, ZipIndex);
+pub type SpecsInBundles = HashMap<UniCase<String>, FileInBundleId>;
+
 #[derive(Debug,Clone,Serialize,Deserialize)]
 pub enum State {
   Uploading,
@@ -91,6 +94,7 @@ define_index_type!{ pub struct LibInBundleI = usize; }
 struct Parsed {
   meta: BundleMeta,
   libs: IndexVec<LibInBundleI, shapelib::Contents>,
+  specs: SpecsInBundles,
 }
 
 #[derive(Debug)]
@@ -546,6 +550,7 @@ fn parse_bundle<EH>(id: Id, instance: &InstanceName, file: File, eh: EH,
   }
 
   let mut libs = Vec::new();
+  let mut specs = HashMap::new();
   for (name,i) in &za {
     eh.besteffort(|| Ok::<_,LE>(if_chain!{
       let mut split = name.as_ref().split('/');
@@ -563,6 +568,20 @@ fn parse_bundle<EH>(id: Id, instance: &InstanceName, file: File, eh: EH,
               inzip: i,
             });
           }
+        }} else if unicase::eq(dir, "specs") { if_chain!{
+          let mut split = file.rsplitn(3,'.');
+          if let Some(ext) = split.next(); if unicase::eq(ext, "toml");
+          if let Some(ext) = split.next(); if unicase::eq(ext, "game");
+          if let Some(base) = split.next();
+          then {
+            use hash_map::Entry::*;
+            match specs.entry(base.to_owned().into()) {
+              Occupied(oe) => throw!(LE::BadBundle(format!(
+                "duplicate spec {:?} vs {:?} - files varying only in case!",
+                file, oe.key()))),
+              Vacant(ve) => { ve.insert((id,i)); }
+            }
+          }
         }}
       }
     }), ||())?;
@@ -624,7 +643,7 @@ fn parse_bundle<EH>(id: Id, instance: &InstanceName, file: File, eh: EH,
   let (libs, newlibs) = newlibs.into_iter().unzip();
 
   (ForProcess { za, newlibs },
-   Parsed { meta, libs })
+   Parsed { meta, libs, specs })
 }
 
 #[throws(LE)]
@@ -729,7 +748,22 @@ pub fn load_spec_to_read(ig: &Instance, spec_name: &str) -> String {
 
   let spec_leaf = format!("{}.game.toml", spec_name);
 
-  // todo: game specs from bundles
+  if let Some((id, index)) = ig.bundle_specs.get(&UniCase::from(spec_name)) {
+    let fpath = id.path_(&ig.name);
+    let f = File::open(&fpath)
+      .with_context(|| fpath.clone()).context("reopen bundle")
+      .map_err(IE::from)?;
+    match id.kind {
+      Kind::Zip => {
+        let mut za = ZipArchive::new(BufReader::new(f)).map_err(
+          |e| LE::BadBundle(format!("re-examine zipfile: {}", e)))?;
+        let mut f = za.i(*index).map_err(
+          |e| LE::BadBundle(format!("re-find zipfile member: {}", e)))?;
+        return read_from_read(&mut f, &mut |e|{
+          LE::BadBundle(format!("read zipfile member: {}", e))}.into())?;
+      }
+    }
+  }
 
   if spec_name.chars().all(
     |c| c.is_ascii_alphanumeric() || c=='-' || c =='_'
@@ -760,7 +794,7 @@ pub fn load_spec_to_read(ig: &Instance, spec_name: &str) -> String {
 #[throws(InternalError)]
 fn incorporate_bundle(ib: &mut InstanceBundles, ig: &mut Instance,
                  id: Id, parsed: Parsed) {
-  let Parsed { meta, libs } = parsed;
+  let Parsed { meta, libs, specs } = parsed;
 
   let iu: usize = id.index.into();
   let slot = &mut ib.bundles[iu];
@@ -776,13 +810,18 @@ fn incorporate_bundle(ib: &mut InstanceBundles, ig: &mut Instance,
   for lib in libs {
     ig.local_libs.add(lib);
   }
+  ig.bundle_specs.extend(specs);
 
   let state = State::Loaded(Loaded { meta });
   *slot = Some(Note { kind: id.kind, state });
 }
 
 impl InstanceBundles {
-  pub fn new() -> Self { InstanceBundles{ bundles: default() } }
+  pub fn new() -> Self {
+    InstanceBundles{
+      bundles: default(),
+    }
+  }
 
   fn iter(&self) -> impl Iterator<Item=(Id, &State)> {
     self.bundles.iter().enumerate().filter_map(|(index, slot)| {
@@ -1087,6 +1126,7 @@ impl InstanceBundles {
 
       // Right, everything is at most NEARLY-ASENT, make them ABSENT
       self.bundles.clear();
+      ig.bundle_specs.clear();
 
       // Prevent old, removed, players from accessing any new bundles.
       ig.asset_url_key = new_asset_key;
index c9ae90d57f2a9c6ede467a5992db01df92cbde79..b8f6f1a8067fab7f250935b73a04a49b6b7fb626 100644 (file)
@@ -57,6 +57,7 @@ pub struct Instance {
   pub acl: LoadedAcl<TablePermission>,
   pub links: Arc<LinksTable>,
   pub bundle_list: MgmtBundleList, // copy for easy access
+  pub bundle_specs: bundles::SpecsInBundles,
   pub asset_url_key: AssetUrlKey,
   pub local_libs: shapelib::Registry,
 }
@@ -350,6 +351,7 @@ impl Instance {
       tokens_clients: default(),
       links: default(),
       bundle_list: default(),
+      bundle_specs: default(),
       asset_url_key: AssetUrlKey::new_random()?,
       local_libs: default(),
     };
@@ -509,6 +511,7 @@ impl Instance {
       acl: default(),
       links: default(),
       bundle_list: default(),
+      bundle_specs: default(),
       asset_url_key: AssetUrlKey::Dummy,
       local_libs: default(),
       iplayers: default(),
@@ -1189,6 +1192,7 @@ impl InstanceGuard<'_> {
       tokens_players: default(),
       bundle_list: default(), // set by load_game_bundles
       local_libs: default(), // set by load_game_bundles
+      bundle_specs: default(), // set by load_game_bundles
       asset_url_key,
     };