Merge branch 'search_provider' into 'master'

WIP: Implement Shell Search Provider

See merge request World/podcasts!100
This commit is contained in:
Felix Häcker 2019-10-09 19:14:11 +00:00
commit 1cfc80b8f5
14 changed files with 355 additions and 217 deletions

393
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,7 @@
"--socket=pulseaudio",
"--device=dri",
"--own-name=org.mpris.MediaPlayer2.Podcasts",
"--own-name=org.gnome.Podcasts.Devel.SearchProvider",
"--env=USE_PLAYBING3=1"
],
"build-options" : {

View File

@ -23,6 +23,7 @@
"--socket=pulseaudio",
"--device=dri",
"--own-name=org.mpris.MediaPlayer2.Podcasts",
"--own-name=org.gnome.Podcasts.SearchProvider",
"--env=USE_PLAYBING3=1"
],
"build-options" : {

View File

@ -87,6 +87,25 @@ pub(crate) fn get_downloaded_episodes() -> Result<Vec<EpisodeCleanerModel>, Data
.map_err(From::from)
}
// Only searches from downloaded episodes
pub fn search_downloaded_episodes(terms: Vec<&str>) -> Result<Vec<Episode>, DataError> {
use crate::schema::episodes::dsl::*;
let db = connection();
let con = db.get()?;
let mut search_term = "".to_string();
for term in terms {
search_term.push_str(&format!("%{}%", term));
}
episodes
.filter(local_uri.is_not_null())
.filter(title.like(&search_term))
.order(epoch.desc())
.load::<Episode>(&con)
.map_err(From::from)
}
// pub(crate) fn get_played_episodes() -> Result<Vec<Episode>, DataError> {
// use schema::episodes::dsl::*;

View File

@ -28,6 +28,7 @@ reqwest = "0.9.12"
serde_json = "1.0.39"
# html2text = "0.1.8"
html2text = { git = "https://github.com/jugglerchris/rust-html2text" }
search-provider = { git = "https://gitlab.gnome.org/World/Rust/search-provider" }
[dependencies.gettext-rs]
git = "https://github.com/danigm/gettext-rs"

View File

@ -65,4 +65,23 @@ podcasts_resources = gnome.compile_resources(
source_dir: meson.current_build_dir()
)
# Search Provider
service_conf = configuration_data()
service_conf.set('appid', application_id)
service_conf.set('bindir', podcasts_bindir)
configure_file(
input: 'org.gnome.Podcasts.SearchProvider.service.in',
output: '@0@.SearchProvider.service'.format(application_id),
configuration: service_conf,
install_dir: join_paths(datadir,'dbus-1', 'services')
)
search_conf = configuration_data()
search_conf.set('appid', application_id)
configure_file(
input: 'org.gnome.Podcasts.search-provider.ini',
output: '@0@.search-provider.ini'.format(application_id),
configuration: search_conf,
install_dir: join_paths(datadir, 'gnome-shell', 'search-providers'),
)
meson.add_install_script('../../scripts/compile-gschema.py')

View File

@ -0,0 +1,3 @@
[D-BUS Service]
Name=@appid@.SearchProvider
Exec=@bindir@/gnome-podcasts --gapplication-service

View File

@ -66,6 +66,7 @@
<kudos>
<kudo>ModernToolkit</kudo>
<kudo>HiDpiIcon</kudo>
<kudo>SearchProvider</kudo>
</kudos>
<launchable type="desktop-id">@appid@.desktop</launchable>
<url type="homepage">https://wiki.gnome.org/Apps/Podcasts</url>

View File

@ -0,0 +1,5 @@
[Shell Search Provider]
DesktopId=@appid@.desktop
BusName=@appid@.SearchProvider
ObjectPath=/org/gnome/Podcasts/SearchProvider
Version=2

0
podcasts-gtk/resources/org.gnome.Podcasts.service.in Normal file → Executable file
View File

View File

@ -87,6 +87,7 @@ mod headerbar;
mod window;
mod manager;
mod search_provider;
mod settings;
mod static_resource;
mod utils;

View File

@ -53,6 +53,7 @@ podcasts_sources = files(
'i18n.rs',
'main.rs',
'manager.rs',
'search_provider.rs',
'settings.rs',
'utils.rs'
)

View File

@ -0,0 +1,123 @@
// search_provider.rs
//
// Copyright 2019 Felix Häcker <haeckerfelix@gnome.org>
//
// 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 3 of the License, or
// (at your option) any later version.
//
// 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 <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-or-later
use crossbeam_channel::Sender;
use gtk::prelude::*;
use podcasts_data::dbqueries;
use search_provider::{ResultMetadata, SearchProvider as SP};
use std::str::FromStr;
use std::sync::Arc;
use crate::app::Action;
use crate::config::APP_ID;
#[derive(Debug, Clone)]
pub(crate) struct SearchProvider {
search_provider: Arc<SP>,
window: gtk::ApplicationWindow,
sender: Sender<Action>,
}
impl SearchProvider {
pub(crate) fn new(window: gtk::ApplicationWindow, sender: Sender<Action>) -> SearchProvider {
let search_provider = SP::new(
APP_ID.to_string(),
"/org/gnome/Podcasts/SearchProvider".to_string(),
);
let sp = SearchProvider {
search_provider,
window,
sender,
};
sp.setup_callbacks();
sp
}
fn setup_callbacks(&self) {
let window = self.window.clone();
let sender = self.sender.clone();
self.search_provider
.connect_activate_result(move |sp_id, _, timestamp| {
// Show window
window.present_with_time(timestamp);
window.show_all();
window.present();
// Get episode
let mut id = sp_id.clone().split("_").collect::<Vec<&str>>();
let title: &str = id.pop().unwrap();
let show_id: i32 = std::str::FromStr::from_str(id.pop().unwrap()).unwrap();
let episode = dbqueries::get_episode_from_pk(title, show_id).unwrap();
// Play episode
sender
.send(Action::InitEpisode(episode.rowid()))
.expect("Action channel blew up somehow");
});
self.search_provider
.connect_get_initial_result_set(|terms| Self::search(terms));
self.search_provider
.connect_get_subsearch_result_set(|_, terms| Self::search(terms));
self.search_provider.connect_get_result_metas(|sp_ids| {
let mut metas = Vec::new();
for sp_id in sp_ids {
let id = sp_id.clone().split("_").collect::<Vec<&str>>();
let show_id: i32 = FromStr::from_str(id[0]).unwrap();
let episode = dbqueries::get_episode_from_pk(id[1], show_id).unwrap();
let image_uri = &dbqueries::get_podcast_cover_from_id(show_id)
.map(|cover| {
cover
.image_uri()
.unwrap_or("image-x-generic-symbolic")
.to_owned()
})
.unwrap_or(String::from("image-x-generic-symbolic"));
let meta = ResultMetadata::new(
sp_id,
episode.title(),
image_uri,
episode.description().unwrap_or(""),
);
metas.insert(0, meta);
}
metas
});
}
fn search(terms: Vec<&str>) -> Vec<String> {
let episodes = dbqueries::search_downloaded_episodes(terms)
.expect("Could not search for episodes (search provider)");
let mut sp_ids = Vec::new();
for episode in episodes {
let sp_id = format!("{}_{}", episode.show_id(), episode.title());
sp_ids.insert(0, sp_id);
}
sp_ids
}
}

View File

@ -29,6 +29,7 @@ use crossbeam_channel::{unbounded, Receiver, Sender};
use crate::app::{Action, PdApplication};
use crate::headerbar::Header;
use crate::search_provider::SearchProvider;
use crate::settings::{self, WindowGeometry};
use crate::stacks::Content;
use crate::utils;
@ -149,6 +150,9 @@ impl MainWindow {
glib::Continue(true)
});
// Setup GNOME Shell search provider integration
let search_provider = SearchProvider::new(window.clone(), sender.clone());
Self {
app: app.clone(),
window,