examples_relevancy_cli/
main.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use std::sync::Arc;
6
7use clap::Parser;
8use cli_support::{
9    fxa_creds::{get_cli_fxa, get_default_fxa_config, SYNC_SCOPE},
10    remote_settings_service,
11};
12use env_logger::Builder;
13use interrupt_support::NeverInterrupts;
14use places::{ConnectionType, PlacesApi};
15use relevancy::RelevancyStore;
16use sync15::client::{sync_multiple, MemoryCachedState};
17use sync15::engine::SyncEngineId;
18
19use anyhow::{bail, Result};
20
21static CREDENTIALS_PATH: &str = ".cli-data/credentials.json";
22
23#[derive(Parser)]
24#[command(about, long_about = None)]
25struct Cli {
26    /// Printout extra details, including how each URL is classified
27    #[clap(long, short, action)]
28    verbose: bool,
29
30    /// Load places data from disk rather than syncing it
31    ///
32    /// Note: this only works for mobile places databases, desktop uses a different format.
33    #[clap(long)]
34    places_db: Option<String>,
35}
36
37fn main() -> Result<()> {
38    let cli = Cli::parse();
39    nss::ensure_initialized();
40    viaduct_reqwest::use_reqwest_backend();
41    if let Some(dir) = std::path::PathBuf::from(CREDENTIALS_PATH).parent() {
42        std::fs::create_dir_all(dir)?;
43    }
44    let mut builder = Builder::new();
45    builder.filter_level(log::LevelFilter::Info);
46    if cli.verbose {
47        builder.filter_module("relevancy", log::LevelFilter::Trace);
48    }
49    builder.init();
50    println!("================== Initializing Relevancy ===================");
51    let relevancy_store = RelevancyStore::new(
52        "file:relevancy-cli-relevancy?mode=memory&cache=shared".to_owned(),
53        remote_settings_service(),
54    );
55    relevancy_store.ensure_interest_data_populated()?;
56
57    println!("==================== Downloading History ====================");
58    let places_api = if let Some(path) = cli.places_db {
59        PlacesApi::new(path)?
60    } else {
61        let places_api = PlacesApi::new_memory("relevancy-cli-places")?;
62        sync_places(&places_api)?;
63        places_api
64    };
65
66    let conn = places_api.open_connection(ConnectionType::ReadOnly)?;
67    let top_frecency_info = places::storage::history::get_top_frecent_site_infos(&conn, 5000, 0)?;
68    let top_frecency_urls = top_frecency_info
69        .into_iter()
70        .map(|info| info.url.to_string())
71        .collect();
72    println!("==================== Calculated Interests====================");
73    let interest_vector = relevancy_store.ingest(top_frecency_urls)?;
74    interest_vector.print_all_counts();
75
76    Ok(())
77}
78
79fn sync_places(places_api: &Arc<PlacesApi>) -> Result<()> {
80    Arc::clone(places_api).register_with_sync_manager();
81    let cli_fxa = get_cli_fxa(get_default_fxa_config(), CREDENTIALS_PATH, &[SYNC_SCOPE])?;
82    let mut mem_cached_state = MemoryCachedState::default();
83    let mut global_state: Option<String> = None;
84    let engine = places::get_registered_sync_engine(&SyncEngineId::History)
85        .expect("no registered sync engine");
86    let result = sync_multiple(
87        &[&*engine],
88        &mut global_state,
89        &mut mem_cached_state,
90        &cli_fxa.client_init.clone(),
91        &cli_fxa.as_key_bundle()?,
92        &NeverInterrupts,
93        None,
94    );
95
96    if result.engine_results.len() != 1 {
97        bail!(
98            "Unexpected number of engine result: {}",
99            result.engine_results.len()
100        );
101    }
102
103    match result.result {
104        Ok(()) => log::info!("Sync successful"),
105        Err(e) => {
106            log::info!("Sync failed");
107            bail!(e);
108        }
109    }
110    Ok(())
111}