diff --git a/Cargo.toml b/Cargo.toml index 39c3d96..0c06f40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ gtk-sys = "^0.4.0" libpijul = "^0.7.3" log = "^0.3.8" base64 = "^0.6.0" +chrono = "^0.4.0" [dependencies.gdk] features = [ diff --git a/data/ui/window.glade b/data/ui/window.glade index 7623b96..59baf50 100644 --- a/data/ui/window.glade +++ b/data/ui/window.glade @@ -60,14 +60,89 @@ True False - + + True + False + + + True + False + Branch + + + False + True + 2 + 0 + + + + + True + False + + + False + True + 2 + 1 + + + + + False + True + 0 + + + + True False False True - 0 + 1 + + + + + True + False + 2 + + + True + False + Hash + + + False + True + 2 + 0 + + + + + True + True + False + + + True + True + 2 + 1 + + + + + True + True + 2 + 2 diff --git a/src/appstate.rs b/src/appstate.rs new file mode 100644 index 0000000..4df142b --- /dev/null +++ b/src/appstate.rs @@ -0,0 +1,20 @@ +use gtk; +use ui::gui::*; + + +pub struct AppS { + _cant_construct: (), + pub gui: Gui, +} + + +impl AppS { + pub fn new() -> Self { + let builder = gtk::Builder::new_from_string(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/data/ui/window.glade"))); + + return AppS { + _cant_construct: (), + gui: Gui::new(builder), + } + } +} diff --git a/src/main.rs b/src/main.rs index fc26cfe..ae62e7e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ extern crate gtk; extern crate gtk_sys; extern crate libpijul; extern crate base64; +extern crate chrono; extern crate flexi_logger; #[macro_use] @@ -17,9 +18,12 @@ extern crate log; #[macro_use] extern crate error_chain; -mod ui; +mod appstate; mod errors; +mod pijul_glue; +mod ui; +use appstate::*; use ui::entry::*; use std::rc::Rc; diff --git a/src/pijul_glue/branches.rs b/src/pijul_glue/branches.rs new file mode 100644 index 0000000..52d5158 --- /dev/null +++ b/src/pijul_glue/branches.rs @@ -0,0 +1,18 @@ +use libpijul; +use libpijul::fs_representation::*; +use errors::*; + + +pub fn get_branches(path: &str) -> Result> { + let mut vec = Vec::new(); + + let repo = libpijul::Repository::open(pristine_dir(path), None).unwrap(); + let txn = repo.txn_begin().unwrap(); + + let branches = txn.iter_branches(None).map(|x| String::from(x.name.as_str())); + + vec.extend(branches); + return Ok(vec); +} + + diff --git a/src/pijul_glue/mod.rs b/src/pijul_glue/mod.rs new file mode 100644 index 0000000..a5e43ea --- /dev/null +++ b/src/pijul_glue/mod.rs @@ -0,0 +1,4 @@ +//! The pijul glue subsystem. + +pub mod branches; +pub mod patches; diff --git a/src/pijul_glue/patches.rs b/src/pijul_glue/patches.rs new file mode 100644 index 0000000..634a028 --- /dev/null +++ b/src/pijul_glue/patches.rs @@ -0,0 +1,79 @@ +use libpijul; +use libpijul::patch; +use libpijul::fs_representation::*; +use std::path::Path; +use errors::*; +use base64; + + +pub struct Patch { + pub name: String, + pub authors: String, + pub created: String, + pub signed: bool, + hash: libpijul::Hash, +} + + +impl Patch { + pub fn from_patch(patch: patch::Patch, hash: libpijul::Hash) -> Self { + let p = Patch { + name: patch.header().name.clone(), + authors: patch.header().authors.join(", "), + created: format!("{}", patch.header().timestamp.format("%F %T")), + signed: patch_is_signed(patch), + hash: hash, + }; + + return p; + } + + pub fn hash_to_str(&self) -> String { + let config = base64::Config::new(base64::CharacterSet::Standard, + false, + false, + base64::LineWrap::NoWrap); + let s = self.hash.to_base64(config); + + return s; + } +} + + +pub fn str_to_hash(s: String) -> Option { + return libpijul::Hash::from_base64(s.as_ref()); +} + + +pub fn get_patch_headers(path: &str, branch: &str) -> Result> { + let mut vec = Vec::new(); + + let repo = libpijul::Repository::open(pristine_dir(path), None)?; + let txn = repo.txn_begin().unwrap(); + + let branch = txn.get_branch(branch).unwrap(); + + let patches_ids = txn.iter_patches(&branch, None).map(|x| x.0); + for pid in patches_ids { + let hash_ref = txn.external_hash(pid); + let mp = libpijul::fs_representation::read_patch(Path::new(path), + hash_ref); + match mp { + Ok(p) => { + vec.push(Patch::from_patch(p, hash_ref.to_owned())); + }, + Err(e) => warn!("Could not get patch for patch_id {:?}\nError: {:?}", pid, e), + } + } + + return Ok(vec); +} + + +pub fn patch_is_signed(patch: patch::Patch) -> bool { + match patch { + patch::Patch::Unsigned(_) => return false, + patch::Patch::Signed{patch: _, signature: _ } => return true, + } +} + diff --git a/src/ui/entry.rs b/src/ui/entry.rs index 2903ea5..2f79bcc 100644 --- a/src/ui/entry.rs +++ b/src/ui/entry.rs @@ -1,49 +1,15 @@ -use gtk; -use gtk::prelude::*; -use std::rc::Rc; +use appstate::*; use gtk::WidgetExt; -use libpijul; -use libpijul::fs_representation::*; -use std::path::Path; -use errors::*; +use gtk::prelude::*; +use gtk; +use pijul_glue::branches::*; +use pijul_glue::patches::*; +use std::rc::Rc; -pub struct AppS { - _cant_construct: (), - pub gui: Gui, -} +const HASH_COLUMN: i32 = 3; -impl AppS { - pub fn new() -> Self { - let builder = gtk::Builder::new_from_string(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/data/ui/window.glade"))); - - return AppS { - _cant_construct: (), - gui: Gui::new(builder), - } - } -} - - -pub struct Gui { - _cant_construct: (), - pub window: gtk::Window, - pub branch_select: gtk::ComboBoxText, - pub patch_tree: gtk::TreeView, -} - -impl Gui { - pub fn new(builder: gtk::Builder) -> Self { - return Gui { - _cant_construct: (), - window: builder.get_object("main").unwrap(), - branch_select: builder.get_object("branch_select").unwrap(), - patch_tree: builder.get_object("patch_tree").unwrap(), - } - } -} - pub fn init(appstate: Rc) { { @@ -56,61 +22,74 @@ pub fn init(appstate: Rc) { } { - let ls = gtk::ListStore::new(&[gtk::Type::String]); - let patches = get_patches("/home/hasufell/git/pijul", + let mut columns: Vec = Vec::new(); + append_column("Patch", &mut columns, &appstate.gui.patch_tree, Some(200)); + append_column("Authors", &mut columns, &appstate.gui.patch_tree, None); + append_column("Date", &mut columns, &appstate.gui.patch_tree, None); + + let ls = gtk::ListStore::new(&[ + // visible + gtk::Type::String, // name + gtk::Type::String, // authors + gtk::Type::String, // creation date + // not visible + gtk::Type::String, // hash + ]); + let patches = get_patch_headers("/home/hasufell/git/pijul", "master"); for patch in patches.unwrap() { - ls.insert_with_values(None, &[0], &[&patch.as_str()]); + ls.insert_with_values(None, &[0, 1, 2, 3], + &[&patch.name.as_str(), + &patch.authors.as_str(), + &patch.created.as_str(), + &patch.hash_to_str(), + ]); } - - let renderer = gtk::CellRendererText::new(); - let col = gtk::TreeViewColumn::new(); - col.set_title("Patch"); - col.set_resizable(true); - col.pack_start(&renderer, true); - col.add_attribute(&renderer, "text", 0); - col.set_clickable(true); - col.set_sort_column_id(0); - - appstate.gui.patch_tree.append_column(&col); appstate.gui.patch_tree.set_model(Some(&ls)); + appstate.gui.patch_tree.set_headers_visible(true); + } + + let selection = appstate.gui.patch_tree.get_selection(); + selection.set_mode(gtk::SelectionMode::Single); + + /* connect selection change */ + { + let apps = appstate.clone(); + selection.connect_changed(move |ts| { + if let Some((model, iter)) = ts.get_selected() { + let val = model.get_value(&iter, HASH_COLUMN); + let s: String = val.downcast().unwrap().get().unwrap(); + apps.gui.hash_entry.set_text(s.as_ref()); + } + }); } appstate.gui.window.show_all(); } +fn append_column(title: &str, + v: &mut Vec, + tree: >k::TreeView, + max_width: Option) { -fn get_branches(path: &str) -> Result> { - let mut vec = Vec::new(); + let id = v.len() as i32; - let repo = libpijul::Repository::open(pristine_dir(path), None).unwrap(); - let txn = repo.txn_begin().unwrap(); - - let branches = txn.iter_branches(None).map(|x| String::from(x.name.as_str())); - - vec.extend(branches); - return Ok(vec); -} - - -fn get_patches(path: &str, branch: &str) -> Result> { - let mut vec = Vec::new(); - - let repo = libpijul::Repository::open(pristine_dir(path), None)?; - let txn = repo.txn_begin().unwrap(); - - let branch = txn.get_branch(branch).unwrap(); - - let patches_ids = txn.iter_patches(&branch, None).map(|x| x.0); - for pid in patches_ids { - let mp = libpijul::fs_representation::read_patch(Path::new(path), - txn.external_hash(pid)); - match mp { - Ok(p) => vec.push(p.header().name.clone()), - Err(e) => warn!("Could not get patch for patch_id {:?}\nError: {:?}", pid, e), - } + let renderer = gtk::CellRendererText::new(); + let col = gtk::TreeViewColumn::new(); + col.set_title(title); + col.set_resizable(true); + col.set_sort_column_id(id); + if let Some(max_width) = max_width { + col.set_max_width(max_width); + col.set_expand(true); } + col.pack_start(&renderer, true); + col.add_attribute(&renderer, "text", id); + col.set_clickable(true); - return Ok(vec); + tree.append_column(&col); + + v.push(col); } + diff --git a/src/ui/gui.rs b/src/ui/gui.rs new file mode 100644 index 0000000..3c17f7e --- /dev/null +++ b/src/ui/gui.rs @@ -0,0 +1,22 @@ +use gtk; + + +pub struct Gui { + _cant_construct: (), + pub window: gtk::Window, + pub branch_select: gtk::ComboBoxText, + pub patch_tree: gtk::TreeView, + pub hash_entry: gtk::Entry, +} + +impl Gui { + pub fn new(builder: gtk::Builder) -> Self { + return Gui { + _cant_construct: (), + window: builder.get_object("main").unwrap(), + branch_select: builder.get_object("branch_select").unwrap(), + patch_tree: builder.get_object("patch_tree").unwrap(), + hash_entry: builder.get_object("hash_entry").unwrap(), + } + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index e3dbce6..a1e29ea 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,3 +1,4 @@ //! The UI subsystem. pub mod entry; +pub mod gui;