// Copyright 2017 Jos van den Oever // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 2 of // the License or (at your option) version 3 or any later version // accepted by the membership of KDE e.V. (or its successor approved // by the membership of KDE e.V.), which shall act as a proxy // defined in Section 14 of version 3 of the license. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . use interface::*; use std::fs::*; use std::fs::read_dir; use std::path::PathBuf; use std::ffi::OsString; use std::default::Default; use std::thread; use std::sync::mpsc::{Receiver, Sender, channel}; use std::marker::Sync; pub struct DirEntry { name: OsString, metadata: Option, path: Option, icon: Vec, } impl Item for DirEntry { fn new(name: &str) -> DirEntry { DirEntry { name: OsString::from(name), metadata: metadata(name).ok(), path: None, icon: Vec::new(), } } fn can_fetch_more(&self) -> bool { self.metadata.as_ref().map_or(false, |m| m.is_dir()) } fn file_name(&self) -> String { self.name.to_string_lossy().to_string() } fn file_path(&self) -> Option { self.path.as_ref().map(|p| p.to_string_lossy().into()) } fn file_permissions(&self) -> i32 { 42 } fn file_type(&self) -> i32 { 0 } fn file_size(&self) -> Option { self.metadata.as_ref().map(|m| m.len()) } fn icon(&self) -> &[u8] { &self.icon } fn retrieve(id: usize, parents: Vec<&Self>, outgoing: &Sender<(usize, PathBuf)>) { let path: PathBuf = parents.into_iter().map(|e| &e.name).collect(); if let Err(e) = outgoing.send((id, path)) { eprintln!("{}", e); } } fn read_files( incoming: Receiver<(usize, PathBuf)>, outgoing: Sender<(usize, Vec)>, mut emit: FileSystemTreeEmitter, ) { thread::spawn(move || while let Ok((id, path)) = incoming.recv() { let mut v = Vec::new(); if let Ok(it) = read_dir(&path) { for i in it.filter_map(|v| v.ok()) { let de = DirEntry { name: i.file_name(), metadata: i.metadata().ok(), path: Some(i.path()), icon: Vec::new(), }; v.push(de); } } v.sort_by(|a, b| a.name.cmp(&b.name)); if let Ok(()) = outgoing.send((id, v)) { emit.new_data_ready(Some(id)); } else { return; } }); } } impl Default for DirEntry { fn default() -> DirEntry { DirEntry { name: OsString::new(), metadata: None, path: None, icon: Vec::new(), } } } pub trait Item: Default { fn new(name: &str) -> Self; fn can_fetch_more(&self) -> bool; fn retrieve(id: usize, parents: Vec<&Self>, outgoing: &Sender<(usize, PathBuf)>); fn read_files( incoming: Receiver<(usize, PathBuf)>, outgoing: Sender<(usize, Vec)>, emit: FileSystemTreeEmitter, ); fn file_name(&self) -> String; fn file_path(&self) -> Option; fn file_permissions(&self) -> i32; fn file_type(&self) -> i32; fn file_size(&self) -> Option; fn icon(&self) -> &[u8]; } pub type FileSystemTree = RGeneralItemModel; struct Entry { parent: Option, row: usize, children: Option>, data: T, } pub struct RGeneralItemModel { emit: FileSystemTreeEmitter, model: FileSystemTreeTree, entries: Vec>, path: Option, outgoing: Sender<(usize, PathBuf)>, incoming: Receiver<(usize, Vec)>, } impl RGeneralItemModel where T: Sync + Send, { fn reset(&mut self) { self.model.begin_reset_model(); self.entries.clear(); if let Some(ref path) = self.path { let root = Entry { parent: None, row: 0, children: None, data: T::new(path), }; self.entries.push(root); } self.model.end_reset_model(); } fn get(&self, index: usize) -> &Entry { &self.entries[index] } fn retrieve(&self, index: usize) { let parents = self.get_parents(index); T::retrieve(index, parents, &self.outgoing); } fn process_incoming(&mut self) { while let Ok((id, entries)) = self.incoming.try_recv() { if self.entries[id].children.is_some() { continue; } let mut new_entries = Vec::new(); let mut children = Vec::new(); for (r, d) in entries.into_iter().enumerate() { new_entries.push(Entry { parent: Some(id), row: r, children: None, data: d, }); children.push(self.entries.len() + r); } if new_entries.is_empty() { self.entries[id].children = Some(children); } else { self.model.begin_insert_rows( Some(id), 0, new_entries.len() - 1, ); self.entries[id].children = Some(children); self.entries.append(&mut new_entries); self.model.end_insert_rows(); } } } fn get_parents(&self, id: usize) -> Vec<&T> { let mut pos = Some(id); let mut e = Vec::new(); while let Some(p) = pos { e.push(p); pos = self.entries[p].parent; } e.into_iter().rev().map(|i| &self.entries[i].data).collect() } } impl FileSystemTreeTrait for RGeneralItemModel where T: Sync + Send, { fn new(mut emit: FileSystemTreeEmitter, model: FileSystemTreeTree) -> Self { let (outgoing, thread_incoming) = channel(); let (thread_outgoing, incoming) = channel::<(usize, Vec)>(); T::read_files(thread_incoming, thread_outgoing, emit.clone()); let mut tree: RGeneralItemModel = RGeneralItemModel { emit, model, entries: Vec::new(), path: None, outgoing, incoming, }; tree.reset(); tree } fn emit(&mut self) -> &mut FileSystemTreeEmitter { &mut self.emit } fn path(&self) -> Option<&str> { self.path.as_ref().map(|s| &s[..]) } fn set_path(&mut self, value: Option) { if self.path != value { self.path = value; self.emit.path_changed(); self.reset(); } } fn can_fetch_more(&self, index: Option) -> bool { let entry = self.get(index.unwrap_or(0)); entry.children.is_none() && entry.data.can_fetch_more() } fn fetch_more(&mut self, index: Option) { self.process_incoming(); if !self.can_fetch_more(index) { return; } self.retrieve(index.unwrap_or(0)); } fn row_count(&self, index: Option) -> usize { if self.entries.is_empty() { return 0; } if let Some(i) = index { let entry = self.get(i); if let Some(ref children) = entry.children { children.len() } else { self.retrieve(i); 0 } } else { 1 } } fn index(&self, index: Option, row: usize) -> usize { if let Some(index) = index { self.get(index).children.as_ref().unwrap()[row] } else { 0 } } fn parent(&self, index: usize) -> Option { self.entries[index].parent } fn row(&self, index: usize) -> usize { self.entries[index].row } fn check_row(&self, index: usize, _row: usize) -> Option { if index < self.entries.len() { Some(self.row(index)) } else { None } } fn file_name(&self, index: usize) -> String { self.get(index).data.file_name() } fn file_permissions(&self, index: usize) -> i32 { self.get(index).data.file_permissions() } #[allow(unused_variables)] fn file_icon(&self, index: usize) -> &[u8] { self.get(index).data.icon() } fn file_path(&self, index: usize) -> Option { self.get(index).data.file_path() } fn file_type(&self, index: usize) -> i32 { self.get(index).data.file_type() } fn file_size(&self, index: usize) -> Option { self.get(index).data.file_size() } }