flutter_download/
lib.rs

1extern crate curl;
2extern crate dirs;
3extern crate unzip;
4
5use curl::easy::Easy;
6use std::{
7    fs::{self, File},
8    io::{BufReader, Write},
9    path::{Path, PathBuf},
10    process::Command,
11    sync::{mpsc, Mutex},
12    thread,
13};
14
15pub mod util;
16
17pub use util::{get_flutter_version, Error};
18
19#[derive(PartialEq, Copy, Clone)]
20enum Target {
21    Linux,
22    Windows,
23    MacOS,
24}
25
26pub fn download(version: &str) -> (PathBuf, Result<mpsc::Receiver<(f64, f64)>, Error>) {
27    let libs_dir = dirs::cache_dir()
28        .expect("Cannot get cache dir")
29        .join("flutter-engine");
30
31    let url = download_url(version);
32    let dir = libs_dir.to_path_buf().join(version);
33
34    if !should_download(&dir) {
35        println!("Flutter engine already exist. Download not necessary");
36        return (libs_dir, Err(Error::AlreadyDownloaded));
37    }
38
39    let (tx, rx) = mpsc::channel();
40    thread::spawn(move || {
41        // TODO: less unwrap, more error handling
42
43        // Write the contents of rust-lang.org to stdout
44        tx.send((0.0, 0.0)).unwrap();
45        // create target dir
46
47        fs::create_dir_all(&dir).unwrap();
48
49        let download_file = dir.join("engine.zip");
50
51        let mut file = File::create(&download_file).unwrap();
52
53        let tx = Mutex::new(tx);
54
55        let mut easy = Easy::new();
56
57        println!("Starting download from {}", url);
58        easy.url(&url).unwrap();
59        easy.follow_location(true).unwrap();
60        easy.progress(true).unwrap();
61        easy.progress_function(move |total, done, _, _| {
62            tx.lock().unwrap().send((total, done)).unwrap();
63            true
64        })
65        .unwrap();
66        easy.write_function(move |data| Ok(file.write(data).unwrap()))
67            .unwrap();
68        easy.perform().unwrap();
69
70        println!("Download finished");
71
72        println!("Extracting...");
73        let zip_file = File::open(&download_file).unwrap();
74        let reader = BufReader::new(zip_file);
75        let unzipper = unzip::Unzipper::new(reader, &dir);
76        unzipper.unzip().unwrap();
77
78        // mac framework file is a double zip file
79        if target() == Target::MacOS {
80            Command::new("unzip")
81                .args(&[
82                    "FlutterEmbedder.framework.zip",
83                    "-d",
84                    "FlutterEmbedder.framework",
85                ])
86                .current_dir(&dir)
87                .status()
88                .unwrap();
89
90            // TODO: fixme
91            // unzip bug! Extracted file corrupted!
92            // let zip_file = File::open(dir.join("FlutterEmbedder.framework.zip")).unwrap();
93            // let reader = BufReader::new(zip_file);
94            // let unzipper = unzip::Unzipper::new(reader, dir.join("FlutterEmbedder.framework"));
95            // unzipper.unzip().unwrap();
96        }
97    });
98
99    (libs_dir, Ok(rx))
100}
101
102pub fn download_url(version: &str) -> String {
103    let url = match target() {
104        Target::Linux => "{base_url}/flutter_infra/flutter/{version}/linux-x64/linux-x64-embedder",
105        Target::MacOS => {
106            "{base_url}/flutter_infra/flutter/{version}/darwin-x64/FlutterEmbedder.framework.zip"
107        }
108        Target::Windows => {
109            "{base_url}/flutter_infra/flutter/{version}/windows-x64/windows-x64-embedder.zip"
110        }
111    };
112    let base_url = std::env::var("FLUTTER_STORAGE_BASE_URL");
113    let base_url = base_url
114        .as_ref()
115        .map(String::as_str)
116        .unwrap_or("https://storage.googleapis.com");
117    url.replace("{base_url}", base_url)
118        .replace("{version}", version)
119}
120
121fn should_download(path: &Path) -> bool {
122    match target() {
123        Target::Linux => !path.join("libflutter_engine.so").exists(),
124        Target::MacOS => !path.join("FlutterEmbedder.framework").exists(),
125        Target::Windows => !path.join("flutter_engine.dll").exists(),
126    }
127}
128
129fn target() -> Target {
130    let target = std::env::var("TARGET").expect("Cannot determine target");
131    if target.contains("linux") {
132        Target::Linux
133    } else if target.contains("apple") {
134        Target::MacOS
135    } else if target.contains("windows") {
136        Target::Windows
137    } else {
138        panic!("Unknown target {}", target)
139    }
140}