Compare commits

...

23 Commits

Author SHA1 Message Date
f2ac198831
Merge branch 'master' of gitlab.gnome.org:World/podcasts 2020-06-21 21:15:03 +01:00
Jordan Petridis
abd263f127 Merge branch 'wip/exalm/tnum' into 'master'
Tabular figures; model buttons for speed rate popover

See merge request World/podcasts!134
2020-06-21 18:27:08 +00:00
Alexander Mikhaylenko
9c2d4ac6a7
episode: Use tabular figures
Prevents jumping during downloads.
2020-06-21 21:17:00 +03:00
Alexander Mikhaylenko
4d950ac17e
player: Use tabular figures for duration 2020-06-21 21:17:00 +03:00
Alexander Mikhaylenko
5e688c104c
player_rate: Use tabular figures for speed rate menu
Since gtk::ModelButton allows to set markup, the menu entries can now use
tabular figures.

Make the labels untranslatable, as they are generic enough already, and
being translatable would make even less sense with markup.
2020-06-21 21:17:00 +03:00
Alexander Mikhaylenko
4ce0819c68
player: Use model buttons for rate popover
Also simplify the xml.
2020-06-21 21:16:58 +03:00
Jordan Petridis
f7d9d0555c
meson: keep track of changes in Cargo.toml and lockfile 2020-06-21 18:55:25 +03:00
Jordan Petridis
b754182e0d
cargo fmt 2020-06-21 18:38:29 +03:00
Jordan Petridis
7b3a607b5e
improve test.sh further
Before we were able to call the script with ninja directly,
we were trying to override some of the envvars to fix how/where
cargo artifacts where stored.

This is no longer an issue as ninja is making sure the proper
setup will be met. This is also makes it so that Builder
doesn't rebuild everything since $BUILDDIR was empty before
causing rebuilds of the whole world.
2020-06-21 18:30:48 +03:00
Jordan Petridis
0e47e9c07f
test.sh: run the build proccess multithreaded 2020-06-21 18:21:32 +03:00
Jordan Petridis
c140e5a163
downloader: remove unused dependency on error-chain 2020-06-21 18:12:23 +03:00
Jordan Petridis
0bc379bf42 Merge branch 'alatiera/spelling' into 'master'
Fix spelling of things

See merge request World/podcasts!143
2020-06-21 15:11:41 +00:00
Jordan Petridis
9f0a3a0d9f
Fix spelling of things 2020-06-21 18:10:53 +03:00
Jordan Petridis
355cf9a36c Merge branch 'alatiera/rever-futures-upgrade' into 'master'
Revert 096197cf81

See merge request World/podcasts!144
2020-06-21 15:06:57 +00:00
Jordan Petridis
d9792e99c1
Revert 096197cf81
It breaks the testsuite 'sometime' which is fairly annoying.
The whole testuite setup is crap though and likely needs to be
fixed first.

Will re-revert the changes once a new stable release of the app
is done.
2020-06-21 17:03:36 +03:00
Jordan Petridis
6cb7de7fb3 Merge branch 'more-clone' into 'master'
Use clone! macro in more cases

See merge request World/podcasts!141
2020-06-21 13:38:19 +00:00
Julian Hofer
cb0860cddf
Use clone! macro in more cases 2020-06-20 18:30:44 +03:00
Thibault Martin
6edeb59b16 Update French translation 2020-06-19 17:35:06 +00:00
Jordan Petridis
a245aa73d4 Merge branch 'window-cleanup' into 'master'
Cleanup windows.rs

See merge request World/podcasts!140
2020-06-19 12:00:10 +00:00
Julian Hofer
08be9bdb4e Fix broken window.connect_delete_event
It was never called with the original configuration
2020-06-19 09:51:35 +00:00
Julian Hofer
2ee2181211 Cleanup windows.rs
It shouldn't result in any functional changes, but some unnecessary
parts were removed and windows.rs is now tracked by meson
2020-06-19 09:51:35 +00:00
Jordan Petridis
59e1c7d6f4
cargo update deps 2020-06-19 12:05:03 +03:00
Jordan Petridis
c2aca6e3a0
Flatpak: use the 3.36 runtime
Nightly has moved into fd.o-sdk 20.08 base and the rust-sdk
extension for that base is in flux and I haven't had time to
deal with it yet.
2020-06-19 11:42:44 +03:00
31 changed files with 1949 additions and 2005 deletions

View File

@ -4,7 +4,7 @@ include:
# ref: ''
flatpak:
image: 'registry.gitlab.gnome.org/gnome/gnome-runtime-images/rust_bundle:master'
image: 'registry.gitlab.gnome.org/gnome/gnome-runtime-images/rust_bundle:3.36'
variables:
MANIFEST_PATH: "org.gnome.Podcasts.Devel.json"
FLATPAK_MODULE: "gnome-podcasts"

2861
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -40,6 +40,14 @@ else
version_suffix = ''
endif
podcast_toml = files(
'Cargo.toml',
'Cargo.lock',
'podcasts-data/Cargo.toml',
'podcasts-downloader/Cargo.toml',
'podcasts-gtk/Cargo.toml',
)
application_id = 'org.gnome.Podcasts@0@'.format(profile)
i18n = import('i18n')
gnome = import('gnome')

View File

@ -1,7 +1,7 @@
{
"app-id" : "org.gnome.Podcasts.Devel",
"runtime" : "org.gnome.Platform",
"runtime-version" : "master",
"runtime-version" : "3.36",
"sdk" : "org.gnome.Sdk",
"sdk-extensions" : [
"org.freedesktop.Sdk.Extension.rust-stable"

View File

@ -1,7 +1,7 @@
{
"app-id" : "org.gnome.Podcasts",
"runtime" : "org.gnome.Platform",
"runtime-version" : "master",
"runtime-version" : "3.36",
"sdk" : "org.gnome.Sdk",
"sdk-extensions" : [
"org.freedesktop.Sdk.Extension.rust-stable"

View File

@ -5,39 +5,36 @@ version = "0.1.0"
edition = "2018"
[dependencies]
ammonia = "3.0.0"
chrono = "0.4.9"
derive_builder = "0.8.0"
ammonia = "3.1.0"
chrono = "0.4.11"
derive_builder = "0.9.0"
lazy_static = "1.4.0"
log = "0.4.8"
rayon = "1.2.0"
rayon = "1.3.1"
rfc822_sanitizer = "0.3.3"
rss = "1.9.0"
url = "2.1.1"
xdg = "2.2.0"
xml-rs = "0.8.0"
futures = "0.3.4"
hyper = "0.13.2"
http = "0.2.0"
hyper-tls = "0.4.1"
xml-rs = "0.8.3"
futures = "0.1.29"
hyper = "0.12.35"
http = "0.1.19"
tokio = "0.1.22"
hyper-tls = "0.3.2"
native-tls = "0.2.3"
num_cpus = "1.10.1"
failure = "0.1.6"
failure_derive = "0.1.6"
base64 = "0.10.1"
num_cpus = "1.13.0"
failure = "0.1.8"
failure_derive = "0.1.8"
base64 = "0.12.2"
[dependencies.diesel]
features = ["sqlite", "r2d2"]
version = "1.4.3"
version = "1.4.5"
[dependencies.diesel_migrations]
features = ["sqlite"]
version = "1.4.0"
[dependencies.tokio]
features = ["rt-core", "rt-threaded", "macros"]
version = "0.2.13"
[dev-dependencies]
rand = "0.7.2"
tempdir = "0.3.7"

View File

@ -84,7 +84,10 @@ pub enum DataError {
FeedRedirect(Source),
#[fail(display = "Feed is up to date")]
FeedNotModified(Source),
#[fail(display = "Error occured while Parsing an Episode. Reason: {}", reason)]
#[fail(
display = "Error occurred while Parsing an Episode. Reason: {}",
reason
)]
ParseEpisodeError { reason: String, parent_id: i32 },
#[fail(display = "Episode was not changed and thus skipped.")]
EpisodeNotChanged,

View File

@ -21,6 +21,7 @@
#![allow(clippy::unit_arg)]
//! Index Feeds.
use futures::future::*;
use futures::prelude::*;
use futures::stream;
use rss;
@ -44,31 +45,31 @@ pub struct Feed {
impl Feed {
/// Index the contents of the RSS `Feed` into the database.
pub async fn index(self) -> Result<(), DataError> {
let show = self.parse_podcast().to_podcast()?;
self.index_channel_items(show).await
pub fn index(self) -> impl Future<Item = (), Error = DataError> + Send {
ok(self.parse_podcast())
.and_then(|pd| pd.to_podcast())
.and_then(move |pd| self.index_channel_items(pd))
}
fn parse_podcast(&self) -> NewShow {
NewShow::new(&self.channel, self.source_id)
}
async fn index_channel_items(self, pd: Show) -> Result<(), DataError> {
let stream = stream::iter(self.channel.into_items());
fn index_channel_items(self, pd: Show) -> impl Future<Item = (), Error = DataError> + Send {
let stream = stream::iter_ok::<_, DataError>(self.channel.into_items());
// Parse the episodes
let episodes = stream.filter_map(move |item| {
let ret = NewEpisodeMinimal::new(&item, pd.id())
.and_then(move |ep| determine_ep_state(ep, &item));
if ret.is_ok() {
future::ready(Some(ret))
} else {
future::ready(None)
}
NewEpisodeMinimal::new(&item, pd.id())
.and_then(move |ep| determine_ep_state(ep, &item))
.map_err(|err| error!("Failed to parse an episode: {}", err))
.ok()
});
// Filter errors, Index updatable episodes, return insertables.
let insertable_episodes = filter_episodes(episodes).await?;
batch_insert_episodes(&insertable_episodes);
Ok(())
filter_episodes(episodes)
// Batch index insertable episodes.
.and_then(|eps| ok(batch_insert_episodes(&eps)))
}
}
@ -93,31 +94,28 @@ fn determine_ep_state(
}
}
async fn filter_episodes<'a, S>(stream: S) -> Result<Vec<NewEpisode>, DataError>
fn filter_episodes<'a, S>(
stream: S,
) -> impl Future<Item = Vec<NewEpisode>, Error = DataError> + Send + 'a
where
S: Stream<Item = Result<IndexState<NewEpisode>, DataError>>,
S: Stream<Item = IndexState<NewEpisode>, Error = DataError> + Send + 'a,
{
stream
.try_filter_map(|state| {
async {
let result = match state {
IndexState::NotChanged => None,
// Update individual rows, and filter them
IndexState::Update((ref ep, rowid)) => {
ep.update(rowid)
.map_err(|err| error!("{}", err))
.map_err(|_| error!("Failed to index episode: {:?}.", ep.title()))
.ok();
None
}
IndexState::Index(s) => Some(s),
};
Ok(result)
.filter_map(|state| match state {
IndexState::NotChanged => None,
// Update individual rows, and filter them
IndexState::Update((ref ep, rowid)) => {
ep.update(rowid)
.map_err(|err| error!("{}", err))
.map_err(|_| error!("Failed to index episode: {:?}.", ep.title()))
.ok();
None
}
IndexState::Index(s) => Some(s),
})
// only Index is left, collect them for batch index
.try_collect()
.await
.collect()
}
fn batch_insert_episodes(episodes: &[NewEpisode]) {
@ -144,9 +142,8 @@ fn batch_insert_episodes(episodes: &[NewEpisode]) {
#[cfg(test)]
mod tests {
use failure::Error;
use futures::executor::block_on;
use rss::Channel;
use tokio;
use tokio::{self, prelude::*};
use crate::database::truncate_db;
use crate::dbqueries;
@ -201,10 +198,9 @@ mod tests {
})
.collect();
// Index the channes
let stream_ = stream::iter(feeds).for_each(|x| x.index().map(|x| x.unwrap()));
let mut rt = tokio::runtime::Runtime::new()?;
rt.block_on(stream_);
// Index the channels
let stream_ = stream::iter_ok(feeds).for_each(|x| x.index());
tokio::run(stream_.map_err(|_| ()));
// Assert the index rows equal the controlled results
assert_eq!(dbqueries::get_sources()?.len(), 5);
@ -236,7 +232,7 @@ mod tests {
let feed = get_feed(path, 42);
let pd = feed.parse_podcast().to_podcast()?;
block_on(feed.index_channel_items(pd))?;
feed.index_channel_items(pd).wait()?;
assert_eq!(dbqueries::get_podcasts()?.len(), 1);
assert_eq!(dbqueries::get_episodes()?.len(), 43);
Ok(())

View File

@ -111,7 +111,7 @@ pub use crate::models::{Episode, EpisodeWidgetModel, Show, ShowCoverModel, Sourc
/// It originates from the Tor-browser UA.
pub const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 6.1; rv:60.0) Gecko/20100101 Firefox/60.0";
/// [XDG Base Direcotory](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) Paths.
/// [XDG Base Directory](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) Paths.
#[allow(missing_debug_implementations)]
pub mod xdg_dirs {
use std::path::PathBuf;
@ -137,7 +137,7 @@ pub mod xdg_dirs {
PODCASTS_XDG.create_cache_directory(PODCASTS_XDG.get_cache_home()).unwrap()
};
/// GNOME Podcasts Download Direcotry `PathBuf`.
/// GNOME Podcasts Download Directory `PathBuf`.
pub static ref DL_DIR: PathBuf = {
PODCASTS_XDG.create_data_directory("Downloads").unwrap()
};

View File

@ -255,7 +255,7 @@ impl NewEpisodeMinimal {
return Err(err);
};
// Default to rfc2822 represantation of epoch 0.
// Default to rfc2822 representation of epoch 0.
let date = parse_rfc822(item.pub_date().unwrap_or("Thu, 1 Jan 1970 00:00:00 +0000"));
// Should treat information from the rss feeds as invalid by default.
// Case: "Thu, 05 Aug 2016 06:00:00 -0400" <-- Actually that was friday.
@ -342,7 +342,7 @@ mod tests {
use std::io::BufReader;
// TODO: Add tests for other feeds too.
// Especially if you find an *intresting* generated feed.
// Especially if you find an *interesting* generated feed.
// Known prebuilt expected objects.
lazy_static! {
@ -578,6 +578,10 @@ mod tests {
let ep = EXPECTED_MINIMAL_INTERCEPTED_1
.clone()
.into_new_episode(&item);
println!(
"EPISODE: {:#?}\nEXPECTED: {:#?}",
ep, *EXPECTED_INTERCEPTED_1
);
assert_eq!(ep, *EXPECTED_INTERCEPTED_1);
let item = channel.items().iter().nth(15).unwrap();

View File

@ -31,6 +31,9 @@ use http::header::{
USER_AGENT as USER_AGENT_HEADER,
};
use http::{Request, Response, StatusCode, Uri};
// use futures::future::ok;
use futures::future::{loop_fn, Future, Loop};
use futures::prelude::*;
use base64::{encode_config, URL_SAFE};
@ -159,7 +162,7 @@ impl Source {
let code = res.status();
if code.is_success() {
// If request is successful save the etag
// If request is succesful save the etag
self = self.update_etag(&res)?
} else {
match code.as_u16() {
@ -189,7 +192,7 @@ impl Source {
return Err(DataError::FeedRedirect(self));
}
401 => return Err(self.make_err("401: Unauthorized.", code)),
403 => return Err(self.make_err("403: Forbidden.", code)),
403 => return Err(self.make_err("403: Forbidden.", code)),
404 => return Err(self.make_err("404: Not found.", code)),
408 => return Err(self.make_err("408: Request Timeout.", code)),
410 => return Err(self.make_err("410: Feed was deleted..", code)),
@ -213,7 +216,10 @@ impl Source {
self = self.save()?;
debug!("Updated Source: {:#?}", &self);
info!("Feed url of Source {}, was updated succesfully.", self.id());
info!(
"Feed url of Source {}, was updated successfully.",
self.id()
);
}
Ok(self)
@ -236,45 +242,41 @@ impl Source {
///
/// Consumes `self` and Returns the corresponding `Feed` Object.
// Refactor into TryInto once it lands on stable.
pub async fn into_feed(
pub fn into_feed(
self,
client: Client<HttpsConnector<HttpConnector>>,
) -> Result<Feed, DataError> {
) -> impl Future<Item = Feed, Error = DataError> {
let id = self.id();
let response = loop_fn(self, move |source| {
source
.request_constructor(&client.clone())
.then(|res| match res {
Ok(response) => Ok(Loop::Break(response)),
Err(err) => match err {
DataError::FeedRedirect(s) => {
info!("Following redirect...");
Ok(Loop::Continue(s))
}
e => Err(e),
},
})
});
let resp = self.get_response(&client).await?;
let chan = response_to_channel(resp).await?;
FeedBuilder::default()
.channel(chan)
.source_id(id)
.build()
.map_err(From::from)
response
.and_then(response_to_channel)
.and_then(move |chan| {
FeedBuilder::default()
.channel(chan)
.source_id(id)
.build()
.map_err(From::from)
})
}
async fn get_response(
fn request_constructor(
self,
client: &Client<HttpsConnector<HttpConnector>>,
) -> Result<Response<Body>, DataError> {
let mut source = self;
loop {
match source.request_constructor(&client.clone()).await {
Ok(response) => return Ok(response),
Err(err) => match err {
DataError::FeedRedirect(s) => {
info!("Following redirect...");
source = s;
}
e => return Err(e),
},
}
}
}
async fn request_constructor(
self,
client: &Client<HttpsConnector<HttpConnector>>,
) -> Result<Response<Body>, DataError> {
) -> impl Future<Item = Response<Body>, Error = DataError> {
// FIXME: remove unwrap somehow
let uri = Uri::from_str(self.uri()).unwrap();
let mut req = Request::get(uri).body(Body::empty()).unwrap();
@ -305,22 +307,30 @@ impl Source {
.insert(IF_MODIFIED_SINCE, HeaderValue::from_str(lmod).unwrap());
}
let res = client.request(req).await?;
//.map_err(From::from)
self.match_status(res)
client
.request(req)
.map_err(From::from)
.and_then(move |res| self.match_status(res))
}
}
async fn response_to_channel(res: Response<Body>) -> Result<Channel, DataError> {
let chunk = hyper::body::to_bytes(res.into_body()).await?;
let buf = String::from_utf8_lossy(&chunk).into_owned();
Channel::from_str(&buf).map_err(From::from)
fn response_to_channel(
res: Response<Body>,
) -> impl Future<Item = Channel, Error = DataError> + Send {
res.into_body()
.concat2()
.map(|x| x.into_iter())
.map_err(From::from)
.map(|iter| iter.collect::<Vec<u8>>())
.map(|utf_8_bytes| String::from_utf8_lossy(&utf_8_bytes).into_owned())
.and_then(|buf| Channel::from_str(&buf).map_err(From::from))
}
#[cfg(test)]
mod tests {
use super::*;
use failure::Error;
use num_cpus;
use tokio;
use crate::database::truncate_db;
@ -331,7 +341,7 @@ mod tests {
truncate_db()?;
let mut rt = tokio::runtime::Runtime::new()?;
let https = HttpsConnector::new();
let https = HttpsConnector::new(num_cpus::get())?;
let client = Client::builder().build::<_, Body>(https);
let url = "https://web.archive.org/web/20180120083840if_/https://feeds.feedburner.\

View File

@ -43,7 +43,7 @@ use failure::Error;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
// FIXME: Make it a Diesel model
/// Represents an `outline` xml element as per the `OPML` [specification][spec]
/// not `RSS` related sub-elements are ommited.
/// not `RSS` related sub-elements are omitted.
///
/// [spec]: http://dev.opml.org/spec2.html
pub struct Opml {
@ -82,7 +82,7 @@ pub fn import_from_file<P: AsRef<Path>>(path: P) -> Result<Vec<Source>, DataErro
import_to_db(content.as_slice()).map_err(From::from)
}
/// Export a file to `P`, taking the feeds from the database and outputing
/// Export a file to `P`, taking the feeds from the database and outputting
/// them in opml format.
pub fn export_from_db<P: AsRef<Path>>(path: P, export_title: &str) -> Result<(), Error> {
let file = File::create(path)?;
@ -163,7 +163,7 @@ pub fn export_to_file<F: Write>(file: F, export_title: &str) -> Result<(), Error
Ok(())
}
/// Extracts the `outline` elemnts from a reader `R` and returns a `HashSet` of `Opml` structs.
/// Extracts the `outline` elements from a reader `R` and returns a `HashSet` of `Opml` structs.
pub fn extract_sources<R: Read>(reader: R) -> Result<HashSet<Opml>, reader::Error> {
let mut list = HashSet::new();
let parser = reader::EventReader::new(reader);
@ -210,7 +210,7 @@ mod tests {
use super::*;
use chrono::Local;
use failure::Error;
use futures::executor::block_on;
use futures::Future;
use crate::database::{truncate_db, TEMPDIR};
use crate::utils::get_feed;
@ -318,7 +318,7 @@ mod tests {
// Create and insert a Source into db
let s = Source::from_url(url).unwrap();
let feed = get_feed(path, s.id());
block_on(feed.index()).unwrap();
feed.index().wait().unwrap();
});
let mut map: HashSet<Opml> = HashSet::new();

View File

@ -20,13 +20,15 @@
// FIXME:
//! Docs.
use futures::{future::ok, prelude::*, stream::FuturesUnordered};
use futures::{future::ok, lazy, prelude::*, stream::FuturesUnordered};
use tokio;
use hyper::client::HttpConnector;
use hyper::{Body, Client};
use hyper_tls::HttpsConnector;
use num_cpus;
use crate::errors::DataError;
use crate::Source;
@ -40,24 +42,29 @@ type HttpsClient = Client<HttpsConnector<HttpConnector>>;
/// Messy temp diagram:
/// Source -> GET Request -> Update Etags -> Check Status -> Parse `xml/Rss` ->
/// Convert `rss::Channel` into `Feed` -> Index Podcast -> Index Episodes.
#[tokio::main]
pub async fn pipeline<'a, S>(mut sources: S, client: HttpsClient)
pub fn pipeline<'a, S>(sources: S, client: HttpsClient) -> impl Future<Item = (), Error = ()> + 'a
where
S: Stream<Item = Result<Source, DataError>> + Send + 'a + std::marker::Unpin,
S: Stream<Item = Source, Error = DataError> + Send + 'a,
{
while let Some(source_result) = sources.next().await {
if let Ok(source) = source_result {
match source.into_feed(client.clone()).await {
Ok(feed) => {
let fut = feed.index().map_err(|err| error!("Error: {}", err));
tokio::spawn(fut);
}
// Avoid spamming the stderr when it's not an actual error
Err(DataError::FeedNotModified(_)) => (),
Err(err) => error!("Error: {}", err),
};
}
}
sources
.and_then(move |s| s.into_feed(client.clone()))
.map_err(|err| {
match err {
// Avoid spamming the stderr when its not an eactual error
DataError::FeedNotModified(_) => (),
_ => error!("Error: {}", err),
}
})
.and_then(move |feed| {
let fut = lazy(|| feed.index().map_err(|err| error!("Error: {}", err)));
tokio::spawn(fut);
Ok(())
})
// For each terminates the stream at the first error so we make sure
// we pass good values regardless
.then(move |_| ok(()))
// Convert the stream into a Future to later execute as a tokio task
.for_each(move |_| ok(()))
}
/// Creates a tokio `reactor::Core`, and a `hyper::Client` and
@ -66,12 +73,13 @@ pub fn run<S>(sources: S) -> Result<(), DataError>
where
S: IntoIterator<Item = Source>,
{
let https = HttpsConnector::new();
let https = HttpsConnector::new(num_cpus::get())?;
let client = Client::builder().build::<_, Body>(https);
let foo = sources.into_iter().map(ok::<_, _>);
let stream = FuturesUnordered::from_iter(foo);
pipeline(stream, client);
let p = pipeline(stream, client);
tokio::run(p);
Ok(())
}
@ -113,7 +121,7 @@ mod tests {
run(sources)?;
let sources = dbqueries::get_sources()?;
// Run again to cover Unique constrains errors.
// Run again to cover Unique constrains erros.
run(sources)?;
// Assert the index rows equal the controlled results

View File

@ -56,7 +56,7 @@ fn download_checker() -> Result<(), DataError> {
Ok(())
}
/// Delete watched `episodes` that have exceded their liftime after played.
/// Delete watched `episodes` that have exceeded their lifetime after played.
fn played_cleaner(cleanup_date: DateTime<Utc>) -> Result<(), DataError> {
let mut episodes = dbqueries::get_played_cleaner_episodes()?;
let now_utc = cleanup_date.timestamp() as i32;
@ -68,7 +68,7 @@ fn played_cleaner(cleanup_date: DateTime<Utc>) -> Result<(), DataError> {
let limit = ep.played().unwrap();
if now_utc > limit {
delete_local_content(ep)
.map(|_| info!("Episode {:?} was deleted succesfully.", ep.local_uri()))
.map(|_| info!("Episode {:?} was deleted successfully.", ep.local_uri()))
.map_err(|err| error!("Error: {}", err))
.map_err(|_| error!("Failed to delete file: {:?}", ep.local_uri()))
.ok();
@ -144,11 +144,11 @@ pub fn get_download_folder(pd_title: &str) -> Result<String, DataError> {
// TODO: Write Tests
pub fn delete_show(pd: &Show) -> Result<(), DataError> {
dbqueries::remove_feed(pd)?;
info!("{} was removed succesfully.", pd.title());
info!("{} was removed successfully.", pd.title());
let fold = get_download_folder(pd.title())?;
fs::remove_dir_all(&fold)?;
info!("All the content at, {} was removed succesfully", &fold);
info!("All the content at, {} was removed successfully", &fold);
Ok(())
}

View File

@ -5,14 +5,13 @@ version = "0.1.0"
edition = "2018"
[dependencies]
error-chain = "0.12.1"
log = "0.4.8"
mime_guess = "2.0.1"
mime_guess = "2.0.3"
reqwest = "0.9.22"
tempdir = "0.3.7"
glob = "0.3.0"
failure = "0.1.6"
failure_derive = "0.1.6"
failure = "0.1.8"
failure_derive = "0.1.8"
[dependencies.podcasts-data]
path = "../podcasts-data"

View File

@ -54,7 +54,7 @@ pub trait DownloadProgress {
// Sorry to those who will have to work with that code.
// Would much rather use a crate,
// or bindings for a lib like youtube-dl(python),
// But cant seem to find one.
// But can't seem to find one.
// TODO: Write unit-tests.
fn download_into(
dir: &str,
@ -64,7 +64,7 @@ fn download_into(
) -> Result<String, DownloadError> {
info!("GET request to: {}", url);
// Haven't included the loop check as
// Steal the Stars would tigger it as
// Steal the Stars would trigger it as
// it has a loop back before giving correct url
let policy = RedirectPolicy::custom(|attempt| {
info!("Redirect Attempt URL: {:?}", attempt.url());
@ -104,7 +104,7 @@ fn download_into(
.and_then(|h| h.to_str().ok())
.map(From::from);
ct_len.map(|x| info!("File Lenght: {}", x));
ct_len.map(|x| info!("File Length: {}", x));
ct_type.map(|x| info!("Content Type: {}", x));
let ext = get_ext(ct_type).unwrap_or_else(|| String::from("unknown"));
@ -131,7 +131,7 @@ fn download_into(
let target = format!("{}/{}.{}", dir, file_title, ext);
// Rename/move the tempfile into a permanent place upon success.
rename(out_file, &target)?;
info!("Downloading of {} completed succesfully.", &target);
info!("Downloading of {} completed successfully.", &target);
Ok(target)
}
@ -219,10 +219,10 @@ pub fn get_episode(
progress,
)?;
// If download succedes set episode local_uri to dlpath.
// If download succeeds set episode local_uri to dlpath.
ep.set_local_uri(Some(&path));
// Over-write episode lenght
// Over-write episode length
let size = fs::metadata(path);
if let Ok(s) = size {
ep.set_length(Some(s.len() as i32))

View File

@ -5,47 +5,44 @@ version = "0.1.0"
edition = "2018"
[dependencies]
chrono = "0.4.9"
chrono = "0.4.11"
crossbeam-channel = "0.3.9"
gdk = "0.12.0"
gdk = "0.12.1"
gdk-pixbuf = "0.8.0"
gobject-sys = "0.9.1"
glib-sys = "0.9.1"
gst = { version = "0.15.2", package = "gstreamer" }
gst-player = { version = "0.15.0", package = "gstreamer-player" }
gst = { version = "0.15.7", package = "gstreamer" }
gst-player = { version = "0.15.5", package = "gstreamer-player" }
humansize = "1.1.0"
lazy_static = "1.4.0"
log = "0.4.8"
loggerv = "0.7.2"
open = "1.3.2"
rayon = "1.2.0"
open = "1.4.0"
rayon = "1.3.1"
url = "2.1.0"
failure = "0.1.6"
failure_derive = "0.1.6"
fragile = "0.3.0"
regex = "1.3.1"
failure = "0.1.8"
failure_derive = "0.1.8"
fragile = "1.0.0"
regex = "1.3.9"
reqwest = "0.9.22"
serde_json = "1.0.41"
# html2text = "0.1.8"
html2text = { git = "https://github.com/jugglerchris/rust-html2text" }
serde_json = "1.0.55"
html2text = "0.1.12"
mpris-player = "0.5.0"
pango = "0.8.0"
glib = "0.9.3"
[dependencies.gettext-rs]
git = "https://github.com/danigm/gettext-rs"
branch = "no-gettext"
features = ["gettext-system"]
[dependencies.glib]
version = "0.9.1"
[dependencies.gio]
features = ["v2_50"]
version = "0.8.0"
version = "0.8.1"
[dependencies.gtk]
features = ["v3_24"]
version = "0.8.0"
version = "0.8.1"
[dependencies.libhandy]
version = "0.5.0"

View File

@ -2,22 +2,23 @@
# Copyright (C) 2018 podcasts's COPYRIGHT HOLDER
# This file is distributed under the same license as the podcasts package.
# arverne73 <arverne@wanadoo.fr>, 2018.
# Alexandre Franke <alexandre.franke@gmail.com>, 2018
# Alexandre Franke <alexandre.franke@gmail.com>, 2018, 2020
# Thibault Martin <mail@thibaultmart.in>, 2020.
#
msgid ""
msgstr ""
"Project-Id-Version: podcasts master\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n"
"POT-Creation-Date: 2018-10-23 10:23+0000\n"
"PO-Revision-Date: 2018-10-29 13:53+0100\n"
"Last-Translator: Alexandre Franke <alexandre.franke@gmail.com>\n"
"POT-Creation-Date: 2020-06-03 20:13+0000\n"
"PO-Revision-Date: 2020-06-19 17:15+0200\n"
"Last-Translator: Thibault Martin <mail@thibaultmart.in>\n"
"Language-Team: French <gnomefr@traduc.org>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Poedit 2.0.6\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
"X-Generator: Gtranslator 3.36.0\n"
#: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:15
msgid "Top position of the last open main window"
@ -49,8 +50,7 @@ msgstr "Indique sil faut actualiser périodiquement le contenu"
#: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:46
msgid "How many periods of time to wait between automatic refreshes"
msgstr ""
"Nombre de périodes de délais à attendre entre les actualisations automatiques"
msgstr "Nombre de délais à attendre entre les actualisations automatiques"
#: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:50
msgid "What period of time to wait between automatic refreshes"
@ -71,8 +71,8 @@ msgstr "Délai entre les nettoyages automatiques"
#. Weird magic I copy-pasted that sets the Application Name in the Shell.
#: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3
#: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4
#: podcasts-gtk/src/app.rs:92 podcasts-gtk/src/app.rs:416
#: podcasts-gtk/src/widgets/aboutdialog.rs:37
#: podcasts-gtk/resources/gtk/headerbar.ui:158 podcasts-gtk/src/app.rs:353
#: podcasts-gtk/src/widgets/aboutdialog.rs:56 podcasts-gtk/src/window.rs:82
msgid "Podcasts"
msgstr "Podcasts"
@ -81,11 +81,6 @@ msgstr "Podcasts"
msgid "Listen to your favorite podcasts, right from your desktop."
msgstr "Écouter vos podcasts favoris directement sur votre bureau."
#. Translators: Do NOT translate or transliterate this text (this is an icon file name)!
#: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:6
msgid "@icon@"
msgstr "@icon@"
#. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
#: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13
msgid "Podcast;RSS;"
@ -95,13 +90,13 @@ msgstr "Podcast;RSS;Baladodiffusion;Émissions;"
msgid "Podcast app for GNOME"
msgstr "Application de podcast pour GNOME"
#: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:68
#: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:84
msgid "Jordan Petridis"
msgstr "Jordan Petridis"
#: podcasts-gtk/resources/gtk/empty_view.ui:46
msgid "This show does not have episodes yet"
msgstr "Cette émission na pas encore dépisodes"
msgstr "Cette émission na pas encore dépisode"
#: podcasts-gtk/resources/gtk/empty_view.ui:62
msgid "If you think this is an error, please consider writing a bug report."
@ -119,19 +114,23 @@ msgstr "Ajouter de nouvelles émissions via une URL de flux"
msgid "Import shows from another device"
msgstr "Importer des émissions à partir dun autre appareil"
#: podcasts-gtk/resources/gtk/episode_widget.ui:180
#: podcasts-gtk/resources/gtk/episode_widget.ui:79
msgid "Youve already listened to this episode."
msgstr "Vous avez déjà écouté cet épisode."
#: podcasts-gtk/resources/gtk/episode_widget.ui:208
msgid "Calculating episode size…"
msgstr "Calcul de la taille de lépisode…"
#: podcasts-gtk/resources/gtk/episode_widget.ui:220
#: podcasts-gtk/resources/gtk/episode_widget.ui:248
msgid "Play this episode"
msgstr "Lire cet épisode"
#: podcasts-gtk/resources/gtk/episode_widget.ui:241
#: podcasts-gtk/resources/gtk/episode_widget.ui:269
msgid "Cancel the download process"
msgstr "Annuler le téléchargement"
#: podcasts-gtk/resources/gtk/episode_widget.ui:264
#: podcasts-gtk/resources/gtk/episode_widget.ui:292
msgid "Download this episode"
msgstr "Télécharger cet épisode"
@ -143,20 +142,20 @@ msgstr "_Chercher de nouveaux épisodes"
msgid "_Import Shows"
msgstr "_Importer les émissions"
#: podcasts-gtk/resources/gtk/hamburger.ui:22
msgid "_Preferences"
msgstr "_Préférences"
#: podcasts-gtk/resources/gtk/hamburger.ui:16
msgid "_Export Shows"
msgstr "_Exporter les émissions"
#: podcasts-gtk/resources/gtk/hamburger.ui:27
#: podcasts-gtk/resources/gtk/hamburger.ui:22
msgid "_Keyboard Shortcuts"
msgstr "_Raccourcis clavier"
#: podcasts-gtk/resources/gtk/hamburger.ui:35
#: podcasts-gtk/resources/gtk/hamburger.ui:30
msgid "_About Podcasts"
msgstr "À _propos de Podcasts"
#: podcasts-gtk/resources/gtk/headerbar.ui:35
#: podcasts-gtk/resources/gtk/headerbar.ui:189
#: podcasts-gtk/resources/gtk/headerbar.ui:186
msgid "Add a new feed"
msgstr "Ajouter un nouveau flux"
@ -168,15 +167,11 @@ msgstr "Entrer ladresse du flux à ajouter"
msgid "Add"
msgstr "Ajouter"
#: podcasts-gtk/resources/gtk/headerbar.ui:133
msgid "You are already subscribed to that feed!"
msgstr "Vous êtes déjà abonné à ce flux !"
#: podcasts-gtk/resources/gtk/headerbar.ui:169
#: podcasts-gtk/resources/gtk/headerbar.ui:171
msgid "Show Title"
msgstr "Titre de lémission"
#: podcasts-gtk/resources/gtk/headerbar.ui:210
#: podcasts-gtk/resources/gtk/headerbar.ui:207
msgid "Back"
msgstr "Retour"
@ -191,11 +186,6 @@ msgstr "Chercher de nouveaux épisodes"
#: podcasts-gtk/resources/gtk/help-overlay.ui:25
msgctxt "shortcut window"
msgid "Preferences"
msgstr "Préférences"
#: podcasts-gtk/resources/gtk/help-overlay.ui:32
msgctxt "shortcut window"
msgid "Quit the application"
msgstr "Quitter lapplication"
@ -227,71 +217,54 @@ msgstr "Une notification daction intégrée à lapplication"
msgid "Undo"
msgstr "Annuler"
#: podcasts-gtk/resources/gtk/player_toolbar.ui:72
msgid "Rewind 10 seconds"
msgstr "Reculer de 10 secondes"
#: podcasts-gtk/resources/gtk/player_dialog.ui:14
msgid "Now Playing"
msgstr "Lecture en cours"
#: podcasts-gtk/resources/gtk/player_toolbar.ui:87
msgid "Play"
msgstr "Lire"
#: podcasts-gtk/resources/gtk/player_toolbar.ui:103
msgid "Pause"
msgstr "Pause"
#: podcasts-gtk/resources/gtk/player_toolbar.ui:119
msgid "Fast forward 10 seconds"
msgstr "Avancer de 10 secondes"
#: podcasts-gtk/resources/gtk/player_toolbar.ui:285
#: podcasts-gtk/resources/gtk/player_rate.ui:32
msgid "Change the playback speed"
msgstr "Changer la vitesse de lecture"
#: podcasts-gtk/resources/gtk/player_toolbar.ui:300
#: podcasts-gtk/resources/gtk/player_toolbar.ui:380
#: podcasts-gtk/resources/gtk/player_rate.ui:47
#: podcasts-gtk/resources/gtk/player_rate.ui:122
msgid "1.00×"
msgstr "1,00×"
#: podcasts-gtk/resources/gtk/player_toolbar.ui:344
#: podcasts-gtk/resources/gtk/player_rate.ui:86
msgid "1.50×"
msgstr "1,50×"
#: podcasts-gtk/resources/gtk/player_toolbar.ui:348
#: podcasts-gtk/resources/gtk/player_rate.ui:90
msgid "1.5 speed rate"
msgstr "Débit × 1,5"
#: podcasts-gtk/resources/gtk/player_toolbar.ui:362
#: podcasts-gtk/resources/gtk/player_rate.ui:104
msgid "1.25×"
msgstr "1,25×"
#: podcasts-gtk/resources/gtk/player_toolbar.ui:366
#: podcasts-gtk/resources/gtk/player_rate.ui:108
msgid "1.25 speed rate"
msgstr "Débit × 1,25"
#: podcasts-gtk/resources/gtk/player_toolbar.ui:384
#: podcasts-gtk/resources/gtk/player_rate.ui:126
msgid "Normal speed"
msgstr "Vitesse normale"
#: podcasts-gtk/resources/gtk/prefs.ui:42
#: podcasts-gtk/resources/gtk/prefs.ui:295
msgid "Preferences"
msgstr "Préférences"
#: podcasts-gtk/resources/gtk/player_toolbar.ui:97
msgid "Rewind 10 seconds"
msgstr "Reculer de 10 secondes"
#: podcasts-gtk/resources/gtk/prefs.ui:76
msgid "Appearance"
msgstr "Apparence"
#: podcasts-gtk/resources/gtk/player_toolbar.ui:112
msgid "Play"
msgstr "Lire"
#: podcasts-gtk/resources/gtk/prefs.ui:120
msgid "Dark Theme"
msgstr "Thème sombre"
#: podcasts-gtk/resources/gtk/player_toolbar.ui:128
msgid "Pause"
msgstr "Pause"
#: podcasts-gtk/resources/gtk/prefs.ui:166
msgid "Delete played episodes"
msgstr "Supprimer les épisodes lus"
#: podcasts-gtk/resources/gtk/prefs.ui:211
msgid "After"
msgstr "Après"
#: podcasts-gtk/resources/gtk/player_toolbar.ui:144
msgid "Fast forward 10 seconds"
msgstr "Avancer de 10 secondes"
#: podcasts-gtk/resources/gtk/secondary_menu.ui:7
msgid "_Mark All Episodes as Played"
@ -317,91 +290,135 @@ msgstr "Marquer tout comme lu"
msgid "Unsubscribe"
msgstr "Se désabonner"
#: podcasts-gtk/src/app.rs:333
#: podcasts-gtk/resources/gtk/show_widget.ui:99
msgid "Read More"
msgstr "En savoir plus"
#: podcasts-gtk/src/app.rs:287
msgid "Fetching new episodes"
msgstr "Récupérer les nouveaux épisodes"
#: podcasts-gtk/src/headerbar.rs:98
msgid "You are already subscribed to this show"
msgstr "Vous êtes déjà abonné à cette émission"
#: podcasts-gtk/src/headerbar.rs:106
msgid "Invalid URL"
msgstr "URL non valide"
#: podcasts-gtk/src/prefs.rs:59
msgid "Seconds"
msgstr "Secondes"
#: podcasts-gtk/src/prefs.rs:60
msgid "Minutes"
msgstr "Minutes"
#: podcasts-gtk/src/prefs.rs:61
msgid "Hours"
msgstr "Heures"
#: podcasts-gtk/src/prefs.rs:62
msgid "Days"
msgstr "Jours"
#: podcasts-gtk/src/prefs.rs:63
msgid "Weeks"
msgstr "Semaines"
#: podcasts-gtk/src/stacks/content.rs:35
#: podcasts-gtk/src/stacks/content.rs:54
msgid "New"
msgstr "Nouveau"
#: podcasts-gtk/src/stacks/content.rs:36
#: podcasts-gtk/src/stacks/content.rs:55
msgid "Shows"
msgstr "Émissions"
#: podcasts-gtk/src/utils.rs:357
#: podcasts-gtk/src/utils.rs:398
msgid "Select the file from which to you want to import shows."
msgstr "Sélectionnez le fichier à partir duquel importer les émissions."
#: podcasts-gtk/src/utils.rs:360
#: podcasts-gtk/src/utils.rs:401
msgid "_Import"
msgstr "_Importer"
#: podcasts-gtk/src/utils.rs:369
#: podcasts-gtk/src/utils.rs:410 podcasts-gtk/src/utils.rs:457
msgid "OPML file"
msgstr "Fichier OPML"
#: podcasts-gtk/src/utils.rs:386
#: podcasts-gtk/src/utils.rs:427
msgid "Failed to parse the imported file"
msgstr "Échec de lanalyse du fichier importé"
#: podcasts-gtk/src/utils.rs:391
#: podcasts-gtk/src/utils.rs:432 podcasts-gtk/src/utils.rs:475
msgid "Selected file could not be accessed."
msgstr "Le fichier sélectionné nest pas accessible."
#: podcasts-gtk/src/widgets/aboutdialog.rs:32
#: podcasts-gtk/src/utils.rs:445
msgid "Export shows to…"
msgstr "Exporter les émissions vers…"
#: podcasts-gtk/src/utils.rs:448
msgid "_Export"
msgstr "_Exporter"
#: podcasts-gtk/src/utils.rs:449
msgid "_Cancel"
msgstr "A_nnuler"
#: podcasts-gtk/src/utils.rs:469
msgid "GNOME Podcasts Subscriptions"
msgstr "Abonnements GNOME Podcasts"
#: podcasts-gtk/src/utils.rs:470
msgid "Failed to export podcasts"
msgstr "Échec de lexportation des podcasts"
#: podcasts-gtk/src/widgets/aboutdialog.rs:51
msgid "Podcast Client for the GNOME Desktop."
msgstr "Client de podcast pour le bureau GNOME."
#: podcasts-gtk/src/widgets/aboutdialog.rs:39
#: podcasts-gtk/src/widgets/aboutdialog.rs:58
msgid "Learn more about GNOME Podcasts"
msgstr "En apprendre plus sur GNOME Podcasts"
#: podcasts-gtk/src/widgets/aboutdialog.rs:44
#: podcasts-gtk/src/widgets/aboutdialog.rs:63
msgid "translator-credits"
msgstr "Alexandre Franke"
msgstr ""
"Alexandre Franke\n"
"Thibault Martin"
#: podcasts-gtk/src/widgets/episode.rs:130
#: podcasts-gtk/src/widgets/episode.rs:149
msgid "{} min"
msgstr "{} min"
#. sender.send(Action::ErrorNotification(format!("Player Error: {}", error)));
#: podcasts-gtk/src/widgets/player.rs:365
#: podcasts-gtk/src/widgets/player.rs:828
msgid "The media player was unable to execute an action."
msgstr "Le lecteur na pas pu réaliser laction."
#: podcasts-gtk/src/widgets/show_menu.rs:150
#: podcasts-gtk/src/widgets/show_menu.rs:174
msgid "Marked all episodes as listened"
msgstr "Marquer tous les épisodes comme écoutés"
#: podcasts-gtk/src/widgets/show_menu.rs:155
#: podcasts-gtk/src/widgets/show_menu.rs:179
msgid "Unsubscribed from {}"
msgstr "Se désabonner de {}"
#~ msgid "@icon@"
#~ msgstr "@icon@"
#~ msgid "_Preferences"
#~ msgstr "_Préférences"
#~ msgid "You are already subscribed to that feed!"
#~ msgstr "Vous êtes déjà abonné à ce flux !"
#~ msgctxt "shortcut window"
#~ msgid "Preferences"
#~ msgstr "Préférences"
#~ msgid "Preferences"
#~ msgstr "Préférences"
#~ msgid "Appearance"
#~ msgstr "Apparence"
#~ msgid "Dark Theme"
#~ msgstr "Thème sombre"
#~ msgid "Delete played episodes"
#~ msgstr "Supprimer les épisodes lus"
#~ msgid "After"
#~ msgstr "Après"
#~ msgid "Invalid URL"
#~ msgstr "URL non valide"
#~ msgid "Seconds"
#~ msgstr "Secondes"
#~ msgid "Minutes"
#~ msgstr "Minutes"
#~ msgid "Hours"
#~ msgstr "Heures"
#~ msgid "Days"
#~ msgstr "Jours"
#~ msgid "Weeks"
#~ msgstr "Semaines"

View File

@ -110,6 +110,9 @@ Tobias Bernard
<style>
<class name="dim-label"/>
</style>
<attributes>
<attribute name="font-features" value="tnum=1"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
@ -143,6 +146,9 @@ Tobias Bernard
<style>
<class name="dim-label"/>
</style>
<attributes>
<attribute name="font-features" value="tnum=1"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
@ -177,6 +183,9 @@ Tobias Bernard
<style>
<class name="dim-label"/>
</style>
<attributes>
<attribute name="font-features" value="tnum=1"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
@ -212,6 +221,9 @@ Tobias Bernard
<style>
<class name="dim-label"/>
</style>
<attributes>
<attribute name="font-features" value="tnum=1"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>

View File

@ -132,6 +132,9 @@
<property name="can_focus">False</property>
<property name="width_chars">5</property>
<property name="xalign">1</property>
<attributes>
<attribute name="font-features" value="tnum=1"/>
</attributes>
<style>
<class name="dim-label"/>
<class name="small-label"/>
@ -154,6 +157,9 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="width_chars">5</property>
<attributes>
<attribute name="font-features" value="tnum=1"/>
</attributes>
<style>
<class name="dim-label"/>
<class name="small-label"/>

View File

@ -45,6 +45,9 @@ Tobias Bernard
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">1.00×</property>
<attributes>
<attribute name="font-features" value="tnum=1"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
@ -75,23 +78,15 @@ Tobias Bernard
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_start">6</property>
<property name="margin_end">6</property>
<property name="margin_top">6</property>
<property name="margin_bottom">6</property>
<property name="margin">6</property>
<property name="orientation">vertical</property>
<property name="spacing">3</property>
<child>
<object class="GtkRadioButton" id="rate_3_00">
<property name="label" translatable="yes">3.00×</property>
<object class="GtkModelButton" id="rate_4_00">
<property name="text">&lt;span font-features="tnum=1"&gt;4.00×&lt;/span&gt;</property>
<property name="use-markup">True</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">3.0 speed rate</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="draw_indicator">True</property>
<property name="group">normal_rate</property>
<property name="tooltip_text" translatable="yes">4.0 speed rate</property>
<property name="role">radio</property>
</object>
<packing>
<property name="expand">True</property>
@ -100,16 +95,12 @@ Tobias Bernard
</packing>
</child>
<child>
<object class="GtkRadioButton" id="rate_2_75">
<property name="label" translatable="yes">2.75×</property>
<object class="GtkModelButton" id="rate_3_75">
<property name="text">&lt;span font-features="tnum=1"&gt;3.75×&lt;/span&gt;</property>
<property name="use-markup">True</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">2.75 speed rate</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="draw_indicator">True</property>
<property name="group">normal_rate</property>
<property name="tooltip_text" translatable="yes">3.75 speed rate</property>
<property name="role">radio</property>
</object>
<packing>
<property name="expand">True</property>
@ -118,16 +109,12 @@ Tobias Bernard
</packing>
</child>
<child>
<object class="GtkRadioButton" id="rate_2_50">
<property name="label" translatable="yes">2.50×</property>
<object class="GtkModelButton" id="rate_3_50">
<property name="text">&lt;span font-features="tnum=1"&gt;3.50×&lt;/span&gt;</property>
<property name="use-markup">True</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">2.5 speed rate</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="draw_indicator">True</property>
<property name="group">normal_rate</property>
<property name="tooltip_text" translatable="yes">3.5 speed rate</property>
<property name="role">radio</property>
</object>
<packing>
<property name="expand">True</property>
@ -136,16 +123,12 @@ Tobias Bernard
</packing>
</child>
<child>
<object class="GtkRadioButton" id="rate_2_25">
<property name="label" translatable="yes">2.25×</property>
<object class="GtkModelButton" id="rate_3_25">
<property name="text">&lt;span font-features="tnum=1"&gt;3.25×&lt;/span&gt;</property>
<property name="use-markup">True</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">2.25 speed rate</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="draw_indicator">True</property>
<property name="group">normal_rate</property>
<property name="tooltip_text" translatable="yes">3.25 speed rate</property>
<property name="role">radio</property>
</object>
<packing>
<property name="expand">True</property>
@ -154,16 +137,12 @@ Tobias Bernard
</packing>
</child>
<child>
<object class="GtkRadioButton" id="rate_2_00">
<property name="label" translatable="yes">2.00×</property>
<object class="GtkModelButton" id="rate_3_00">
<property name="text">&lt;span font-features="tnum=1"&gt;3.00×&lt;/span&gt;</property>
<property name="use-markup">True</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">2.0 speed rate</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="draw_indicator">True</property>
<property name="group">normal_rate</property>
<property name="tooltip_text" translatable="yes">3.0 speed rate</property>
<property name="role">radio</property>
</object>
<packing>
<property name="expand">True</property>
@ -172,16 +151,12 @@ Tobias Bernard
</packing>
</child>
<child>
<object class="GtkRadioButton" id="rate_1_75">
<property name="label" translatable="yes">1.75×</property>
<object class="GtkModelButton" id="rate_2_75">
<property name="text">&lt;span font-features="tnum=1"&gt;2.75×&lt;/span&gt;</property>
<property name="use-markup">True</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">1.75 speed rate</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="draw_indicator">True</property>
<property name="group">normal_rate</property>
<property name="tooltip_text" translatable="yes">2.75 speed rate</property>
<property name="role">radio</property>
</object>
<packing>
<property name="expand">True</property>
@ -190,16 +165,12 @@ Tobias Bernard
</packing>
</child>
<child>
<object class="GtkRadioButton" id="rate_1_50">
<property name="label" translatable="yes">1.50×</property>
<object class="GtkModelButton" id="rate_2_50">
<property name="text">&lt;span font-features="tnum=1"&gt;2.50×&lt;/span&gt;</property>
<property name="use-markup">True</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">1.5 speed rate</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="draw_indicator">True</property>
<property name="group">normal_rate</property>
<property name="tooltip_text" translatable="yes">2.5 speed rate</property>
<property name="role">radio</property>
</object>
<packing>
<property name="expand">True</property>
@ -208,16 +179,12 @@ Tobias Bernard
</packing>
</child>
<child>
<object class="GtkRadioButton" id="rate_1_25">
<property name="label" translatable="yes">1.25×</property>
<object class="GtkModelButton" id="rate_2_25">
<property name="text">&lt;span font-features="tnum=1"&gt;2.25×&lt;/span&gt;</property>
<property name="use-markup">True</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">1.25 speed rate</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="draw_indicator">True</property>
<property name="group">normal_rate</property>
<property name="tooltip_text" translatable="yes">2.25 speed rate</property>
<property name="role">radio</property>
</object>
<packing>
<property name="expand">True</property>
@ -226,16 +193,12 @@ Tobias Bernard
</packing>
</child>
<child>
<object class="GtkRadioButton" id="normal_rate">
<property name="label" translatable="yes">1.00×</property>
<object class="GtkModelButton" id="rate_2_00">
<property name="text">&lt;span font-features="tnum=1"&gt;2.00×&lt;/span&gt;</property>
<property name="use-markup">True</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Normal speed</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="tooltip_text" translatable="yes">2.0 speed rate</property>
<property name="role">radio</property>
</object>
<packing>
<property name="expand">True</property>
@ -243,6 +206,63 @@ Tobias Bernard
<property name="position">8</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="rate_1_75">
<property name="text">&lt;span font-features="tnum=1"&gt;1.75×&lt;/span&gt;</property>
<property name="use-markup">True</property>
<property name="visible">True</property>
<property name="tooltip_text" translatable="yes">1.75 speed rate</property>
<property name="role">radio</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">9</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="rate_1_50">
<property name="text">&lt;span font-features="tnum=1"&gt;1.50×&lt;/span&gt;</property>
<property name="use-markup">True</property>
<property name="visible">True</property>
<property name="tooltip_text" translatable="yes">1.5 speed rate</property>
<property name="role">radio</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">10</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="rate_1_25">
<property name="text">&lt;span font-features="tnum=1"&gt;1.25×&lt;/span&gt;</property>
<property name="use-markup">True</property>
<property name="visible">True</property>
<property name="tooltip_text" translatable="yes">1.25 speed rate</property>
<property name="role">radio</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">11</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="normal_rate">
<property name="text">&lt;span font-features="tnum=1"&gt;1.00×&lt;/span&gt;</property>
<property name="use-markup">True</property>
<property name="visible">True</property>
<property name="tooltip_text" translatable="yes">Normal speed</property>
<property name="active">True</property>
<property name="role">radio</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">12</property>
</packing>
</child>
</object>
</child>
</object>

View File

@ -263,6 +263,9 @@ Tobias Bernard
<property name="halign">start</property>
<property name="valign">center</property>
<property name="label">0:00</property>
<attributes>
<attribute name="font-features" value="tnum=1"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
@ -291,6 +294,9 @@ Tobias Bernard
<property name="halign">start</property>
<property name="valign">center</property>
<property name="label">0:00</property>
<attributes>
<attribute name="font-features" value="tnum=1"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>

View File

@ -51,13 +51,13 @@
<release version="0.4.5" date="2018-08-31">
<description>
<p>
Podcasts 0.4.5 brings a month of bug fixes, performance improvements and initial tranlations support.
Podcasts 0.4.5 brings a month of bug fixes, performance improvements and initial translations support.
</p>
<ul>
<li>Finish, Polish, Turkish, Spanish, German, Galician, Indonesian and Korean Translations were added.</li>
<li>Views now adapt better to different window sizes, thanks to libhandy HdyColumn</li>
<li>The update indacator was moved to an In-App notification</li>
<li>Performance improvments when loading Show Cover images.</li>
<li>Performance improvements when loading Show Cover images.</li>
<li>Improved handling of HTTP Redirects</li>
</ul>
</description>

View File

@ -54,12 +54,14 @@ podcasts_sources = files(
'main.rs',
'manager.rs',
'settings.rs',
'utils.rs'
'utils.rs',
'window.rs',
)
cargo_release = custom_target('cargo-build',
build_by_default: true,
input: [
podcast_toml,
data_sources,
downloader_sources,
podcasts_sources,

View File

@ -88,7 +88,7 @@ use crate::i18n::i18n;
pub(crate) fn lazy_load<T, C, F, W, U>(
data: T,
container: WeakRef<C>,
mut contructor: F,
mut constructor: F,
callback: U,
) where
T: IntoIterator + 'static,
@ -104,7 +104,7 @@ pub(crate) fn lazy_load<T, C, F, W, U>(
None => return,
};
let widget = contructor(x);
let widget = constructor(x);
container.add(&widget);
widget.show();
};

View File

@ -18,6 +18,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
use glib;
use glib::clone;
use gtk;
use gtk::prelude::*;
@ -82,21 +83,17 @@ impl InAppNotification {
let notif = InAppNotification::default();
notif.text.set_text(&text);
let revealer_weak = notif.revealer.downgrade();
let mut time = 0;
let id = timeout_add(250, move || {
if time < timer {
time += 250;
return glib::Continue(true);
};
let revealer = match revealer_weak.upgrade() {
Some(r) => r,
None => return glib::Continue(false),
};
callback(revealer)
});
let id = timeout_add(
250,
clone!(@weak notif.revealer as revealer => @default-return glib::Continue(false), move || {
if time < timer {
time += 250;
return glib::Continue(true);
};
callback(revealer)
}),
);
let id = Rc::new(RefCell::new(Some(id)));
if undo_callback.is_some() {

View File

@ -22,6 +22,8 @@ use failure::Error;
use gtk::{self, prelude::*, Adjustment};
use glib::clone;
use crossbeam_channel::Sender;
use libhandy::{Column, ColumnExt};
use podcasts_data::dbqueries;
@ -135,17 +137,11 @@ impl HomeView {
}
};
let home_weak = Rc::downgrade(&home);
let callback = move || {
let home = match home_weak.upgrade() {
Some(h) => h,
None => return,
};
let callback = clone!(@weak home => move || {
if let Some(ref v) = vadj {
home.view.set_adjustments(None, Some(v))
};
};
});
lazy_load_full(episodes, func, callback);
home.view.container().show_all();

View File

@ -208,15 +208,19 @@ fn format_duration(seconds: u32) -> String {
#[derive(Debug, Clone)]
struct PlayerRate {
radio300: gtk::RadioButton,
radio275: gtk::RadioButton,
radio250: gtk::RadioButton,
radio225: gtk::RadioButton,
radio200: gtk::RadioButton,
radio175: gtk::RadioButton,
radio150: gtk::RadioButton,
radio125: gtk::RadioButton,
radio_normal: gtk::RadioButton,
radio400: gtk::ModelButton,
radio375: gtk::ModelButton,
radio350: gtk::ModelButton,
radio325: gtk::ModelButton,
radio300: gtk::ModelButton,
radio275: gtk::ModelButton,
radio250: gtk::ModelButton,
radio225: gtk::ModelButton,
radio200: gtk::ModelButton,
radio175: gtk::ModelButton,
radio150: gtk::ModelButton,
radio125: gtk::ModelButton,
radio_normal: gtk::ModelButton,
popover: gtk::Popover,
btn: gtk::MenuButton,
label: gtk::Label,
@ -226,20 +230,28 @@ impl PlayerRate {
fn new() -> Self {
let builder = gtk::Builder::new_from_resource("/org/gnome/Podcasts/gtk/player_rate.ui");
let radio300: gtk::RadioButton = builder.get_object("rate_3_00").unwrap();
let radio275: gtk::RadioButton = builder.get_object("rate_2_75").unwrap();
let radio250: gtk::RadioButton = builder.get_object("rate_2_50").unwrap();
let radio225: gtk::RadioButton = builder.get_object("rate_2_25").unwrap();
let radio200: gtk::RadioButton = builder.get_object("rate_2_00").unwrap();
let radio175: gtk::RadioButton = builder.get_object("rate_1_75").unwrap();
let radio150: gtk::RadioButton = builder.get_object("rate_1_50").unwrap();
let radio125: gtk::RadioButton = builder.get_object("rate_1_25").unwrap();
let radio_normal: gtk::RadioButton = builder.get_object("normal_rate").unwrap();
let radio400: gtk::ModelButton = builder.get_object("rate_4_00").unwrap();
let radio375: gtk::ModelButton = builder.get_object("rate_3_75").unwrap();
let radio350: gtk::ModelButton = builder.get_object("rate_3_50").unwrap();
let radio325: gtk::ModelButton = builder.get_object("rate_3_25").unwrap();
let radio300: gtk::ModelButton = builder.get_object("rate_3_00").unwrap();
let radio275: gtk::ModelButton = builder.get_object("rate_2_75").unwrap();
let radio250: gtk::ModelButton = builder.get_object("rate_2_50").unwrap();
let radio225: gtk::ModelButton = builder.get_object("rate_2_25").unwrap();
let radio200: gtk::ModelButton = builder.get_object("rate_2_00").unwrap();
let radio175: gtk::ModelButton = builder.get_object("rate_1_75").unwrap();
let radio150: gtk::ModelButton = builder.get_object("rate_1_50").unwrap();
let radio125: gtk::ModelButton = builder.get_object("rate_1_25").unwrap();
let radio_normal: gtk::ModelButton = builder.get_object("normal_rate").unwrap();
let popover = builder.get_object("rate_popover").unwrap();
let btn = builder.get_object("rate_button").unwrap();
let label = builder.get_object("rate_label").unwrap();
PlayerRate {
radio400,
radio375,
radio350,
radio325,
radio300,
radio275,
radio250,
@ -257,45 +269,74 @@ impl PlayerRate {
fn set_rate(&self, rate: f64) {
self.label.set_text(&format!("{:.2}×", rate));
self.radio_normal.set_property_active(rate == 1.0);
self.radio125.set_property_active(rate == 1.25);
self.radio150.set_property_active(rate == 1.5);
self.radio175.set_property_active(rate == 1.75);
self.radio200.set_property_active(rate == 2.0);
self.radio225.set_property_active(rate == 2.25);
self.radio250.set_property_active(rate == 2.5);
self.radio275.set_property_active(rate == 2.75);
self.radio300.set_property_active(rate == 3.0);
self.radio325.set_property_active(rate == 3.25);
self.radio350.set_property_active(rate == 3.5);
self.radio375.set_property_active(rate == 3.75);
self.radio400.set_property_active(rate == 4.0);
}
fn connect_signals(&self, widget: &Rc<PlayerWidget>) {
self.radio_normal
.connect_toggled(clone!(@weak widget => move |_| {
.connect_clicked(clone!(@weak widget => move |_| {
widget.on_rate_changed(1.00);
}));
self.radio125
.connect_toggled(clone!(@weak widget => move |_| {
.connect_clicked(clone!(@weak widget => move |_| {
widget.on_rate_changed(1.25);
}));
self.radio150
.connect_toggled(clone!(@weak widget => move |_| {
.connect_clicked(clone!(@weak widget => move |_| {
widget.on_rate_changed(1.50);
}));
self.radio175
.connect_toggled(clone!(@weak widget => move |_| {
.connect_clicked(clone!(@weak widget => move |_| {
widget.on_rate_changed(1.75);
}));
self.radio200
.connect_toggled(clone!(@weak widget => move |_| {
.connect_clicked(clone!(@weak widget => move |_| {
widget.on_rate_changed(2.00);
}));
self.radio225
.connect_toggled(clone!(@weak widget => move |_| {
.connect_clicked(clone!(@weak widget => move |_| {
widget.on_rate_changed(2.25);
}));
self.radio250
.connect_toggled(clone!(@weak widget => move |_| {
.connect_clicked(clone!(@weak widget => move |_| {
widget.on_rate_changed(2.50);
}));
self.radio275
.connect_toggled(clone!(@weak widget => move |_| {
.connect_clicked(clone!(@weak widget => move |_| {
widget.on_rate_changed(2.75);
}));
self.radio300
.connect_toggled(clone!(@weak widget => move |_| {
.connect_clicked(clone!(@weak widget => move |_| {
widget.on_rate_changed(3.00);
}));
self.radio325
.connect_clicked(clone!(@weak widget => move |_| {
widget.on_rate_changed(3.25);
}));
self.radio350
.connect_clicked(clone!(@weak widget => move |_| {
widget.on_rate_changed(3.50);
}));
self.radio375
.connect_clicked(clone!(@weak widget => move |_| {
widget.on_rate_changed(3.75);
}));
self.radio400
.connect_clicked(clone!(@weak widget => move |_| {
widget.on_rate_changed(4.00);
}));
}
}

View File

@ -17,6 +17,7 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
use glib::clone;
use gtk::{self, prelude::*, Adjustment, Align, SelectionMode};
use crossbeam_channel::Sender;
@ -83,16 +84,14 @@ impl ShowsView {
fn populate_flowbox(shows: &Rc<ShowsView>, vadj: Option<Adjustment>) -> Result<(), Error> {
let ignore = get_ignored_shows()?;
let podcasts = dbqueries::get_podcasts_filter(&ignore)?;
let show_weak = Rc::downgrade(&shows);
let flowbox_weak = shows.flowbox.downgrade();
let constructor = move |parent| ShowsChild::new(&parent).child;
let callback = move || {
match (show_weak.upgrade(), &vadj) {
(Some(ref shows), Some(ref v)) => shows.view.set_adjustments(None, Some(v)),
_ => (),
};
};
let callback = clone!(@weak shows => move || {
if vadj.is_some() {
shows.view.set_adjustments(None, vadj.as_ref())
}
});
lazy_load(podcasts, flowbox_weak, constructor, callback);
Ok(())

View File

@ -48,7 +48,7 @@ use crate::i18n::i18n;
fn action<T, F>(thing: &T, name: &str, action: F)
where
T: ActionMapExt,
for<'r, 's> F: Fn(&'r gio::SimpleAction, Option<&Variant>) + 'static,
F: Fn(&gio::SimpleAction, Option<&Variant>) + 'static,
{
// Create a stateless, parameterless action
let act = gio::SimpleAction::new(name, None);
@ -84,27 +84,17 @@ impl MainWindow {
window.get_style_context().add_class("devel");
}
let weak_s = settings.downgrade();
let weak_app = app.downgrade();
window.connect_delete_event(move |window, _| {
let app = match weak_app.upgrade() {
Some(a) => a,
None => return Inhibit(false),
};
window.connect_delete_event(
clone!(@strong settings, @weak app => @default-return Inhibit(false), move |window, _| {
info!("Saving window position");
WindowGeometry::from_window(&window).write(&settings);
let settings = match weak_s.upgrade() {
Some(s) => s,
None => return Inhibit(false),
};
info!("Saving window position");
WindowGeometry::from_window(&window).write(&settings);
info!("Application is exiting");
let app = app.clone().upcast::<gio::Application>();
app.quit();
Inhibit(false)
});
info!("Application is exiting");
let app = app.upcast::<gio::Application>();
app.quit();
Inhibit(false)
}),
);
// Create a content instance
let content = Content::new(&sender).expect("Content initialization failed.");
@ -128,8 +118,6 @@ impl MainWindow {
wrap.add(&header.bottom_switcher);
let updater = RefCell::new(None);
window.add(&wrap);
// Retrieve the previous window position and size.
@ -138,20 +126,19 @@ impl MainWindow {
// Update the feeds right after the Window is initialized.
if settings.get_boolean("refresh-on-startup") {
info!("Refresh on startup.");
let s: Option<Vec<_>> = None;
utils::schedule_refresh(s, sender.clone());
utils::schedule_refresh(None, sender.clone());
}
let refresh_interval = settings::get_refresh_interval(&settings).num_seconds() as u32;
info!("Auto-refresh every {:?} seconds.", refresh_interval);
let r_sender = sender.clone();
gtk::timeout_add_seconds(refresh_interval, move || {
let s: Option<Vec<_>> = None;
utils::schedule_refresh(s, r_sender.clone());
glib::Continue(true)
});
gtk::timeout_add_seconds(
refresh_interval,
clone!(@strong sender => move || {
utils::schedule_refresh(None, sender.clone());
glib::Continue(true)
}),
);
Self {
app: app.clone(),
@ -161,7 +148,7 @@ impl MainWindow {
content,
player,
updating: Cell::new(false),
updater,
updater: RefCell::new(None),
sender,
receiver,
}
@ -176,41 +163,45 @@ impl MainWindow {
// Create the `refresh` action.
//
// This will trigger a refresh of all the shows in the database.
action(&self.window, "refresh", clone!(@strong sender => move |_, _| {
gtk::idle_add(clone!(@strong sender => move || {
let s: Option<Vec<_>> = None;
utils::schedule_refresh(s, sender.clone());
glib::Continue(false)
action(&self.window, "refresh",
clone!(@strong sender => move |_, _| {
gtk::idle_add(
clone!(@strong sender => move || {
utils::schedule_refresh(None, sender.clone());
glib::Continue(false)
}));
}));
self.app.set_accels_for_action("win.refresh", &["<primary>r"]);
// Create the `OPML` import action
action(&self.window, "import", clone!(@strong sender, @weak self.window as win => move |_, _| {
utils::on_import_clicked(&win, &sender);
action(&self.window, "import",
clone!(@strong sender, @weak self.window as window => move |_, _| {
utils::on_import_clicked(&window, &sender);
}));
action(&self.window, "export", clone!(@strong sender, @weak self.window as win => move |_, _| {
utils::on_export_clicked(&win, &sender);
action(&self.window, "export",
clone!(@strong sender, @weak self.window as window => move |_, _| {
utils::on_export_clicked(&window, &sender);
}));
// Create the action that shows a `gtk::AboutDialog`
action(&self.window, "about", clone!(@weak self.window as win => move |_, _| {
about_dialog(&win);
action(&self.window, "about",
clone!(@weak self.window as win => move |_, _| {
about_dialog(&win);
}));
// Create the quit action
let weak_instance = self.app.downgrade();
action(&self.window, "quit", move |_, _| {
weak_instance.upgrade().map(|app| app.quit());
});
action(&self.window, "quit",
clone!(@weak self.app as app => move |_, _| {
app.quit();
}));
self.app.set_accels_for_action("win.quit", &["<primary>q"]);
// Create the menu action
let header = Rc::downgrade(&self.headerbar);
action(&self.window, "menu", move |_, _| {
header.upgrade().map(|h| h.open_menu());
});
// Create the menu actions
action(&self.window, "menu",
clone!(@weak self.headerbar as headerbar => move |_, _| {
headerbar.open_menu();
}));
// Bind the hamburger menu button to `F10`
self.app.set_accels_for_action("win.menu", &["F10"]);
}

View File

@ -6,16 +6,14 @@ set -x
# $1 Passed by meson and should be the builddir
export CARGO_TARGET_DIR="$1/target/"
export CARGO_HOME="$CARGO_TARGET_DIR/cargo-home"
# If this is run inside a flatpak envrironment, append the export the rustc
# sdk-extension binaries to the path
if [ -f "/.flatpak-info" ]
then
export PATH="$PATH:/usr/lib/sdk/rust-stable/bin"
# This assumes its run inside a Builder terminal
export CARGO_TARGET_DIR="$BUILDDIR/target/"
fi
export CARGO_HOME="$CARGO_TARGET_DIR/cargo-home"
cargo test -j 1 -- --test-threads=1 --nocapture
cargo fetch --locked
cargo test --all-features --offline -- --test-threads=1 --nocapture