1use dev_prefix::*;
2
3use std::time::Duration;
4use std::mem;
5use std::sync::Mutex;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::thread;
8
9use nickel::{HttpRouter, MediaType, MiddlewareResult, Nickel, Request, Response,
10 StaticFilesHandler};
11use nickel::status::StatusCode;
12use serde_json;
13
14use tar::Archive;
15use tempdir::TempDir;
16
17use types::{Project, ServeCmd};
18use export::ProjectData;
19use ctrlc;
20
21mod constants;
22pub mod utils;
23mod handler;
24mod crud;
25
26#[cfg(test)]
27mod tests;
28
29const WEB_FRONTEND_TAR: &'static [u8] = include_bytes!("data/web-ui.tar");
30const REPLACE_FLAGS: &str = "{/* REPLACE WITH FLAGS */}";
31
32pub struct LockedData {
33 pub cmd: ServeCmd,
34 pub project: Project,
35 pub project_data: ProjectData,
36}
37
38lazy_static! {
39 static ref LOCKED: Mutex<LockedData> = Mutex::new(
40 LockedData {
41 cmd: ServeCmd::default(),
42 project_data: ProjectData::default(),
43 project: Project::default(),
44 }
45 );
46}
47
48fn setup_headers(res: &mut Response) {
49 let head = res.headers_mut();
50 let bv = |s: &str| Vec::from(s.as_bytes());
51 head.set_raw("Access-Control-Allow-Origin", vec![bv("*")]);
52 head.set_raw(
53 "Access-Control-Allow-Methods",
54 vec![bv("GET, POST, OPTIONS, PUT, PATCH, DELETE")],
55 );
56 head.set_raw(
57 "Access-Control-Allow-Headers",
58 vec![bv("X-Requested-With,content-type")],
59 );
60}
61
62fn config_json_res(res: &mut Response) {
63 res.set(MediaType::Json);
64 res.set(StatusCode::Ok);
65}
66
67fn handle_artifacts<'a>(req: &mut Request, mut res: Response<'a>) -> MiddlewareResult<'a> {
68 setup_headers(&mut res);
69 debug!("handling json-rpc request");
70
71 let mut body = vec![];
72 req.origin.read_to_end(&mut body).unwrap();
73 let body = match str::from_utf8(&body) {
74 Ok(b) => b,
75 Err(e) => {
76 res.set(StatusCode::BadRequest);
77 return res.send(format!("invalid utf8: {:?}", e));
78 }
79 };
80
81 debug!("body: {:?}", body);
82 match handler::RPC_HANDLER.handle_request_sync(body) {
83 Some(body) => {
84 trace!("- response {}", body);
85 config_json_res(&mut res);
86 res.send(body)
87 }
88 None => {
89 let msg = "InternalServerError: Got None from json-rpc handler";
90 error!("{}", msg);
91 res.set(StatusCode::InternalServerError);
92 res.send(msg)
93 }
94 }
95}
96
97fn handle_options<'a>(_: &mut Request, mut res: Response<'a>) -> MiddlewareResult<'a> {
98 setup_headers(&mut res);
99 res.set(StatusCode::Ok);
100 res.send("ok")
101}
102
103fn host_frontend(server: &mut Nickel, cmd: &ServeCmd) -> TempDir {
107 let tmp_dir = TempDir::new("artifact-web-ui").expect("unable to create temporary directory");
110 let dir = tmp_dir.path().to_path_buf(); info!("Unpacking web-ui at: {}", dir.display());
112
113 let mut archive = Archive::new(WEB_FRONTEND_TAR);
114 archive.unpack(&dir).expect("Unable to unpack web frontend");
115
116 let app_js_path = dir.join("app.js");
118 let mut app_js = fs::OpenOptions::new()
119 .read(true)
120 .write(true)
121 .open(app_js_path)
122 .expect("couldn't open app.js");
123 let mut text = String::new();
124 app_js
125 .read_to_string(&mut text)
126 .expect("app.js couldn't be read");
127 app_js.seek(SeekFrom::Start(0)).unwrap();
128 app_js.set_len(0).unwrap(); assert!(text.contains(REPLACE_FLAGS));
132 app_js
133 .write_all(
134 text.replace(REPLACE_FLAGS, &serde_json::to_string(cmd).unwrap())
135 .as_bytes(),
136 )
137 .unwrap();
138 app_js.flush().unwrap();
139
140 server.utilize(StaticFilesHandler::new(&dir));
141 println!("Hosting web ui at {}", cmd.addr);
142 tmp_dir
143}
144
145#[allow(unused_variables)] pub fn start_api(project: Project, cmd: &ServeCmd) {
148 {
151 let mut locked = LOCKED.lock().unwrap();
152 let lref = locked.deref_mut();
153 lref.project_data = project.to_data();
154 lref.project = project;
155 lref.cmd = cmd.clone();
156 }
157
158 let endpoint = "/json-rpc";
159 let mut server = Box::new(Nickel::new());
160
161 server.get(endpoint, handle_artifacts);
162 server.put(endpoint, handle_artifacts);
163 server.options(endpoint, handle_options);
164
165 let running = Arc::new(AtomicBool::new(true));
166 let r = running.clone();
167 ctrlc::set_handler(move || { r.store(false, Ordering::SeqCst); })
168 .expect("Error setting Ctrl-C handler");
169
170 let tmp_dir = host_frontend(&mut server, cmd);
173
174 let addr = cmd.addr.clone();
176 let th = thread::spawn(move || {
177 server.listen(&addr).expect("cannot connect to port");
178 });
179
180 println!("exit with ctrlc+C or SIGINT");
181 while running.load(Ordering::SeqCst) {
182 thread::sleep(Duration::new(0, 10 * 1e6 as u32));
183 }
184
185 debug!("Got SIGINT, cleaning up");
186 let locked = LOCKED.lock().unwrap();
187 mem::forget(locked); debug!("All cleaned up, exiting");
189}