summaryrefslogtreecommitdiff
path: root/src/sota.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/sota.rs')
-rw-r--r--src/sota.rs131
1 files changed, 74 insertions, 57 deletions
diff --git a/src/sota.rs b/src/sota.rs
index 3dec33d..64abacd 100644
--- a/src/sota.rs
+++ b/src/sota.rs
@@ -1,11 +1,11 @@
use rustc_serialize::json;
+use std::{fs, io};
use std::fs::File;
-use std::io;
use std::path::PathBuf;
-use datatype::{Config, DeviceReport, DownloadComplete, Error, Package,
- PendingUpdateRequest, UpdateRequestId, UpdateReport, Url};
-use http::Client;
+use datatype::{Config, DownloadComplete, Error, Package,
+ UpdateReport, UpdateRequest, UpdateRequestId, Url};
+use http::{Client, Response};
/// Encapsulate the client configuration and HTTP client used for
@@ -22,39 +22,47 @@ impl<'c, 'h> Sota<'c, 'h> {
}
/// Takes a path and returns a new endpoint of the format
- /// `<Core server>/api/v1/device_updates/<uuid>/<path>`.
- pub fn endpoint(&self, path: &str) -> Url {
- let endpoint = if path.is_empty() {
- format!("/api/v1/device_updates/{}", self.config.device.uuid)
- } else {
- format!("/api/v1/device_updates/{}/{}", self.config.device.uuid, path)
- };
+ /// `<Core server>/api/v1/mydevice/<device-id>$path`.
+ fn endpoint(&self, path: &str) -> Url {
+ let endpoint = format!("/api/v1/mydevice/{}{}", self.config.device.uuid, path);
self.config.core.server.join(&endpoint).expect("couldn't build endpoint url")
}
- /// Query the Core server to identify any new package updates available.
- pub fn get_pending_updates(&mut self) -> Result<Vec<PendingUpdateRequest>, Error> {
- let resp_rx = self.client.get(self.endpoint(""), None);
- let resp = resp_rx.recv().expect("no get_package_updates response received");
- let data = try!(resp);
- let text = try!(String::from_utf8(data));
- Ok(try!(json::decode::<Vec<PendingUpdateRequest>>(&text)))
+ /// Returns the path to a package on the device.
+ fn package_path(&self, id: UpdateRequestId) -> Result<String, Error> {
+ let mut path = PathBuf::new();
+ path.push(&self.config.device.packages_dir);
+ path.push(id);
+ Ok(try!(path.to_str().ok_or(Error::Parse(format!("Path is not valid UTF-8: {:?}", path)))).to_string())
}
- /// Download a specific update from the Core server.
- pub fn download_update(&mut self, id: UpdateRequestId) -> Result<DownloadComplete, Error> {
- let resp_rx = self.client.get(self.endpoint(&format!("{}/download", id)), None);
- let resp = resp_rx.recv().expect("no download_package_update response received");
- let data = try!(resp);
+ /// Query the Core server for any pending or in-flight package updates.
+ pub fn get_update_requests(&mut self) -> Result<Vec<UpdateRequest>, Error> {
+ let resp_rx = self.client.get(self.endpoint("/updates"), None);
+ let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't get new updates".to_string())));
+ let data = match resp {
+ Response::Success(data) => data,
+ Response::Failed(data) => return Err(Error::from(data)),
+ Response::Error(err) => return Err(err)
+ };
- let mut path = PathBuf::new();
- path.push(&self.config.device.packages_dir);
- path.push(id.clone()); // TODO: Use Content-Disposition filename from request?
- let mut file = try!(File::create(path.as_path()));
+ let text = try!(String::from_utf8(data.body));
+ Ok(try!(json::decode::<Vec<UpdateRequest>>(&text)))
+ }
- let _ = io::copy(&mut &*data, &mut file);
- let path = try!(path.to_str().ok_or(Error::Parse(format!("Path is not valid UTF-8: {:?}", path))));
+ /// Download a specific update from the Core server.
+ pub fn download_update(&mut self, id: UpdateRequestId) -> Result<DownloadComplete, Error> {
+ let resp_rx = self.client.get(self.endpoint(&format!("/updates/{}/download", id)), None);
+ let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't download update".to_string())));
+ let data = match resp {
+ Response::Success(data) => data,
+ Response::Failed(data) => return Err(Error::from(data)),
+ Response::Error(err) => return Err(err)
+ };
+ let path = try!(self.package_path(id.clone()));
+ let mut file = try!(File::create(&path));
+ let _ = io::copy(&mut &*data.body, &mut file);
Ok(DownloadComplete {
update_id: id,
update_image: path.to_string(),
@@ -63,46 +71,54 @@ impl<'c, 'h> Sota<'c, 'h> {
}
/// Install an update using the package manager.
- pub fn install_update(&mut self, download: DownloadComplete) -> Result<UpdateReport, UpdateReport> {
+ pub fn install_update(&mut self, id: UpdateRequestId) -> Result<UpdateReport, UpdateReport> {
let ref pacman = self.config.device.package_manager;
- pacman.install_package(&download.update_image).and_then(|(code, output)| {
- Ok(UpdateReport::single(download.update_id.clone(), code, output))
+ let path = self.package_path(id.clone()).expect("install_update expects a valid path");
+ pacman.install_package(&path).and_then(|(code, output)| {
+ let _ = fs::remove_file(&path).unwrap_or_else(|err| error!("couldn't remove installed package: {}", err));
+ Ok(UpdateReport::single(id.clone(), code, output))
}).or_else(|(code, output)| {
- Err(UpdateReport::single(download.update_id.clone(), code, output))
+ Err(UpdateReport::single(id.clone(), code, output))
})
}
- /// Get a list of the currently installed packages from the package manager.
- pub fn get_installed_packages(&mut self) -> Result<Vec<Package>, Error> {
- Ok(try!(self.config.device.package_manager.installed_packages()))
- }
-
/// Send a list of the currently installed packages to the Core server.
pub fn send_installed_packages(&mut self, packages: &Vec<Package>) -> Result<(), Error> {
let body = try!(json::encode(packages));
- let resp_rx = self.client.put(self.endpoint("installed"), Some(body.into_bytes()));
- let _ = resp_rx.recv().expect("no update_installed_packages response received")
- .map_err(|err| error!("update_installed_packages failed: {}", err));
- Ok(())
+ let resp_rx = self.client.put(self.endpoint("/installed"), Some(body.into_bytes()));
+ let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send installed packages".to_string())));
+
+ match resp {
+ Response::Success(_) => Ok(()),
+ Response::Failed(data) => Err(Error::from(data)),
+ Response::Error(err) => Err(err)
+ }
}
/// Send the outcome of a package update to the Core server.
pub fn send_update_report(&mut self, update_report: &UpdateReport) -> Result<(), Error> {
- let report = DeviceReport::new(&self.config.device.uuid, update_report);
- let body = try!(json::encode(&report));
- let url = self.endpoint(report.device);
+ let body = try!(json::encode(&update_report.operation_results));
+ let url = self.endpoint(&format!("/updates/{}", update_report.update_id));
let resp_rx = self.client.post(url, Some(body.into_bytes()));
- let resp = resp_rx.recv().expect("no send_install_report response received");
- let _ = try!(resp);
- Ok(())
+ let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send update report".to_string())));
+
+ match resp {
+ Response::Success(_) => Ok(()),
+ Response::Failed(data) => Err(Error::from(data)),
+ Response::Error(err) => Err(err)
+ }
}
/// Send system information from the device to the Core server.
pub fn send_system_info(&mut self, body: &str) -> Result<(), Error> {
- let resp_rx = self.client.put(self.endpoint("system_info"), Some(body.as_bytes().to_vec()));
- let resp = resp_rx.recv().expect("no send_system_info response received");
- let _ = try!(resp);
- Ok(())
+ let resp_rx = self.client.put(self.endpoint("/system_info"), Some(body.as_bytes().to_vec()));
+ let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send system info".to_string())));
+
+ match resp {
+ Response::Success(_) => Ok(()),
+ Response::Failed(data) => Err(Error::from(data)),
+ Response::Error(err) => Err(err)
+ }
}
}
@@ -112,19 +128,20 @@ mod tests {
use rustc_serialize::json;
use super::*;
- use datatype::{Config, Package, PendingUpdateRequest};
+ use datatype::{Config, Package, UpdateRequest, UpdateRequestStatus};
use http::TestClient;
#[test]
- fn test_get_pending_updates() {
- let pending_update = PendingUpdateRequest {
+ fn test_get_update_requests() {
+ let pending_update = UpdateRequest {
requestId: "someid".to_string(),
- installPos: 0,
+ status: UpdateRequestStatus::Pending,
packageId: Package {
name: "fake-pkg".to_string(),
version: "0.1.1".to_string()
},
+ installPos: 0,
createdAt: "2010-01-01".to_string()
};
@@ -134,7 +151,7 @@ mod tests {
client: &mut TestClient::from(vec![json.to_string()]),
};
- let updates: Vec<PendingUpdateRequest> = sota.get_pending_updates().unwrap();
+ let updates: Vec<UpdateRequest> = sota.get_update_requests().unwrap();
let ids: Vec<String> = updates.iter().map(|p| p.requestId.clone()).collect();
assert_eq!(ids, vec!["someid".to_string()])
}