Commit 3fb2bb45 authored by Felix Häcker's avatar Felix Häcker

Use JSON instead of sqlite for local library, store complete library data on disk

parent 973c4999
Pipeline #66911 passed with stages
in 10 minutes and 29 seconds
......@@ -194,7 +194,7 @@ dependencies = [
"crossbeam-deque 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
......@@ -225,7 +225,7 @@ dependencies = [
"arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
......@@ -236,7 +236,7 @@ version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
......@@ -429,7 +429,7 @@ dependencies = [
"glib 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)",
]
......@@ -452,7 +452,7 @@ dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)",
]
......@@ -486,7 +486,7 @@ dependencies = [
"glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gstreamer-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)",
"muldiv 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
......@@ -590,7 +590,7 @@ dependencies = [
"glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gtk-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)",
"pango 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pango-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
......@@ -731,7 +731,7 @@ dependencies = [
[[package]]
name = "lazy_static"
version = "1.2.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
......@@ -767,7 +767,7 @@ dependencies = [
"gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gtk 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gtk-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)",
"libhandy-sys 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
......@@ -913,7 +913,7 @@ name = "native-tls"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl 0.10.18 (registry+https://github.com/rust-lang/crates.io-index)",
......@@ -1031,7 +1031,7 @@ dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.9.41 (registry+https://github.com/rust-lang/crates.io-index)",
]
......@@ -1069,7 +1069,7 @@ dependencies = [
"glib 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)",
"pango-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
......@@ -1418,7 +1418,7 @@ name = "schannel"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
......@@ -1502,6 +1502,7 @@ dependencies = [
"gstreamer 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gstreamer-pbutils 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gtk 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libhandy 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mdns 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
......@@ -1591,7 +1592,7 @@ name = "thread_local"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
......@@ -1700,7 +1701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
......@@ -1987,7 +1988,7 @@ dependencies = [
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
"checksum libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)" = "413f3dfc802c5dc91dc570b05125b6cda9855edfaa9825c9849807876376e70e"
"checksum libdbus-sys 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "18cb88963258d00f4962205dbb5933d82780d9962c8c8a064b651d2ad7189210"
......
......@@ -26,3 +26,4 @@ chrono = "0.4.6"
rust_cast = "0.14.0"
mdns = "0.3.1"
serde_json = "1.0.39"
lazy_static = "1.3.0"
......@@ -124,7 +124,7 @@
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="action_name">app.export-library</property>
<property name="text" translatable="yes">Export</property>
<property name="text" translatable="yes">Export stations</property>
</object>
<packing>
<property name="expand">False</property>
......@@ -138,7 +138,7 @@
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="action_name">app.import-library</property>
<property name="text" translatable="yes">Import</property>
<property name="text" translatable="yes">Import stations</property>
</object>
<packing>
<property name="expand">False</property>
......
......@@ -18,13 +18,6 @@
<property name="title" translatable="yes" context="shortcut window">Quit the application</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">True</property>
<property name="accelerator">&lt;primary&gt;s</property>
<property name="title" translatable="yes" context="shortcut window">Save library</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">True</property>
......
......@@ -23,7 +23,6 @@ pub enum Action {
PlaybackSetStation(Station),
PlaybackStart,
PlaybackStop,
LibraryWrite,
LibraryImport,
LibraryExport,
LibraryAddStations(Vec<Station>),
......@@ -98,7 +97,6 @@ impl App {
self.gtk_app.run(&[]);
self.player.shutdown();
self.library.write_data();
}
fn setup_gaction(&self) {
......@@ -113,13 +111,6 @@ impl App {
Self::show_about_dialog(window.clone());
});
// Save library
let sender = self.sender.clone();
self.add_gaction("save", move |_, _| {
sender.send(Action::LibraryWrite).unwrap();
});
self.gtk_app.set_accels_for_action("app.save", &["<primary>s"]);
// Search / add stations
let sender = self.sender.clone();
self.add_gaction("search", move |_, _| {
......@@ -213,9 +204,8 @@ impl App {
}
Action::PlaybackStart => self.player.set_playback(PlaybackState::Playing),
Action::PlaybackStop => self.player.set_playback(PlaybackState::Stopped),
Action::LibraryWrite => self.library.write_data(),
Action::LibraryImport => self.import_library(),
Action::LibraryExport => self.export_library(),
Action::LibraryImport => self.import_stations(),
Action::LibraryExport => self.export_stations(),
Action::LibraryAddStations(stations) => self.library.add_stations(stations),
Action::LibraryRemoveStations(stations) => self.library.remove_stations(stations),
Action::SearchFor(data) => self.search.search_for(data),
......@@ -241,31 +231,50 @@ impl App {
dialog.show();
}
fn import_library(&self) {
fn import_stations(&self) {
let import_dialog = gtk::FileChooserNative::new("Select database to import", &self.window.widget, gtk::FileChooserAction::Open, "Import", "Cancel");
let filter = gtk::FileFilter::new();
import_dialog.set_filter(&filter);
filter.add_mime_type("application/x-sqlite3");
filter.add_mime_type("application/json"); // Shortwave library format
filter.add_mime_type("application/x-sqlite3"); // Old Gradio library format
filter.add_mime_type("application/vnd.sqlite3"); // Old Gradio library format
if gtk::ResponseType::from(import_dialog.run()) == gtk::ResponseType::Accept {
let path = import_dialog.get_file().unwrap().get_path().unwrap();
debug!("Import path: {:?}", path);
match self.library.import_from_path(&path) {
Ok(_) => self.sender.send(Action::ViewShowNotification("Successfully imported library".to_string())).unwrap(),
Err(err) => self.sender.send(Action::ViewShowNotification(format!("Could not import library - {}", err.to_string()))).unwrap(),
match Library::read(path) {
Ok(stations) => {
let message = format!("Successfully imported {} stations.", stations.len());
self.sender.send(Action::ViewShowNotification(message)).unwrap();
self.sender.send(Action::LibraryAddStations(stations));
}
Err(error) => {
let message = format!("Could not import stations: {}", error.to_string());
self.sender.send(Action::ViewShowNotification(message)).unwrap();
}
};
}
import_dialog.destroy();
}
fn export_library(&self) {
fn export_stations(&self) {
let export_dialog = gtk::FileChooserNative::new("Export database", &self.window.widget, gtk::FileChooserAction::Save, "Export", "Cancel");
export_dialog.set_current_name("library.json");
if gtk::ResponseType::from(export_dialog.run()) == gtk::ResponseType::Accept {
let path = export_dialog.get_file().unwrap().get_path().unwrap();
debug!("Export path: {:?}", path);
match self.library.export_to_path(&path) {
Ok(_) => self.sender.send(Action::ViewShowNotification("Successfully exported library".to_string())).unwrap(),
Err(err) => self.sender.send(Action::ViewShowNotification(format!("Could not export library - {}", err.to_string()))).unwrap(),
let stations = self.library.to_vec();
let count = stations.len();
match Library::write(stations, path) {
Ok(()) => {
let message = format!("Successfully exported {} stations.", count);
self.sender.send(Action::ViewShowNotification(message)).unwrap();
}
Err(error) => {
let message = format!("Could not export stations: {}", error.to_string());
self.sender.send(Action::ViewShowNotification(message)).unwrap();
}
};
}
export_dialog.destroy();
......
use gio::prelude::*;
use glib::Sender;
use gtk::prelude::*;
use rusqlite::Connection;
......@@ -15,19 +16,23 @@ use crate::app::Action;
use crate::config;
use crate::station_model::StationModel;
use crate::station_model::{Order, Sorting};
use crate::station_object::StationObject;
use crate::widgets::station_listbox::StationListBox;
use crate::widgets::station_row::ContentType;
static SQL_READ: &str = "SELECT station_id, collection_name, library.collection_id
FROM library LEFT JOIN collections ON library.collection_id = collections.collection_id ORDER BY library.collection_id ASC;";
static SQL_INIT_LIBRARY: &str = "CREATE TABLE \"library\" ('station_id' INTEGER, 'collection_id' INTEGER);";
static SQL_INIT_COLLECTIONS: &str = " CREATE TABLE \"collections\" ('collection_id' INTEGER, 'collection_name' TEXT)";
lazy_static! {
static ref LIBRARY_PATH: PathBuf = {
let mut path = glib::get_user_data_dir().unwrap();
path.push(config::NAME);
path.push("library.json");
path
};
}
pub struct Library {
pub widget: gtk::Box,
library_model: RefCell<StationModel>,
db_path: PathBuf,
builder: gtk::Builder,
sender: Sender<Action>,
}
......@@ -43,8 +48,6 @@ impl Library {
station_listbox.bind_model(&library_model.borrow());
content_box.add(&station_listbox.widget);
let db_path = Self::get_database_path("shortwave.db").expect("Could not open database path...");
let logo_image: gtk::Image = builder.get_object("logo_image").unwrap();
logo_image.set_from_icon_name(Some(format!("{}-symbolic", config::APP_ID).as_str()), gtk::IconSize::__Unknown(128));
let welcome_text: gtk::Label = builder.get_object("welcome_text").unwrap();
......@@ -53,15 +56,21 @@ impl Library {
let library = Self {
widget,
library_model,
db_path,
builder,
sender,
};
// read database and import data
library.import_from_path(&library.db_path).expect("Could not import stations from database");
library.setup_signals();
// Read stations
match Self::read(LIBRARY_PATH.to_path_buf()) {
Ok(stations) => library.add_stations(stations),
Err(error) => {
let message = format!("Could not read library data: {}", error.to_string());
library.sender.send(Action::ViewShowNotification(message)).unwrap();
}
};
library
}
......@@ -70,143 +79,100 @@ impl Library {
for station in stations {
self.library_model.borrow_mut().add_station(station.clone());
}
self.update_visible_page();
}
pub fn remove_stations(&self, stations: Vec<Station>) {
debug!("Remove {} station(s)", stations.len());
for station in stations {
self.library_model.borrow_mut().remove_station(station.clone());
self.library_model.borrow_mut().remove_station(&station);
}
self.update_visible_page();
}
pub fn write_data(&self) {
debug!("Write library data to disk...");
Self::write_stations_to_db(&self.db_path, self.library_model.borrow().clone()).expect("Could not write stations to database.");
}
pub fn import_from_path(&self, path: &PathBuf) -> Result<(), LibraryError> {
// test sql connection
let connection = Connection::open(path.clone())?;
let mut _stmt = connection.prepare(SQL_READ)?;
let sender = self.sender.clone();
let p = path.clone();
self.set_visible_page("loading");
thread::spawn(move || {
match Self::read_stations_from_db(&p) {
Ok(stations) => sender.send(Action::LibraryAddStations(stations)).unwrap(),
Err(err) => {
sender.send(Action::LibraryAddStations(Vec::new())).unwrap();
sender.send(Action::ViewShowNotification(format!("Could not load stations - {}", err.to_string()).to_string())).unwrap();
}
};
});
Ok(())
}
pub fn export_to_path(&self, path: &PathBuf) -> Result<(), LibraryError> {
Self::write_stations_to_db(&path, self.library_model.borrow().clone()).expect("Could not export database.");
Ok(())
}
pub fn set_sorting(&self, sorting: Sorting, order: Order) {
self.library_model.borrow_mut().set_sorting(sorting, order);
}
fn read_stations_from_db(path: &PathBuf) -> Result<Vec<Station>, LibraryError> {
debug!("Read stations from \"{:?}\"", path);
let mut result = Vec::new();
let mut client = Client::new("http://www.radio-browser.info");
let connection = Connection::open(path.clone())?;
let mut stmt = connection.prepare(SQL_READ)?;
let mut rows = stmt.query(&[])?;
while let Some(result_row) = rows.next() {
let row = result_row.unwrap();
let station_id: u32 = row.get(0);
match client.get_station_by_id(station_id)? {
Some(station) => {
info!("Found Station: {}", station.name);
result.insert(0, station);
}
None => warn!("Could not fetch station with ID {}", station_id),
}
}
Ok(result)
pub fn to_vec(&self) -> Vec<Station> {
Self::model_to_vec(&self.library_model.borrow().model)
}
fn write_stations_to_db(path: &PathBuf, stations: StationModel) -> Result<(), LibraryError> {
let tmpdb = Self::get_database_path("tmp.db")?;
info!("Delete previous database data...");
let _ = fs::remove_file(path);
let _ = fs::remove_file(&tmpdb);
let _ = Self::create_database(&tmpdb);
info!("Write stations to \"{:?}\"", tmpdb);
let connection = Connection::open(tmpdb.clone())?;
for station in stations {
let mut stmt = connection.prepare(&format!("INSERT INTO library VALUES ('{}', '0');", station.id.to_string(),))?;
stmt.execute(&[])?;
fn model_to_vec(model: &gio::ListStore) -> Vec<Station> {
let mut stations = Vec::new();
for i in 0..model.get_n_items() {
let gobject = model.get_object(i).unwrap();
let station_object = gobject.downcast_ref::<StationObject>().expect("StationObject is of wrong type");
stations.insert(0, station_object.to_station());
}
debug!("Move tmp.db to real path...");
let _ = fs::copy(&tmpdb, &path);
Ok(())
stations
}
fn get_database_path(name: &str) -> Result<PathBuf, LibraryError> {
let mut path = glib::get_user_data_dir().unwrap();
if !path.exists() {
fs::create_dir(&path.to_str().unwrap())?;
}
path.push("shortwave");
if !path.exists() {
fs::create_dir(&path.to_str().unwrap())?;
}
fn setup_signals(&self) {
let sender = self.sender.clone();
self.library_model.borrow().model.connect_items_changed(move |model, pos, removed, added| {
// Check if data got changed
if removed == 1 || added == 1 {
// Convert gio::ListStore into Vec<Station>
let stations = Self::model_to_vec(model);
// Write new data to disk
match Self::write(stations, LIBRARY_PATH.to_path_buf()) {
Ok(()) => (),
Err(error) => {
let message = format!("Could not write library data: {}", error.to_string());
sender.send(Action::ViewShowNotification(message)).unwrap();
}
};
}
});
}
path.push(name);
if !path.exists() {
Self::create_database(&path)?;
}
pub fn write(stations: Vec<Station>, path: PathBuf) -> Result<(), LibraryError> {
debug!("Write library data to: {:?}", path);
Ok(path)
}
// Convert Vec<Station> into text
let data = serde_json::to_string(&stations)?;
fn create_database(path: &PathBuf) -> Result<(), LibraryError> {
info!("Create new database...");
File::create(&path.to_str().unwrap())?;
// Create missing folders, if necessary
let mut fpath = path.clone();
fpath.pop();
fs::create_dir_all(fpath)?;
info!("Initialize database...");
let connection = Connection::open(path.clone())?;
let mut stmt = connection.prepare(SQL_INIT_LIBRARY).expect("Could not initialize sqlite database");
stmt.execute(&[])?;
let mut stmt = connection.prepare(SQL_INIT_COLLECTIONS).expect("Could not initialize sqlite database");
stmt.execute(&[])?;
// Write the actual data
fs::write(path, data)?;
Ok(())
}
fn update_visible_page(&self) {
if self.library_model.borrow().len() != 0 {
self.set_visible_page("content");
pub fn read(path: PathBuf) -> Result<Vec<Station>, LibraryError> {
debug!("Read library data from: {:?}", path);
if path.extension().is_some() && path.extension().unwrap().to_str() == Some("json") {
// New Shortwave library format (.json)
let data = fs::read_to_string(path)?;
let stations: Vec<Station> = serde_json::from_str(&data)?;
Ok(stations)
} else {
self.set_visible_page("empty");
// Old Gradio library format (.db)
let mut result = Vec::new();
let mut client = Client::new("http://www.radio-browser.info");
let connection = Connection::open(path.clone())?;
let mut stmt = connection.prepare("SELECT station_id FROM library;")?;
let mut rows = stmt.query(&[])?;
while let Some(result_row) = rows.next() {
let row = result_row.unwrap();
let station_id: u32 = row.get(0);
match client.get_station_by_id(station_id)? {
Some(station) => {
info!("Found Station: {}", station.name);
result.insert(0, station);
}
None => warn!("Could not fetch station with ID {}", station_id),
}
}
Ok(result)
}
}
fn set_visible_page(&self, name: &str) {
let stack: gtk::Stack = self.builder.get_object("library_stack").unwrap();
stack.set_visible_child_name(name);
}
fn setup_signals(&self) {}
}
quick_error! {
......@@ -221,7 +187,7 @@ quick_error! {
Sqlite(err: rusqlite::Error) {
from()
description("sqlite error")
display("Database error: {}", err)
display("Gradio database error: {}", err)
cause(err)
}
Restson(err: restson::Error) {
......@@ -230,5 +196,11 @@ quick_error! {
display("Network error: {}", err)
cause(err)
}
Serde(err: serde_json::error::Error) {
from()
description("serde error")
display("Parser error: {}", err)
cause(err)
}
}
}
......@@ -8,6 +8,9 @@ extern crate quick_error;
#[macro_use]
extern crate glib;
#[macro_use]
extern crate lazy_static;
mod app;
mod config;
mod library;
......
......@@ -44,24 +44,29 @@ impl StationModel {
}
pub fn add_station(&mut self, station: Station) {
let object = StationObject::new(station.clone());
if !self.index(&station).is_some() {
let object = StationObject::new(station.clone());
let sorting = self.sorting.clone();
let order = self.order.clone();
self.model.insert_sorted(&object, move |a, b| Self::station_cmp(a, b, sorting.clone(), order.clone()));
}
}
let sorting = self.sorting.clone();
let order = self.order.clone();
self.model.insert_sorted(&object, move |a, b| Self::station_cmp(a, b, sorting.clone(), order.clone()));
pub fn remove_station(&mut self, station: &Station) {
self.index(station).map(|index| self.model.remove(index));
}
pub fn remove_station(&mut self, station: Station) {
fn index(&self, station: &Station) -> Option<u32> {
for i in 0..self.model.get_n_items() {
let gobject = self.model.get_object(i).unwrap();
let station_object = gobject.downcast_ref::<StationObject>().expect("StationObject is of wrong type");
let s = station_object.to_station();
if s == station {
self.model.remove(i);
break;
if &s == station {
return Some(i);
}
}
None
}
pub fn set_sorting(&mut self, sorting: Sorting, order: Order) {
......@@ -100,21 +105,3 @@ impl StationModel {
}
}
}
impl Iterator for StationModel {
type Item = Station;
fn next(&mut self) -> Option<Station> {
let max = self.len();
let mut result = None;
if self.iter_id < max {
let gobject = self.model.get_object(self.iter_id).unwrap();
let station_object = gobject.downcast_ref::<StationObject>().expect("StationObject is of wrong type");
result = Some(station_object.to_station());
self.iter_id = self.iter_id + 1;
}
result
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment