filepicker

This commit is contained in:
Zhen Zhang 2016-06-11 01:44:42 +08:00
parent 0c11e8340b
commit 256c7e894e
5 changed files with 135 additions and 46 deletions

View file

@ -7,48 +7,88 @@ use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use mime_classifier::MIMEClassifier; use mime_classifier::MIMEClassifier;
use mime_guess::guess_mime_type_opt; use mime_guess::guess_mime_type_opt;
use net_traits::blob_url_store::{BlobURLStoreEntry, BlobURLStoreError}; 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 net_traits::filemanager_thread::{SelectedFile, FileManagerThreadError, SelectedFileId};
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
#[cfg(any(target_os = "macos", target_os = "linux"))]
use tinyfiledialogs;
use url::Origin; use url::Origin;
use util::thread::spawn_named; use util::thread::spawn_named;
use uuid::Uuid; use uuid::Uuid;
pub trait FileManagerThreadFactory { pub trait FileManagerThreadFactory<UI: 'static + UIProvider> {
fn new() -> Self; 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 /// Create a FileManagerThread
fn new() -> IpcSender<FileManagerThreadMsg> { fn new(ui: &'static UI) -> IpcSender<FileManagerThreadMsg> {
let (chan, recv) = ipc::channel().unwrap(); let (chan, recv) = ipc::channel().unwrap();
spawn_named("FileManager".to_owned(), move || { spawn_named("FileManager".to_owned(), move || {
FileManager::new(recv).start(); FileManager::new(recv, ui).start();
}); });
chan chan
} }
} }
struct FileManager { struct FileManager<UI: 'static + UIProvider> {
receiver: IpcReceiver<FileManagerThreadMsg>, receiver: IpcReceiver<FileManagerThreadMsg>,
idmap: HashMap<Uuid, PathBuf>, idmap: HashMap<Uuid, PathBuf>,
classifier: Arc<MIMEClassifier>, classifier: Arc<MIMEClassifier>,
blob_url_store: Arc<RwLock<BlobURLStore>>, blob_url_store: Arc<RwLock<BlobURLStore>>,
ui: &'static UI,
} }
impl FileManager { impl<UI: 'static + UIProvider> FileManager<UI> {
fn new(recv: IpcReceiver<FileManagerThreadMsg>) -> FileManager { fn new(recv: IpcReceiver<FileManagerThreadMsg>, ui: &'static UI) -> FileManager<UI> {
FileManager { FileManager {
receiver: recv, receiver: recv,
idmap: HashMap::new(), idmap: HashMap::new(),
classifier: Arc::new(MIMEClassifier::new()), classifier: Arc::new(MIMEClassifier::new()),
blob_url_store: Arc::new(RwLock::new(BlobURLStore::new())), blob_url_store: Arc::new(RwLock::new(BlobURLStore::new())),
ui: ui
} }
} }
@ -56,8 +96,8 @@ impl FileManager {
fn start(&mut self) { fn start(&mut self) {
loop { loop {
match self.receiver.recv().unwrap() { match self.receiver.recv().unwrap() {
FileManagerThreadMsg::SelectFile(sender) => self.select_file(sender), FileManagerThreadMsg::SelectFile(filter, sender) => self.select_file(filter, sender),
FileManagerThreadMsg::SelectFiles(sender) => self.select_files(sender), FileManagerThreadMsg::SelectFiles(filter, sender) => self.select_files(filter, sender),
FileManagerThreadMsg::ReadFile(sender, id) => { FileManagerThreadMsg::ReadFile(sender, id) => {
match self.try_read_file(id) { match self.try_read_file(id) {
Ok(buffer) => { let _ = sender.send(Ok(buffer)); } Ok(buffer) => { let _ = sender.send(Ok(buffer)); }
@ -74,22 +114,34 @@ impl FileManager {
}; };
} }
} }
}
impl FileManager { fn select_file(&mut self, _filter: Vec<FilterPattern>,
fn select_file(&mut self, sender: IpcSender<FileManagerResult<SelectedFile>>) { sender: IpcSender<FileManagerResult<SelectedFile>>) {
// TODO: Pull the dialog UI in and get selected match self.ui.open_file_dialog("", None) {
// XXX: "test.txt" is "tests/unit/net/test.txt", for temporary testing purpose Some(s) => {
let selected_path = Path::new("test.txt"); let selected_path = Path::new(&s);
match self.create_entry(selected_path) { match self.create_entry(selected_path) {
Some(triple) => { let _ = sender.send(Ok(triple)); } Some(triple) => { let _ = sender.send(Ok(triple)); }
None => { let _ = sender.send(Err(FileManagerThreadError::InvalidSelection)); } 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>>>) { fn select_files(&mut self, _filter: Vec<FilterPattern>,
let selected_paths = vec![Path::new("test.txt")]; sender: IpcSender<FileManagerResult<Vec<SelectedFile>>>) {
match self.ui.open_file_dialog_multi("", None) {
Some(v) => {
let mut selected_paths = vec![];
for s in &v {
selected_paths.push(Path::new(s));
}
let mut replies = vec![]; let mut replies = vec![];
@ -102,6 +154,12 @@ impl FileManager {
let _ = sender.send(Ok(replies)); let _ = sender.send(Ok(replies));
} }
None => {
let _ = sender.send(Err(FileManagerThreadError::UserCancelled));
return;
}
}
}
fn create_entry(&mut self, file_path: &Path) -> Option<SelectedFile> { fn create_entry(&mut self, file_path: &Path) -> Option<SelectedFile> {
match File::open(file_path) { match File::open(file_path) {
@ -195,4 +253,3 @@ impl BlobURLStore {
self.entries.remove(&id); self.entries.remove(&id);
} }
} }

View file

@ -13,7 +13,7 @@ use data_loader;
use devtools_traits::DevtoolsControlMsg; use devtools_traits::DevtoolsControlMsg;
use fetch::methods::{fetch, FetchContext}; use fetch::methods::{fetch, FetchContext};
use file_loader; use file_loader;
use filemanager_thread::FileManagerThreadFactory; use filemanager_thread::{FileManagerThreadFactory, TFDProvider};
use hsts::HstsList; use hsts::HstsList;
use http_loader::{self, HttpState}; use http_loader::{self, HttpState};
use hyper::client::pool::Pool; use hyper::client::pool::Pool;
@ -49,6 +49,8 @@ use util::prefs;
use util::thread::spawn_named; use util::thread::spawn_named;
use websocket_loader; use websocket_loader;
const TFD_PROVIDER: &'static TFDProvider = &TFDProvider;
pub enum ProgressSender { pub enum ProgressSender {
Channel(IpcSender<ProgressMsg>), Channel(IpcSender<ProgressMsg>),
Listener(AsyncResponseTarget), Listener(AsyncResponseTarget),
@ -161,7 +163,7 @@ pub fn new_resource_threads(user_agent: String,
profiler_chan: ProfilerChan) -> ResourceThreads { profiler_chan: ProfilerChan) -> ResourceThreads {
ResourceThreads::new(new_core_resource_thread(user_agent, devtools_chan, profiler_chan), ResourceThreads::new(new_core_resource_thread(user_agent, devtools_chan, profiler_chan),
StorageThreadFactory::new(), StorageThreadFactory::new(),
FileManagerThreadFactory::new()) FileManagerThreadFactory::new(TFD_PROVIDER))
} }

View file

@ -18,13 +18,16 @@ pub struct SelectedFile {
pub type_string: String, pub type_string: String,
} }
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FilterPattern(pub String);
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub enum FileManagerThreadMsg { pub enum FileManagerThreadMsg {
/// Select a single file, return triple (FileID, FileName, lastModified) /// 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 /// 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 /// Read file, return the bytes
ReadFile(IpcSender<FileManagerResult<Vec<u8>>>, SelectedFileId), ReadFile(IpcSender<FileManagerResult<Vec<u8>>>, SelectedFileId),
@ -43,8 +46,10 @@ pub type FileManagerResult<T> = Result<T, FileManagerThreadError>;
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub enum FileManagerThreadError { pub enum FileManagerThreadError {
/// The selection action is invalid, nothing is selected /// The selection action is invalid due to exceptional reason
InvalidSelection, InvalidSelection,
/// The selection action is cancelled by user
UserCancelled,
/// Failure to process file information such as file name, modified time etc. /// Failure to process file information such as file name, modified time etc.
FileInfoProcessingError, FileInfoProcessingError,
/// Failure to read the file content /// Failure to read the file content

View file

@ -33,7 +33,7 @@ use dom::validation::Validatable;
use dom::virtualmethods::VirtualMethods; use dom::virtualmethods::VirtualMethods;
use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::ipc::{self, IpcSender};
use net_traits::IpcSend; use net_traits::IpcSend;
use net_traits::filemanager_thread::FileManagerThreadMsg; use net_traits::filemanager_thread::{FileManagerThreadMsg, FilterPattern};
use script_traits::ScriptMsg as ConstellationMsg; use script_traits::ScriptMsg as ConstellationMsg;
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::cell::Cell; 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/#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/#checkbox-state-%28type=checkbox%29:activation-behaviour-2
// https://html.spec.whatwg.org/multipage/#radio-button-state-%28type=radio%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(), | InputType::InputCheckbox | InputType::InputRadio => self.is_mutable(),
_ => false _ => false
} }
@ -1140,9 +1140,11 @@ impl Activatable for HTMLInputElement {
let mut files: Vec<Root<File>> = vec![]; let mut files: Vec<Root<File>> = vec![];
let mut error = None; let mut error = None;
let filter = filter_from_accept(self.Accept());
if self.Multiple() { if self.Multiple() {
let (chan, recv) = ipc::channel().expect("Error initializing channel"); 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(); let _ = filemanager.send(msg).unwrap();
match recv.recv().expect("IpcSender side error") { match recv.recv().expect("IpcSender side error") {
@ -1155,7 +1157,7 @@ impl Activatable for HTMLInputElement {
}; };
} else { } else {
let (chan, recv) = ipc::channel().expect("Error initializing channel"); 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(); let _ = filemanager.send(msg).unwrap();
match recv.recv().expect("IpcSender side error") { 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![]
}

View file

@ -3,15 +3,29 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::ipc::{self, IpcSender};
use net::filemanager_thread::FileManagerThreadFactory; use net::filemanager_thread::{FileManagerThreadFactory, UIProvider};
use net_traits::filemanager_thread::{FileManagerThreadMsg, FileManagerThreadError}; use net_traits::filemanager_thread::{FilterPattern, FileManagerThreadMsg, FileManagerThreadError};
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
use std::path::PathBuf; 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] #[test]
fn test_filemanager() { 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 // 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"); 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) handler.read_to_end(&mut test_file_content)
.expect("Read tests/unit/net/test.txt error"); .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" // Try to select a dummy file "tests/unit/net/test.txt"
let (tx, rx) = ipc::channel().unwrap(); 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") let selected = rx.recv().expect("File manager channel is broken")
.expect("The file manager failed to find test.txt"); .expect("The file manager failed to find test.txt");
@ -66,7 +82,7 @@ fn test_filemanager() {
{ {
let (tx, rx) = ipc::channel().unwrap(); 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"); assert!(rx.try_recv().is_err(), "The thread should not respond normally after exited");
} }