From 3b58930c4a1efd433994e5a2215e2900c395f44d Mon Sep 17 00:00:00 2001
From: David Laban <alsuren@gmail.com>
Date: Tue, 10 Sep 2024 19:25:51 +0100
Subject: [PATCH] report to new stats server, with status

---
 crates/binstalk-fetchers/src/common.rs        |  4 +-
 crates/binstalk-fetchers/src/quickinstall.rs  | 60 +++++++++++++++++--
 .../src/gh_api_client.rs                      |  2 +-
 crates/binstalk/src/ops/resolve.rs            |  3 +
 4 files changed, 61 insertions(+), 8 deletions(-)

diff --git a/crates/binstalk-fetchers/src/common.rs b/crates/binstalk-fetchers/src/common.rs
index 310efead..ad9e1d68 100644
--- a/crates/binstalk-fetchers/src/common.rs
+++ b/crates/binstalk-fetchers/src/common.rs
@@ -25,7 +25,7 @@ static WARN_RATE_LIMIT_ONCE: Once = Once::new();
 static WARN_UNAUTHORIZED_ONCE: Once = Once::new();
 
 /// Return Ok(Some(api_artifact_url)) if exists, or Ok(None) if it doesn't.
-/// 
+///
 /// Caches info on all artifacts matching (repo, tag).
 pub(super) async fn get_gh_release_artifact_url(
     gh_api_client: GhApiClient,
@@ -58,7 +58,7 @@ pub(super) async fn get_gh_release_artifact_url(
 }
 
 /// Check if the URL exists by querying the GitHub API.
-/// 
+///
 /// Caches info on all artifacts matching (repo, tag).
 ///
 /// This function returns a future where its size should be at most size of
diff --git a/crates/binstalk-fetchers/src/quickinstall.rs b/crates/binstalk-fetchers/src/quickinstall.rs
index 434f7d49..55d7d42b 100644
--- a/crates/binstalk-fetchers/src/quickinstall.rs
+++ b/crates/binstalk-fetchers/src/quickinstall.rs
@@ -1,7 +1,7 @@
 use std::{
     borrow::Cow,
     path::Path,
-    sync::{Arc, OnceLock},
+    sync::{Arc, Mutex, OnceLock},
 };
 
 use binstalk_downloader::remote::Method;
@@ -16,7 +16,8 @@ use crate::{
 };
 
 const BASE_URL: &str = "https://github.com/cargo-bins/cargo-quickinstall/releases/download";
-pub const QUICK_INSTALL_STATS_URL: &str = "https://warehouse-clerk-tmp.vercel.app/api/crate";
+pub const QUICK_INSTALL_STATS_URL: &str =
+    "https://cargo-quickinstall-stats-server.fly.dev/record-install";
 
 const QUICKINSTALL_SIGN_KEY: Cow<'static, str> =
     Cow::Borrowed("RWTdnnab2pAka9OdwgCMYyOE66M/BlQoFWaJ/JjwcPV+f3n24IRTj97t");
@@ -58,6 +59,7 @@ pub struct QuickInstall {
     gh_api_client: GhApiClient,
     is_supported_v: OnceCell<bool>,
 
+    data: Arc<Data>,
     package: String,
     package_url: Url,
     signature_url: Url,
@@ -67,6 +69,30 @@ pub struct QuickInstall {
     target_data: Arc<TargetDataErased>,
 
     signature_verifier: OnceLock<SignatureVerifier>,
+    status: Mutex<Status>,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Status {
+    Start,
+    NotFound,
+    Found,
+    AttemptingInstall,
+    InvalidSignature,
+    InstalledFromTarball,
+}
+
+impl Status {
+    fn as_str(&self) -> &'static str {
+        match self {
+            Status::Start => "start",
+            Status::NotFound => "not-found",
+            Status::Found => "found",
+            Status::AttemptingInstall => "attempting-install",
+            Status::InvalidSignature => "invalid-signature",
+            Status::InstalledFromTarball => "installed-from-tarball",
+        }
+    }
 }
 
 impl QuickInstall {
@@ -116,6 +142,14 @@ impl QuickInstall {
             }
         })
     }
+
+    fn get_status(&self) -> Status {
+        *self.status.lock().unwrap()
+    }
+
+    fn set_status(&self, status: Status) {
+        *self.status.lock().unwrap() = status;
+    }
 }
 
 #[async_trait::async_trait]
@@ -137,6 +171,7 @@ impl super::Fetcher for QuickInstall {
 
         Arc::new(Self {
             client,
+            data,
             gh_api_client,
             is_supported_v: OnceCell::new(),
 
@@ -144,7 +179,7 @@ impl super::Fetcher for QuickInstall {
                 .expect("package_url is pre-generated and should never be invalid url"),
             signature_url: Url::parse(&format!("{url}.sig"))
                 .expect("signature_url is pre-generated and should never be invalid url"),
-            stats_url: Url::parse(&format!("{QUICK_INSTALL_STATS_URL}/{package}.tar.gz",))
+            stats_url: Url::parse("{QUICK_INSTALL_STATS_URL}")
                 .expect("stats_url is pre-generated and should never be invalid url"),
             package,
             signature_policy,
@@ -152,6 +187,7 @@ impl super::Fetcher for QuickInstall {
             target_data,
 
             signature_verifier: OnceLock::new(),
+            status: Mutex::new(Status::Start),
         })
     }
 
@@ -171,6 +207,7 @@ impl super::Fetcher for QuickInstall {
             .await?;
 
             if !is_found {
+                self.set_status(Status::NotFound);
                 return Ok(false);
             }
 
@@ -182,6 +219,7 @@ impl super::Fetcher for QuickInstall {
                 panic!("<QuickInstall as Fetcher>::find is run twice");
             }
 
+            self.set_status(Status::Found);
             Ok(true)
         })
     }
@@ -209,6 +247,7 @@ by rust officially."#,
     }
 
     async fn fetch_and_extract(&self, dst: &Path) -> Result<ExtractedFiles, FetchError> {
+        self.set_status(Status::AttemptingInstall);
         let Some(verifier) = self.signature_verifier.get() else {
             panic!("<QuickInstall as Fetcher>::find has not been called yet!")
         };
@@ -227,8 +266,10 @@ by rust officially."#,
             if let Some(info) = verifier.info() {
                 info!("Verified signature for package '{}': {info}", self.package);
             }
+            self.set_status(Status::InstalledFromTarball);
             Ok(files)
         } else {
+            self.set_status(Status::InvalidSignature);
             Err(FetchError::InvalidSignature)
         }
     }
@@ -280,10 +321,19 @@ impl QuickInstall {
             return Ok(());
         }
 
-        let url = self.stats_url.clone();
+        let mut url = self.stats_url.clone();
+        url.query_pairs_mut()
+            .append_pair("crate", &self.data.name)
+            .append_pair("version", &self.data.version)
+            .append_pair("target", &self.target_data.target)
+            .append_pair(
+                "agent",
+                concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")),
+            )
+            .append_pair("status", self.get_status().as_str());
         debug!("Sending installation report to quickinstall ({url})");
 
-        self.client.request(Method::HEAD, url).send(true).await?;
+        self.client.request(Method::POST, url).send(true).await?;
 
         Ok(())
     }
diff --git a/crates/binstalk-git-repo-api/src/gh_api_client.rs b/crates/binstalk-git-repo-api/src/gh_api_client.rs
index df5008cf..5b9405d9 100644
--- a/crates/binstalk-git-repo-api/src/gh_api_client.rs
+++ b/crates/binstalk-git-repo-api/src/gh_api_client.rs
@@ -247,7 +247,7 @@ pub struct GhReleaseArtifactUrl(Url);
 
 impl GhApiClient {
     /// Return `Ok(Some(api_artifact_url))` if exists.
-    /// 
+    ///
     /// Caches info on all artifacts matching (repo, tag).
     ///
     /// The returned future is guaranteed to be pointer size.
diff --git a/crates/binstalk/src/ops/resolve.rs b/crates/binstalk/src/ops/resolve.rs
index 59940c94..1666464f 100644
--- a/crates/binstalk/src/ops/resolve.rs
+++ b/crates/binstalk/src/ops/resolve.rs
@@ -203,6 +203,7 @@ async fn resolve_inner(
                     {
                         Ok(bin_files) => {
                             if !bin_files.is_empty() {
+                                fetcher.report_to_upstream();
                                 return Ok(Resolution::Fetch(Box::new(ResolutionFetch {
                                     fetcher: fetcher.clone(),
                                     new_version: package_info.version,
@@ -250,6 +251,8 @@ async fn resolve_inner(
         }
     }
 
+    // At this point, we don't know whether fallback to cargo install is allowed, or whether it will
+    // succeed, but things start to get convoluted when try to include that data, so this will do.
     if !opts.disable_telemetry {
         for fetcher in handles {
             fetcher.report_to_upstream();