feat(package): implement first version of espanso-hub provider
This commit is contained in:
parent
393f431bc3
commit
23a73f7ea2
74
Cargo.lock
generated
74
Cargo.lock
generated
|
@ -105,6 +105,15 @@ version = "0.1.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.15"
|
||||
|
@ -302,6 +311,15 @@ version = "0.8.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.2.1"
|
||||
|
@ -425,6 +443,15 @@ version = "2.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "1.0.5"
|
||||
|
@ -786,6 +813,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"fs_extra",
|
||||
"glob",
|
||||
"hex",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"natord",
|
||||
|
@ -795,6 +823,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"sha2",
|
||||
"tempdir",
|
||||
"thiserror",
|
||||
"zip",
|
||||
|
@ -1034,6 +1063,16 @@ version = "0.3.55"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.16"
|
||||
|
@ -1105,6 +1144,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "html2text"
|
||||
version = "0.2.1"
|
||||
|
@ -1338,9 +1383,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.98"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
|
||||
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
|
@ -1785,6 +1830,12 @@ version = "1.8.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "opener"
|
||||
version = "0.5.0"
|
||||
|
@ -2350,6 +2401,19 @@ dependencies = [
|
|||
"yaml-rust 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9204c41a1597a8c5af23c82d1c921cb01ec0a4c59e07a9c7306062829a3903f3"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simplelog"
|
||||
version = "0.9.0"
|
||||
|
@ -2721,6 +2785,12 @@ version = "0.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.6.0"
|
||||
|
|
|
@ -19,4 +19,6 @@ lazy_static = "1.4.0"
|
|||
regex = "1.4.3"
|
||||
zip = "0.5.13"
|
||||
scopeguard = "1.1.0"
|
||||
fs_extra = "1.2.0"
|
||||
fs_extra = "1.2.0"
|
||||
sha2 = "0.9.6"
|
||||
hex = "0.4.3"
|
|
@ -22,6 +22,8 @@ use std::path::Path;
|
|||
use anyhow::{bail, Result};
|
||||
|
||||
mod archive;
|
||||
#[macro_use]
|
||||
mod logging;
|
||||
mod manifest;
|
||||
mod package;
|
||||
mod provider;
|
||||
|
@ -29,12 +31,12 @@ mod resolver;
|
|||
mod util;
|
||||
|
||||
pub use archive::{ArchivedPackage, Archiver, SaveOptions, StoredPackage};
|
||||
pub use provider::{PackageSpecifier, PackageProvider};
|
||||
pub use package::Package;
|
||||
pub use provider::{PackageProvider, PackageSpecifier};
|
||||
|
||||
// TODO: once the download is completed, avoid copying files beginning with "."
|
||||
|
||||
pub fn get_provider(package: &PackageSpecifier) -> Result<Box<dyn PackageProvider>> {
|
||||
pub fn get_provider(package: &PackageSpecifier,) -> Result<Box<dyn PackageProvider>> {
|
||||
if let Some(git_repo_url) = package.git_repo_url.as_deref() {
|
||||
if !package.use_native_git {
|
||||
let matches_known_hosts =
|
||||
|
@ -73,14 +75,15 @@ pub fn get_provider(package: &PackageSpecifier) -> Result<Box<dyn PackageProvide
|
|||
// (because it's not authenticated)
|
||||
Ok(Box::new(provider::git::GitPackageProvider::new()))
|
||||
} else {
|
||||
// TODO: use espanso-hub method
|
||||
bail!("espanso hub method not yet implemented")
|
||||
// Download from the official espanso hub
|
||||
Ok(Box::new(provider::hub::EspansoHubPackageProvider::new()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn get_archiver(package_dir: &Path) -> Result<Box<dyn Archiver>> {
|
||||
Ok(Box::new(archive::default::DefaultArchiver::new(package_dir)))
|
||||
Ok(Box::new(archive::default::DefaultArchiver::new(
|
||||
package_dir,
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -94,4 +97,4 @@ pub(crate) mod tests {
|
|||
|
||||
action(&tmp_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
23
espanso-package/src/logging.rs
Normal file
23
espanso-package/src/logging.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
#[macro_export]
|
||||
macro_rules! info_println {
|
||||
($($tts:tt)*) => {
|
||||
println!($($tts)*);
|
||||
log::info!($($tts)*);
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! warn_eprintln {
|
||||
($($tts:tt)*) => {
|
||||
eprintln!($($tts)*);
|
||||
log::warn!($($tts)*);
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! error_eprintln {
|
||||
($($tts:tt)*) => {
|
||||
eprintln!($($tts)*);
|
||||
log::error!($($tts)*);
|
||||
}
|
||||
}
|
137
espanso-package/src/provider/hub.rs
Normal file
137
espanso-package/src/provider/hub.rs
Normal file
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* This file is part of espanso.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Federico Terzi
|
||||
*
|
||||
* espanso is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* espanso is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use super::PackageProvider;
|
||||
use crate::{
|
||||
package::DefaultPackage, resolver::resolve_package, util::download::read_string_from_url,
|
||||
Package, PackageSpecifier,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const ESPANSO_HUB_PACKAGE_INDEX_URL: &str =
|
||||
"https://github.com/espanso/hub/releases/latest/download/package_index.json";
|
||||
|
||||
pub struct EspansoHubPackageProvider {}
|
||||
|
||||
impl EspansoHubPackageProvider {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl PackageProvider for EspansoHubPackageProvider {
|
||||
fn name(&self) -> String {
|
||||
"espanso-hub".to_string()
|
||||
}
|
||||
|
||||
fn download(&self, package: &PackageSpecifier) -> Result<Box<dyn Package>> {
|
||||
// TODO: pass index update flag
|
||||
let index = self
|
||||
.get_index(true)
|
||||
.context("unable to get package index from espanso hub")?;
|
||||
|
||||
let package_info = index
|
||||
.get_package(&package.name, package.version.as_deref())
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"unable to find package '{}@{}' in the espanso hub",
|
||||
package.name,
|
||||
package.version.as_deref().unwrap_or("latest")
|
||||
)
|
||||
})?;
|
||||
|
||||
let archive_sha256 = read_string_from_url(&package_info.archive_sha256_url)
|
||||
.context("unable to read archive sha256 signature")?;
|
||||
|
||||
let temp_dir = tempdir::TempDir::new("espanso-package-download")?;
|
||||
|
||||
crate::util::download::download_and_extract_zip_verify_sha256(
|
||||
&package_info.archive_url,
|
||||
temp_dir.path(),
|
||||
Some(&archive_sha256),
|
||||
)?;
|
||||
|
||||
let resolved_package =
|
||||
resolve_package(temp_dir.path(), &package.name, package.version.as_deref())?;
|
||||
|
||||
let package = DefaultPackage::new(
|
||||
resolved_package.manifest,
|
||||
temp_dir,
|
||||
resolved_package.base_dir,
|
||||
);
|
||||
|
||||
Ok(Box::new(package))
|
||||
}
|
||||
}
|
||||
|
||||
impl EspansoHubPackageProvider {
|
||||
fn get_index(&self, _force_update: bool) -> Result<PackageIndex> {
|
||||
// TODO: if force_update is false, we should try to use a locally-cached version of the package index
|
||||
self.download_index()
|
||||
}
|
||||
|
||||
fn download_index(&self) -> Result<PackageIndex> {
|
||||
info_println!("fetching package index...");
|
||||
let json_body = read_string_from_url(ESPANSO_HUB_PACKAGE_INDEX_URL)?;
|
||||
|
||||
let index: PackageIndex = serde_json::from_str(&json_body)?;
|
||||
|
||||
Ok(index)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct PackageIndex {
|
||||
last_update: u64,
|
||||
packages: Vec<PackageInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct PackageInfo {
|
||||
name: String,
|
||||
title: String,
|
||||
author: String,
|
||||
description: String,
|
||||
version: String,
|
||||
|
||||
archive_url: String,
|
||||
archive_sha256_url: String,
|
||||
}
|
||||
|
||||
impl PackageIndex {
|
||||
fn get_package(&self, name: &str, version: Option<&str>) -> Option<PackageInfo> {
|
||||
let mut matching_packages: Vec<PackageInfo> = self
|
||||
.packages
|
||||
.iter()
|
||||
.filter(|package| package.name == name)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
matching_packages.sort_by(|a, b| natord::compare(&a.version, &b.version));
|
||||
|
||||
if let Some(explicit_version) = version {
|
||||
matching_packages
|
||||
.into_iter()
|
||||
.find(|package| package.version == explicit_version)
|
||||
} else {
|
||||
matching_packages.into_iter().last()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ use anyhow::Result;
|
|||
|
||||
use crate::Package;
|
||||
|
||||
pub(crate) mod hub;
|
||||
pub(crate) mod git;
|
||||
pub(crate) mod github;
|
||||
|
||||
|
|
|
@ -17,15 +17,35 @@
|
|||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{Context, Result, bail};
|
||||
use std::io::{copy, Cursor};
|
||||
use std::path::Path;
|
||||
use sha2::{Sha256, Digest};
|
||||
|
||||
pub fn download_and_extract_zip(url: &str, dest_dir: &Path) -> Result<()> {
|
||||
download_and_extract_zip_verify_sha256(url, dest_dir, None)
|
||||
}
|
||||
|
||||
pub fn download_and_extract_zip_verify_sha256(url: &str, dest_dir: &Path, sha256: Option<&str>) -> Result<()> {
|
||||
let data = download(url).context("error downloading archive")?;
|
||||
if let Some(sha256) = sha256 {
|
||||
info_println!("validating sha256 signature...");
|
||||
if !verify_sha256(&data, sha256) {
|
||||
bail!("signature mismatch");
|
||||
}
|
||||
}
|
||||
extract_zip(data, dest_dir).context("error extracting archive")
|
||||
}
|
||||
|
||||
pub fn read_string_from_url(url: &str) -> Result<String> {
|
||||
let client = reqwest::blocking::Client::builder();
|
||||
let client = client.build()?;
|
||||
|
||||
let response = client.get(url).send()?;
|
||||
|
||||
Ok(response.text()?)
|
||||
}
|
||||
|
||||
fn download(url: &str) -> Result<Vec<u8>> {
|
||||
let client = reqwest::blocking::Client::builder();
|
||||
let client = client.build()?;
|
||||
|
@ -37,6 +57,14 @@ fn download(url: &str) -> Result<Vec<u8>> {
|
|||
Ok(buffer)
|
||||
}
|
||||
|
||||
fn verify_sha256(data: &[u8], sha256: &str) -> bool {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(data);
|
||||
let result = hasher.finalize();
|
||||
let hash = hex::encode(result);
|
||||
hash == sha256
|
||||
}
|
||||
|
||||
// Adapted from zip-rs extract.rs example
|
||||
fn extract_zip(data: Vec<u8>, dest_dir: &Path) -> Result<()> {
|
||||
let reader = Cursor::new(data);
|
||||
|
|
Loading…
Reference in New Issue
Block a user