diff options
-rw-r--r-- | components/net/filemanager_thread.rs | 121 | ||||
-rw-r--r-- | components/net/resource_thread.rs | 6 | ||||
-rw-r--r-- | components/net_traits/filemanager_thread.rs | 11 | ||||
-rw-r--r-- | components/script/dom/htmlinputelement.rs | 17 | ||||
-rw-r--r-- | tests/unit/net/filemanager_thread.rs | 26 |
5 files changed, 135 insertions, 46 deletions
diff --git a/components/net/filemanager_thread.rs b/components/net/filemanager_thread.rs index 5b18b332fdc..6cc47adf060 100644 --- a/components/net/filemanager_thread.rs +++ b/components/net/filemanager_thread.rs @@ -7,48 +7,88 @@ use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use mime_classifier::MIMEClassifier; use mime_guess::guess_mime_type_opt; use net_traits::blob_url_store::{BlobURLStoreEntry, BlobURLStoreError}; -use net_traits::filemanager_thread::{FileManagerThreadMsg, FileManagerResult}; +use net_traits::filemanager_thread::{FileManagerThreadMsg, FileManagerResult, FilterPattern}; use net_traits::filemanager_thread::{SelectedFile, FileManagerThreadError, SelectedFileId}; use std::collections::HashMap; use std::fs::File; use std::io::Read; use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock}; +#[cfg(any(target_os = "macos", target_os = "linux"))] +use tinyfiledialogs; use url::Origin; use util::thread::spawn_named; use uuid::Uuid; -pub trait FileManagerThreadFactory { - fn new() -> Self; +pub trait FileManagerThreadFactory<UI: 'static + UIProvider> { + fn new(&'static UI) -> Self; } -impl FileManagerThreadFactory for IpcSender<FileManagerThreadMsg> { +pub trait UIProvider where Self: Sync { + fn open_file_dialog(&self, path: &str, + filter: Option<(&[&str], &str)>) -> Option<String>; + + fn open_file_dialog_multi(&self, path: &str, + filter: Option<(&[&str], &str)>) -> Option<Vec<String>>; +} + +pub struct TFDProvider; + +impl UIProvider for TFDProvider { + #[cfg(any(target_os = "macos", target_os = "linux"))] + fn open_file_dialog(&self, path: &str, + filter: Option<(&[&str], &str)>) -> Option<String> { + tinyfiledialogs::open_file_dialog("Pick a file", path, filter) + } + + #[cfg(any(target_os = "macos", target_os = "linux"))] + fn open_file_dialog_multi(&self, path: &str, + filter: Option<(&[&str], &str)>) -> Option<Vec<String>> { + tinyfiledialogs::open_file_dialog_multi("Pick files", path, filter) + } + + #[cfg(not(any(target_os = "macos", target_os = "linux")))] + fn open_file_dialog(&self, path: &str, + filter: Option<(&[&str], &str)>) -> Option<String> { + None + } + + #[cfg(not(any(target_os = "macos", target_os = "linux")))] + fn open_file_dialog_multi(&self, path: &str, + filter: Option<(&[&str], &str)>) -> Option<Vec<String>> { + None + } +} + +impl<UI: 'static + UIProvider> FileManagerThreadFactory<UI> for IpcSender<FileManagerThreadMsg> { /// Create a FileManagerThread - fn new() -> IpcSender<FileManagerThreadMsg> { + fn new(ui: &'static UI) -> IpcSender<FileManagerThreadMsg> { let (chan, recv) = ipc::channel().unwrap(); spawn_named("FileManager".to_owned(), move || { - FileManager::new(recv).start(); + FileManager::new(recv, ui).start(); }); chan } } -struct FileManager { +struct FileManager<UI: 'static + UIProvider> { receiver: IpcReceiver<FileManagerThreadMsg>, idmap: HashMap<Uuid, PathBuf>, classifier: Arc<MIMEClassifier>, blob_url_store: Arc<RwLock<BlobURLStore>>, + ui: &'static UI, } -impl FileManager { - fn new(recv: IpcReceiver<FileManagerThreadMsg>) -> FileManager { +impl<UI: 'static + UIProvider> FileManager<UI> { + fn new(recv: IpcReceiver<FileManagerThreadMsg>, ui: &'static UI) -> FileManager<UI> { FileManager { receiver: recv, idmap: HashMap::new(), classifier: Arc::new(MIMEClassifier::new()), blob_url_store: Arc::new(RwLock::new(BlobURLStore::new())), + ui: ui } } @@ -56,8 +96,8 @@ impl FileManager { fn start(&mut self) { loop { match self.receiver.recv().unwrap() { - FileManagerThreadMsg::SelectFile(sender) => self.select_file(sender), - FileManagerThreadMsg::SelectFiles(sender) => self.select_files(sender), + FileManagerThreadMsg::SelectFile(filter, sender) => self.select_file(filter, sender), + FileManagerThreadMsg::SelectFiles(filter, sender) => self.select_files(filter, sender), FileManagerThreadMsg::ReadFile(sender, id) => { match self.try_read_file(id) { Ok(buffer) => { let _ = sender.send(Ok(buffer)); } @@ -74,33 +114,51 @@ impl FileManager { }; } } -} -impl FileManager { - fn select_file(&mut self, sender: IpcSender<FileManagerResult<SelectedFile>>) { - // TODO: Pull the dialog UI in and get selected - // XXX: "test.txt" is "tests/unit/net/test.txt", for temporary testing purpose - let selected_path = Path::new("test.txt"); + fn select_file(&mut self, _filter: Vec<FilterPattern>, + sender: IpcSender<FileManagerResult<SelectedFile>>) { + match self.ui.open_file_dialog("", None) { + Some(s) => { + let selected_path = Path::new(&s); - match self.create_entry(selected_path) { - Some(triple) => { let _ = sender.send(Ok(triple)); } - None => { let _ = sender.send(Err(FileManagerThreadError::InvalidSelection)); } - }; + match self.create_entry(selected_path) { + Some(triple) => { let _ = sender.send(Ok(triple)); } + None => { let _ = sender.send(Err(FileManagerThreadError::InvalidSelection)); } + }; + } + None => { + let _ = sender.send(Err(FileManagerThreadError::UserCancelled)); + return; + } + } } - fn select_files(&mut self, sender: IpcSender<FileManagerResult<Vec<SelectedFile>>>) { - let selected_paths = vec![Path::new("test.txt")]; + fn select_files(&mut self, _filter: Vec<FilterPattern>, + sender: IpcSender<FileManagerResult<Vec<SelectedFile>>>) { + match self.ui.open_file_dialog_multi("", None) { + Some(v) => { + let mut selected_paths = vec![]; - let mut replies = vec![]; + for s in &v { + selected_paths.push(Path::new(s)); + } - for path in selected_paths { - match self.create_entry(path) { - Some(triple) => replies.push(triple), - None => { let _ = sender.send(Err(FileManagerThreadError::InvalidSelection)); } - }; - } + let mut replies = vec![]; - let _ = sender.send(Ok(replies)); + for path in selected_paths { + match self.create_entry(path) { + Some(triple) => replies.push(triple), + None => { let _ = sender.send(Err(FileManagerThreadError::InvalidSelection)); } + }; + } + + let _ = sender.send(Ok(replies)); + } + None => { + let _ = sender.send(Err(FileManagerThreadError::UserCancelled)); + return; + } + } } fn create_entry(&mut self, file_path: &Path) -> Option<SelectedFile> { @@ -195,4 +253,3 @@ impl BlobURLStore { self.entries.remove(&id); } } - diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index 6e94fd1cf03..dfcbf1eedd3 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -13,7 +13,7 @@ use data_loader; use devtools_traits::DevtoolsControlMsg; use fetch::methods::{fetch, FetchContext}; use file_loader; -use filemanager_thread::FileManagerThreadFactory; +use filemanager_thread::{FileManagerThreadFactory, TFDProvider}; use hsts::HstsList; use http_loader::{self, HttpState}; use hyper::client::pool::Pool; @@ -49,6 +49,8 @@ use util::prefs; use util::thread::spawn_named; use websocket_loader; +const TFD_PROVIDER: &'static TFDProvider = &TFDProvider; + pub enum ProgressSender { Channel(IpcSender<ProgressMsg>), Listener(AsyncResponseTarget), @@ -161,7 +163,7 @@ pub fn new_resource_threads(user_agent: String, profiler_chan: ProfilerChan) -> ResourceThreads { ResourceThreads::new(new_core_resource_thread(user_agent, devtools_chan, profiler_chan), StorageThreadFactory::new(), - FileManagerThreadFactory::new()) + FileManagerThreadFactory::new(TFD_PROVIDER)) } diff --git a/components/net_traits/filemanager_thread.rs b/components/net_traits/filemanager_thread.rs index 3182161c9eb..122b99511b9 100644 --- a/components/net_traits/filemanager_thread.rs +++ b/components/net_traits/filemanager_thread.rs @@ -18,13 +18,16 @@ pub struct SelectedFile { pub type_string: String, } +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct FilterPattern(pub String); + #[derive(Deserialize, Serialize)] pub enum FileManagerThreadMsg { /// Select a single file, return triple (FileID, FileName, lastModified) - SelectFile(IpcSender<FileManagerResult<SelectedFile>>), + SelectFile(Vec<FilterPattern>, IpcSender<FileManagerResult<SelectedFile>>), /// Select multiple files, return a vector of triples - SelectFiles(IpcSender<FileManagerResult<Vec<SelectedFile>>>), + SelectFiles(Vec<FilterPattern>, IpcSender<FileManagerResult<Vec<SelectedFile>>>), /// Read file, return the bytes ReadFile(IpcSender<FileManagerResult<Vec<u8>>>, SelectedFileId), @@ -43,8 +46,10 @@ pub type FileManagerResult<T> = Result<T, FileManagerThreadError>; #[derive(Debug, Deserialize, Serialize)] pub enum FileManagerThreadError { - /// The selection action is invalid, nothing is selected + /// The selection action is invalid due to exceptional reason InvalidSelection, + /// The selection action is cancelled by user + UserCancelled, /// Failure to process file information such as file name, modified time etc. FileInfoProcessingError, /// Failure to read the file content diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 4c164f2c819..561ccc647f2 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -33,7 +33,7 @@ use dom::validation::Validatable; use dom::virtualmethods::VirtualMethods; use ipc_channel::ipc::{self, IpcSender}; use net_traits::IpcSend; -use net_traits::filemanager_thread::FileManagerThreadMsg; +use net_traits::filemanager_thread::{FileManagerThreadMsg, FilterPattern}; use script_traits::ScriptMsg as ConstellationMsg; use std::borrow::ToOwned; use std::cell::Cell; @@ -993,7 +993,7 @@ impl Activatable for HTMLInputElement { // https://html.spec.whatwg.org/multipage/#reset-button-state-%28type=reset%29:activation-behaviour-2 // https://html.spec.whatwg.org/multipage/#checkbox-state-%28type=checkbox%29:activation-behaviour-2 // https://html.spec.whatwg.org/multipage/#radio-button-state-%28type=radio%29:activation-behaviour-2 - InputType::InputSubmit | InputType::InputReset + InputType::InputSubmit | InputType::InputReset | InputType::InputFile | InputType::InputCheckbox | InputType::InputRadio => self.is_mutable(), _ => false } @@ -1140,9 +1140,11 @@ impl Activatable for HTMLInputElement { let mut files: Vec<Root<File>> = vec![]; let mut error = None; + let filter = filter_from_accept(self.Accept()); + if self.Multiple() { let (chan, recv) = ipc::channel().expect("Error initializing channel"); - let msg = FileManagerThreadMsg::SelectFiles(chan); + let msg = FileManagerThreadMsg::SelectFiles(filter, chan); let _ = filemanager.send(msg).unwrap(); match recv.recv().expect("IpcSender side error") { @@ -1155,7 +1157,7 @@ impl Activatable for HTMLInputElement { }; } else { let (chan, recv) = ipc::channel().expect("Error initializing channel"); - let msg = FileManagerThreadMsg::SelectFile(chan); + let msg = FileManagerThreadMsg::SelectFile(filter, chan); let _ = filemanager.send(msg).unwrap(); match recv.recv().expect("IpcSender side error") { @@ -1228,3 +1230,10 @@ impl Activatable for HTMLInputElement { } } } + +fn filter_from_accept(_s: DOMString) -> Vec<FilterPattern> { + /// TODO: it means not pattern restriction now + /// Blocked by https://github.com/cybergeek94/mime_guess/issues/19 + vec![] +} + diff --git a/tests/unit/net/filemanager_thread.rs b/tests/unit/net/filemanager_thread.rs index fe03bdc401e..43cda6b8244 100644 --- a/tests/unit/net/filemanager_thread.rs +++ b/tests/unit/net/filemanager_thread.rs @@ -3,15 +3,29 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use ipc_channel::ipc::{self, IpcSender}; -use net::filemanager_thread::FileManagerThreadFactory; -use net_traits::filemanager_thread::{FileManagerThreadMsg, FileManagerThreadError}; +use net::filemanager_thread::{FileManagerThreadFactory, UIProvider}; +use net_traits::filemanager_thread::{FilterPattern, FileManagerThreadMsg, FileManagerThreadError}; use std::fs::File; use std::io::Read; use std::path::PathBuf; +const TEST_PROVIDER: &'static TestProvider = &TestProvider; + +struct TestProvider; + +impl UIProvider for TestProvider { + fn open_file_dialog(&self, _: &str, _: Option<(&[&str], &str)>) -> Option<String> { + Some("test.txt".to_string()) + } + + fn open_file_dialog_multi(&self, _: &str, _: Option<(&[&str], &str)>) -> Option<Vec<String>> { + Some(vec!["test.txt".to_string()]) + } +} + #[test] fn test_filemanager() { - let chan: IpcSender<FileManagerThreadMsg> = FileManagerThreadFactory::new(); + let chan: IpcSender<FileManagerThreadMsg> = FileManagerThreadFactory::new(TEST_PROVIDER); // Try to open a dummy file "tests/unit/net/test.txt" in tree let mut handler = File::open("test.txt").expect("test.txt is stolen"); @@ -20,11 +34,13 @@ fn test_filemanager() { handler.read_to_end(&mut test_file_content) .expect("Read tests/unit/net/test.txt error"); + let patterns = vec![FilterPattern(".txt".to_string())]; + { // Try to select a dummy file "tests/unit/net/test.txt" let (tx, rx) = ipc::channel().unwrap(); - chan.send(FileManagerThreadMsg::SelectFile(tx)).unwrap(); + chan.send(FileManagerThreadMsg::SelectFile(patterns.clone(), tx)).unwrap(); let selected = rx.recv().expect("File manager channel is broken") .expect("The file manager failed to find test.txt"); @@ -66,7 +82,7 @@ fn test_filemanager() { { let (tx, rx) = ipc::channel().unwrap(); - let _ = chan.send(FileManagerThreadMsg::SelectFile(tx)); + let _ = chan.send(FileManagerThreadMsg::SelectFile(patterns.clone(), tx)); assert!(rx.try_recv().is_err(), "The thread should not respond normally after exited"); } |