Merge branch 'master' of gitlab.gnome.org:World/podcasts
This commit is contained in:
commit
f2ac198831
@ -4,7 +4,7 @@ include:
|
|||||||
# ref: ''
|
# ref: ''
|
||||||
|
|
||||||
flatpak:
|
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:
|
variables:
|
||||||
MANIFEST_PATH: "org.gnome.Podcasts.Devel.json"
|
MANIFEST_PATH: "org.gnome.Podcasts.Devel.json"
|
||||||
FLATPAK_MODULE: "gnome-podcasts"
|
FLATPAK_MODULE: "gnome-podcasts"
|
||||||
|
|||||||
2861
Cargo.lock
generated
2861
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -40,6 +40,14 @@ else
|
|||||||
version_suffix = ''
|
version_suffix = ''
|
||||||
endif
|
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)
|
application_id = 'org.gnome.Podcasts@0@'.format(profile)
|
||||||
i18n = import('i18n')
|
i18n = import('i18n')
|
||||||
gnome = import('gnome')
|
gnome = import('gnome')
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"app-id" : "org.gnome.Podcasts.Devel",
|
"app-id" : "org.gnome.Podcasts.Devel",
|
||||||
"runtime" : "org.gnome.Platform",
|
"runtime" : "org.gnome.Platform",
|
||||||
"runtime-version" : "master",
|
"runtime-version" : "3.36",
|
||||||
"sdk" : "org.gnome.Sdk",
|
"sdk" : "org.gnome.Sdk",
|
||||||
"sdk-extensions" : [
|
"sdk-extensions" : [
|
||||||
"org.freedesktop.Sdk.Extension.rust-stable"
|
"org.freedesktop.Sdk.Extension.rust-stable"
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"app-id" : "org.gnome.Podcasts",
|
"app-id" : "org.gnome.Podcasts",
|
||||||
"runtime" : "org.gnome.Platform",
|
"runtime" : "org.gnome.Platform",
|
||||||
"runtime-version" : "master",
|
"runtime-version" : "3.36",
|
||||||
"sdk" : "org.gnome.Sdk",
|
"sdk" : "org.gnome.Sdk",
|
||||||
"sdk-extensions" : [
|
"sdk-extensions" : [
|
||||||
"org.freedesktop.Sdk.Extension.rust-stable"
|
"org.freedesktop.Sdk.Extension.rust-stable"
|
||||||
|
|||||||
@ -5,39 +5,36 @@ version = "0.1.0"
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ammonia = "3.0.0"
|
ammonia = "3.1.0"
|
||||||
chrono = "0.4.9"
|
chrono = "0.4.11"
|
||||||
derive_builder = "0.8.0"
|
derive_builder = "0.9.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
rayon = "1.2.0"
|
rayon = "1.3.1"
|
||||||
rfc822_sanitizer = "0.3.3"
|
rfc822_sanitizer = "0.3.3"
|
||||||
rss = "1.9.0"
|
rss = "1.9.0"
|
||||||
url = "2.1.1"
|
url = "2.1.1"
|
||||||
xdg = "2.2.0"
|
xdg = "2.2.0"
|
||||||
xml-rs = "0.8.0"
|
xml-rs = "0.8.3"
|
||||||
futures = "0.3.4"
|
futures = "0.1.29"
|
||||||
hyper = "0.13.2"
|
hyper = "0.12.35"
|
||||||
http = "0.2.0"
|
http = "0.1.19"
|
||||||
hyper-tls = "0.4.1"
|
tokio = "0.1.22"
|
||||||
|
hyper-tls = "0.3.2"
|
||||||
native-tls = "0.2.3"
|
native-tls = "0.2.3"
|
||||||
num_cpus = "1.10.1"
|
num_cpus = "1.13.0"
|
||||||
failure = "0.1.6"
|
failure = "0.1.8"
|
||||||
failure_derive = "0.1.6"
|
failure_derive = "0.1.8"
|
||||||
base64 = "0.10.1"
|
base64 = "0.12.2"
|
||||||
|
|
||||||
[dependencies.diesel]
|
[dependencies.diesel]
|
||||||
features = ["sqlite", "r2d2"]
|
features = ["sqlite", "r2d2"]
|
||||||
version = "1.4.3"
|
version = "1.4.5"
|
||||||
|
|
||||||
[dependencies.diesel_migrations]
|
[dependencies.diesel_migrations]
|
||||||
features = ["sqlite"]
|
features = ["sqlite"]
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
|
||||||
[dependencies.tokio]
|
|
||||||
features = ["rt-core", "rt-threaded", "macros"]
|
|
||||||
version = "0.2.13"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = "0.7.2"
|
rand = "0.7.2"
|
||||||
tempdir = "0.3.7"
|
tempdir = "0.3.7"
|
||||||
|
|||||||
@ -84,7 +84,10 @@ pub enum DataError {
|
|||||||
FeedRedirect(Source),
|
FeedRedirect(Source),
|
||||||
#[fail(display = "Feed is up to date")]
|
#[fail(display = "Feed is up to date")]
|
||||||
FeedNotModified(Source),
|
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 },
|
ParseEpisodeError { reason: String, parent_id: i32 },
|
||||||
#[fail(display = "Episode was not changed and thus skipped.")]
|
#[fail(display = "Episode was not changed and thus skipped.")]
|
||||||
EpisodeNotChanged,
|
EpisodeNotChanged,
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
#![allow(clippy::unit_arg)]
|
#![allow(clippy::unit_arg)]
|
||||||
//! Index Feeds.
|
//! Index Feeds.
|
||||||
|
|
||||||
|
use futures::future::*;
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use futures::stream;
|
use futures::stream;
|
||||||
use rss;
|
use rss;
|
||||||
@ -44,31 +45,31 @@ pub struct Feed {
|
|||||||
|
|
||||||
impl Feed {
|
impl Feed {
|
||||||
/// Index the contents of the RSS `Feed` into the database.
|
/// Index the contents of the RSS `Feed` into the database.
|
||||||
pub async fn index(self) -> Result<(), DataError> {
|
pub fn index(self) -> impl Future<Item = (), Error = DataError> + Send {
|
||||||
let show = self.parse_podcast().to_podcast()?;
|
ok(self.parse_podcast())
|
||||||
self.index_channel_items(show).await
|
.and_then(|pd| pd.to_podcast())
|
||||||
|
.and_then(move |pd| self.index_channel_items(pd))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_podcast(&self) -> NewShow {
|
fn parse_podcast(&self) -> NewShow {
|
||||||
NewShow::new(&self.channel, self.source_id)
|
NewShow::new(&self.channel, self.source_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn index_channel_items(self, pd: Show) -> Result<(), DataError> {
|
fn index_channel_items(self, pd: Show) -> impl Future<Item = (), Error = DataError> + Send {
|
||||||
let stream = stream::iter(self.channel.into_items());
|
let stream = stream::iter_ok::<_, DataError>(self.channel.into_items());
|
||||||
|
|
||||||
// Parse the episodes
|
// Parse the episodes
|
||||||
let episodes = stream.filter_map(move |item| {
|
let episodes = stream.filter_map(move |item| {
|
||||||
let ret = NewEpisodeMinimal::new(&item, pd.id())
|
NewEpisodeMinimal::new(&item, pd.id())
|
||||||
.and_then(move |ep| determine_ep_state(ep, &item));
|
.and_then(move |ep| determine_ep_state(ep, &item))
|
||||||
if ret.is_ok() {
|
.map_err(|err| error!("Failed to parse an episode: {}", err))
|
||||||
future::ready(Some(ret))
|
.ok()
|
||||||
} else {
|
|
||||||
future::ready(None)
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Filter errors, Index updatable episodes, return insertables.
|
// Filter errors, Index updatable episodes, return insertables.
|
||||||
let insertable_episodes = filter_episodes(episodes).await?;
|
filter_episodes(episodes)
|
||||||
batch_insert_episodes(&insertable_episodes);
|
// Batch index insertable episodes.
|
||||||
Ok(())
|
.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
|
where
|
||||||
S: Stream<Item = Result<IndexState<NewEpisode>, DataError>>,
|
S: Stream<Item = IndexState<NewEpisode>, Error = DataError> + Send + 'a,
|
||||||
{
|
{
|
||||||
stream
|
stream
|
||||||
.try_filter_map(|state| {
|
.filter_map(|state| match state {
|
||||||
async {
|
IndexState::NotChanged => None,
|
||||||
let result = match state {
|
// Update individual rows, and filter them
|
||||||
IndexState::NotChanged => None,
|
IndexState::Update((ref ep, rowid)) => {
|
||||||
// Update individual rows, and filter them
|
ep.update(rowid)
|
||||||
IndexState::Update((ref ep, rowid)) => {
|
.map_err(|err| error!("{}", err))
|
||||||
ep.update(rowid)
|
.map_err(|_| error!("Failed to index episode: {:?}.", ep.title()))
|
||||||
.map_err(|err| error!("{}", err))
|
.ok();
|
||||||
.map_err(|_| error!("Failed to index episode: {:?}.", ep.title()))
|
|
||||||
.ok();
|
None
|
||||||
None
|
|
||||||
}
|
|
||||||
IndexState::Index(s) => Some(s),
|
|
||||||
};
|
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
|
IndexState::Index(s) => Some(s),
|
||||||
})
|
})
|
||||||
// only Index is left, collect them for batch index
|
// only Index is left, collect them for batch index
|
||||||
.try_collect()
|
.collect()
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn batch_insert_episodes(episodes: &[NewEpisode]) {
|
fn batch_insert_episodes(episodes: &[NewEpisode]) {
|
||||||
@ -144,9 +142,8 @@ fn batch_insert_episodes(episodes: &[NewEpisode]) {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use futures::executor::block_on;
|
|
||||||
use rss::Channel;
|
use rss::Channel;
|
||||||
use tokio;
|
use tokio::{self, prelude::*};
|
||||||
|
|
||||||
use crate::database::truncate_db;
|
use crate::database::truncate_db;
|
||||||
use crate::dbqueries;
|
use crate::dbqueries;
|
||||||
@ -201,10 +198,9 @@ mod tests {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Index the channes
|
// Index the channels
|
||||||
let stream_ = stream::iter(feeds).for_each(|x| x.index().map(|x| x.unwrap()));
|
let stream_ = stream::iter_ok(feeds).for_each(|x| x.index());
|
||||||
let mut rt = tokio::runtime::Runtime::new()?;
|
tokio::run(stream_.map_err(|_| ()));
|
||||||
rt.block_on(stream_);
|
|
||||||
|
|
||||||
// Assert the index rows equal the controlled results
|
// Assert the index rows equal the controlled results
|
||||||
assert_eq!(dbqueries::get_sources()?.len(), 5);
|
assert_eq!(dbqueries::get_sources()?.len(), 5);
|
||||||
@ -236,7 +232,7 @@ mod tests {
|
|||||||
let feed = get_feed(path, 42);
|
let feed = get_feed(path, 42);
|
||||||
let pd = feed.parse_podcast().to_podcast()?;
|
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_podcasts()?.len(), 1);
|
||||||
assert_eq!(dbqueries::get_episodes()?.len(), 43);
|
assert_eq!(dbqueries::get_episodes()?.len(), 43);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@ -111,7 +111,7 @@ pub use crate::models::{Episode, EpisodeWidgetModel, Show, ShowCoverModel, Sourc
|
|||||||
/// It originates from the Tor-browser UA.
|
/// 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";
|
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)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub mod xdg_dirs {
|
pub mod xdg_dirs {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@ -137,7 +137,7 @@ pub mod xdg_dirs {
|
|||||||
PODCASTS_XDG.create_cache_directory(PODCASTS_XDG.get_cache_home()).unwrap()
|
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 = {
|
pub static ref DL_DIR: PathBuf = {
|
||||||
PODCASTS_XDG.create_data_directory("Downloads").unwrap()
|
PODCASTS_XDG.create_data_directory("Downloads").unwrap()
|
||||||
};
|
};
|
||||||
|
|||||||
@ -255,7 +255,7 @@ impl NewEpisodeMinimal {
|
|||||||
return Err(err);
|
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"));
|
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.
|
// Should treat information from the rss feeds as invalid by default.
|
||||||
// Case: "Thu, 05 Aug 2016 06:00:00 -0400" <-- Actually that was friday.
|
// Case: "Thu, 05 Aug 2016 06:00:00 -0400" <-- Actually that was friday.
|
||||||
@ -342,7 +342,7 @@ mod tests {
|
|||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
|
|
||||||
// TODO: Add tests for other feeds too.
|
// 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.
|
// Known prebuilt expected objects.
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
@ -578,6 +578,10 @@ mod tests {
|
|||||||
let ep = EXPECTED_MINIMAL_INTERCEPTED_1
|
let ep = EXPECTED_MINIMAL_INTERCEPTED_1
|
||||||
.clone()
|
.clone()
|
||||||
.into_new_episode(&item);
|
.into_new_episode(&item);
|
||||||
|
println!(
|
||||||
|
"EPISODE: {:#?}\nEXPECTED: {:#?}",
|
||||||
|
ep, *EXPECTED_INTERCEPTED_1
|
||||||
|
);
|
||||||
assert_eq!(ep, *EXPECTED_INTERCEPTED_1);
|
assert_eq!(ep, *EXPECTED_INTERCEPTED_1);
|
||||||
|
|
||||||
let item = channel.items().iter().nth(15).unwrap();
|
let item = channel.items().iter().nth(15).unwrap();
|
||||||
|
|||||||
@ -31,6 +31,9 @@ use http::header::{
|
|||||||
USER_AGENT as USER_AGENT_HEADER,
|
USER_AGENT as USER_AGENT_HEADER,
|
||||||
};
|
};
|
||||||
use http::{Request, Response, StatusCode, Uri};
|
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};
|
use base64::{encode_config, URL_SAFE};
|
||||||
|
|
||||||
@ -159,7 +162,7 @@ impl Source {
|
|||||||
let code = res.status();
|
let code = res.status();
|
||||||
|
|
||||||
if code.is_success() {
|
if code.is_success() {
|
||||||
// If request is successful save the etag
|
// If request is succesful save the etag
|
||||||
self = self.update_etag(&res)?
|
self = self.update_etag(&res)?
|
||||||
} else {
|
} else {
|
||||||
match code.as_u16() {
|
match code.as_u16() {
|
||||||
@ -189,7 +192,7 @@ impl Source {
|
|||||||
return Err(DataError::FeedRedirect(self));
|
return Err(DataError::FeedRedirect(self));
|
||||||
}
|
}
|
||||||
401 => return Err(self.make_err("401: Unauthorized.", code)),
|
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)),
|
404 => return Err(self.make_err("404: Not found.", code)),
|
||||||
408 => return Err(self.make_err("408: Request Timeout.", code)),
|
408 => return Err(self.make_err("408: Request Timeout.", code)),
|
||||||
410 => return Err(self.make_err("410: Feed was deleted..", code)),
|
410 => return Err(self.make_err("410: Feed was deleted..", code)),
|
||||||
@ -213,7 +216,10 @@ impl Source {
|
|||||||
self = self.save()?;
|
self = self.save()?;
|
||||||
|
|
||||||
debug!("Updated Source: {:#?}", &self);
|
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)
|
Ok(self)
|
||||||
@ -236,45 +242,41 @@ impl Source {
|
|||||||
///
|
///
|
||||||
/// Consumes `self` and Returns the corresponding `Feed` Object.
|
/// Consumes `self` and Returns the corresponding `Feed` Object.
|
||||||
// Refactor into TryInto once it lands on stable.
|
// Refactor into TryInto once it lands on stable.
|
||||||
pub async fn into_feed(
|
pub fn into_feed(
|
||||||
self,
|
self,
|
||||||
client: Client<HttpsConnector<HttpConnector>>,
|
client: Client<HttpsConnector<HttpConnector>>,
|
||||||
) -> Result<Feed, DataError> {
|
) -> impl Future<Item = Feed, Error = DataError> {
|
||||||
let id = self.id();
|
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?;
|
response
|
||||||
let chan = response_to_channel(resp).await?;
|
.and_then(response_to_channel)
|
||||||
|
.and_then(move |chan| {
|
||||||
FeedBuilder::default()
|
FeedBuilder::default()
|
||||||
.channel(chan)
|
.channel(chan)
|
||||||
.source_id(id)
|
.source_id(id)
|
||||||
.build()
|
.build()
|
||||||
.map_err(From::from)
|
.map_err(From::from)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_response(
|
fn request_constructor(
|
||||||
self,
|
self,
|
||||||
client: &Client<HttpsConnector<HttpConnector>>,
|
client: &Client<HttpsConnector<HttpConnector>>,
|
||||||
) -> Result<Response<Body>, DataError> {
|
) -> impl Future<Item = Response<Body>, Error = 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> {
|
|
||||||
// FIXME: remove unwrap somehow
|
// FIXME: remove unwrap somehow
|
||||||
let uri = Uri::from_str(self.uri()).unwrap();
|
let uri = Uri::from_str(self.uri()).unwrap();
|
||||||
let mut req = Request::get(uri).body(Body::empty()).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());
|
.insert(IF_MODIFIED_SINCE, HeaderValue::from_str(lmod).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = client.request(req).await?;
|
client
|
||||||
//.map_err(From::from)
|
.request(req)
|
||||||
self.match_status(res)
|
.map_err(From::from)
|
||||||
|
.and_then(move |res| self.match_status(res))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn response_to_channel(res: Response<Body>) -> Result<Channel, DataError> {
|
fn response_to_channel(
|
||||||
let chunk = hyper::body::to_bytes(res.into_body()).await?;
|
res: Response<Body>,
|
||||||
let buf = String::from_utf8_lossy(&chunk).into_owned();
|
) -> impl Future<Item = Channel, Error = DataError> + Send {
|
||||||
Channel::from_str(&buf).map_err(From::from)
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
|
use num_cpus;
|
||||||
use tokio;
|
use tokio;
|
||||||
|
|
||||||
use crate::database::truncate_db;
|
use crate::database::truncate_db;
|
||||||
@ -331,7 +341,7 @@ mod tests {
|
|||||||
truncate_db()?;
|
truncate_db()?;
|
||||||
|
|
||||||
let mut rt = tokio::runtime::Runtime::new()?;
|
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 client = Client::builder().build::<_, Body>(https);
|
||||||
|
|
||||||
let url = "https://web.archive.org/web/20180120083840if_/https://feeds.feedburner.\
|
let url = "https://web.archive.org/web/20180120083840if_/https://feeds.feedburner.\
|
||||||
|
|||||||
@ -43,7 +43,7 @@ use failure::Error;
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
// FIXME: Make it a Diesel model
|
// FIXME: Make it a Diesel model
|
||||||
/// Represents an `outline` xml element as per the `OPML` [specification][spec]
|
/// 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
|
/// [spec]: http://dev.opml.org/spec2.html
|
||||||
pub struct Opml {
|
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)
|
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.
|
/// them in opml format.
|
||||||
pub fn export_from_db<P: AsRef<Path>>(path: P, export_title: &str) -> Result<(), Error> {
|
pub fn export_from_db<P: AsRef<Path>>(path: P, export_title: &str) -> Result<(), Error> {
|
||||||
let file = File::create(path)?;
|
let file = File::create(path)?;
|
||||||
@ -163,7 +163,7 @@ pub fn export_to_file<F: Write>(file: F, export_title: &str) -> Result<(), Error
|
|||||||
Ok(())
|
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> {
|
pub fn extract_sources<R: Read>(reader: R) -> Result<HashSet<Opml>, reader::Error> {
|
||||||
let mut list = HashSet::new();
|
let mut list = HashSet::new();
|
||||||
let parser = reader::EventReader::new(reader);
|
let parser = reader::EventReader::new(reader);
|
||||||
@ -210,7 +210,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use futures::executor::block_on;
|
use futures::Future;
|
||||||
|
|
||||||
use crate::database::{truncate_db, TEMPDIR};
|
use crate::database::{truncate_db, TEMPDIR};
|
||||||
use crate::utils::get_feed;
|
use crate::utils::get_feed;
|
||||||
@ -318,7 +318,7 @@ mod tests {
|
|||||||
// Create and insert a Source into db
|
// Create and insert a Source into db
|
||||||
let s = Source::from_url(url).unwrap();
|
let s = Source::from_url(url).unwrap();
|
||||||
let feed = get_feed(path, s.id());
|
let feed = get_feed(path, s.id());
|
||||||
block_on(feed.index()).unwrap();
|
feed.index().wait().unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut map: HashSet<Opml> = HashSet::new();
|
let mut map: HashSet<Opml> = HashSet::new();
|
||||||
|
|||||||
@ -20,13 +20,15 @@
|
|||||||
// FIXME:
|
// FIXME:
|
||||||
//! Docs.
|
//! Docs.
|
||||||
|
|
||||||
use futures::{future::ok, prelude::*, stream::FuturesUnordered};
|
use futures::{future::ok, lazy, prelude::*, stream::FuturesUnordered};
|
||||||
use tokio;
|
use tokio;
|
||||||
|
|
||||||
use hyper::client::HttpConnector;
|
use hyper::client::HttpConnector;
|
||||||
use hyper::{Body, Client};
|
use hyper::{Body, Client};
|
||||||
use hyper_tls::HttpsConnector;
|
use hyper_tls::HttpsConnector;
|
||||||
|
|
||||||
|
use num_cpus;
|
||||||
|
|
||||||
use crate::errors::DataError;
|
use crate::errors::DataError;
|
||||||
use crate::Source;
|
use crate::Source;
|
||||||
|
|
||||||
@ -40,24 +42,29 @@ type HttpsClient = Client<HttpsConnector<HttpConnector>>;
|
|||||||
/// Messy temp diagram:
|
/// Messy temp diagram:
|
||||||
/// Source -> GET Request -> Update Etags -> Check Status -> Parse `xml/Rss` ->
|
/// Source -> GET Request -> Update Etags -> Check Status -> Parse `xml/Rss` ->
|
||||||
/// Convert `rss::Channel` into `Feed` -> Index Podcast -> Index Episodes.
|
/// Convert `rss::Channel` into `Feed` -> Index Podcast -> Index Episodes.
|
||||||
#[tokio::main]
|
pub fn pipeline<'a, S>(sources: S, client: HttpsClient) -> impl Future<Item = (), Error = ()> + 'a
|
||||||
pub async fn pipeline<'a, S>(mut sources: S, client: HttpsClient)
|
|
||||||
where
|
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 {
|
sources
|
||||||
if let Ok(source) = source_result {
|
.and_then(move |s| s.into_feed(client.clone()))
|
||||||
match source.into_feed(client.clone()).await {
|
.map_err(|err| {
|
||||||
Ok(feed) => {
|
match err {
|
||||||
let fut = feed.index().map_err(|err| error!("Error: {}", err));
|
// Avoid spamming the stderr when its not an eactual error
|
||||||
tokio::spawn(fut);
|
DataError::FeedNotModified(_) => (),
|
||||||
}
|
_ => error!("Error: {}", err),
|
||||||
// Avoid spamming the stderr when it's not an actual error
|
}
|
||||||
Err(DataError::FeedNotModified(_)) => (),
|
})
|
||||||
Err(err) => 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
|
/// Creates a tokio `reactor::Core`, and a `hyper::Client` and
|
||||||
@ -66,12 +73,13 @@ pub fn run<S>(sources: S) -> Result<(), DataError>
|
|||||||
where
|
where
|
||||||
S: IntoIterator<Item = Source>,
|
S: IntoIterator<Item = Source>,
|
||||||
{
|
{
|
||||||
let https = HttpsConnector::new();
|
let https = HttpsConnector::new(num_cpus::get())?;
|
||||||
let client = Client::builder().build::<_, Body>(https);
|
let client = Client::builder().build::<_, Body>(https);
|
||||||
|
|
||||||
let foo = sources.into_iter().map(ok::<_, _>);
|
let foo = sources.into_iter().map(ok::<_, _>);
|
||||||
let stream = FuturesUnordered::from_iter(foo);
|
let stream = FuturesUnordered::from_iter(foo);
|
||||||
pipeline(stream, client);
|
let p = pipeline(stream, client);
|
||||||
|
tokio::run(p);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -113,7 +121,7 @@ mod tests {
|
|||||||
run(sources)?;
|
run(sources)?;
|
||||||
|
|
||||||
let sources = dbqueries::get_sources()?;
|
let sources = dbqueries::get_sources()?;
|
||||||
// Run again to cover Unique constrains errors.
|
// Run again to cover Unique constrains erros.
|
||||||
run(sources)?;
|
run(sources)?;
|
||||||
|
|
||||||
// Assert the index rows equal the controlled results
|
// Assert the index rows equal the controlled results
|
||||||
|
|||||||
@ -56,7 +56,7 @@ fn download_checker() -> Result<(), DataError> {
|
|||||||
Ok(())
|
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> {
|
fn played_cleaner(cleanup_date: DateTime<Utc>) -> Result<(), DataError> {
|
||||||
let mut episodes = dbqueries::get_played_cleaner_episodes()?;
|
let mut episodes = dbqueries::get_played_cleaner_episodes()?;
|
||||||
let now_utc = cleanup_date.timestamp() as i32;
|
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();
|
let limit = ep.played().unwrap();
|
||||||
if now_utc > limit {
|
if now_utc > limit {
|
||||||
delete_local_content(ep)
|
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(|err| error!("Error: {}", err))
|
||||||
.map_err(|_| error!("Failed to delete file: {:?}", ep.local_uri()))
|
.map_err(|_| error!("Failed to delete file: {:?}", ep.local_uri()))
|
||||||
.ok();
|
.ok();
|
||||||
@ -144,11 +144,11 @@ pub fn get_download_folder(pd_title: &str) -> Result<String, DataError> {
|
|||||||
// TODO: Write Tests
|
// TODO: Write Tests
|
||||||
pub fn delete_show(pd: &Show) -> Result<(), DataError> {
|
pub fn delete_show(pd: &Show) -> Result<(), DataError> {
|
||||||
dbqueries::remove_feed(pd)?;
|
dbqueries::remove_feed(pd)?;
|
||||||
info!("{} was removed succesfully.", pd.title());
|
info!("{} was removed successfully.", pd.title());
|
||||||
|
|
||||||
let fold = get_download_folder(pd.title())?;
|
let fold = get_download_folder(pd.title())?;
|
||||||
fs::remove_dir_all(&fold)?;
|
fs::remove_dir_all(&fold)?;
|
||||||
info!("All the content at, {} was removed succesfully", &fold);
|
info!("All the content at, {} was removed successfully", &fold);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,14 +5,13 @@ version = "0.1.0"
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
error-chain = "0.12.1"
|
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
mime_guess = "2.0.1"
|
mime_guess = "2.0.3"
|
||||||
reqwest = "0.9.22"
|
reqwest = "0.9.22"
|
||||||
tempdir = "0.3.7"
|
tempdir = "0.3.7"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
failure = "0.1.6"
|
failure = "0.1.8"
|
||||||
failure_derive = "0.1.6"
|
failure_derive = "0.1.8"
|
||||||
|
|
||||||
[dependencies.podcasts-data]
|
[dependencies.podcasts-data]
|
||||||
path = "../podcasts-data"
|
path = "../podcasts-data"
|
||||||
|
|||||||
@ -54,7 +54,7 @@ pub trait DownloadProgress {
|
|||||||
// Sorry to those who will have to work with that code.
|
// Sorry to those who will have to work with that code.
|
||||||
// Would much rather use a crate,
|
// Would much rather use a crate,
|
||||||
// or bindings for a lib like youtube-dl(python),
|
// 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.
|
// TODO: Write unit-tests.
|
||||||
fn download_into(
|
fn download_into(
|
||||||
dir: &str,
|
dir: &str,
|
||||||
@ -64,7 +64,7 @@ fn download_into(
|
|||||||
) -> Result<String, DownloadError> {
|
) -> Result<String, DownloadError> {
|
||||||
info!("GET request to: {}", url);
|
info!("GET request to: {}", url);
|
||||||
// Haven't included the loop check as
|
// 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
|
// it has a loop back before giving correct url
|
||||||
let policy = RedirectPolicy::custom(|attempt| {
|
let policy = RedirectPolicy::custom(|attempt| {
|
||||||
info!("Redirect Attempt URL: {:?}", attempt.url());
|
info!("Redirect Attempt URL: {:?}", attempt.url());
|
||||||
@ -104,7 +104,7 @@ fn download_into(
|
|||||||
.and_then(|h| h.to_str().ok())
|
.and_then(|h| h.to_str().ok())
|
||||||
.map(From::from);
|
.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));
|
ct_type.map(|x| info!("Content Type: {}", x));
|
||||||
|
|
||||||
let ext = get_ext(ct_type).unwrap_or_else(|| String::from("unknown"));
|
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);
|
let target = format!("{}/{}.{}", dir, file_title, ext);
|
||||||
// Rename/move the tempfile into a permanent place upon success.
|
// Rename/move the tempfile into a permanent place upon success.
|
||||||
rename(out_file, &target)?;
|
rename(out_file, &target)?;
|
||||||
info!("Downloading of {} completed succesfully.", &target);
|
info!("Downloading of {} completed successfully.", &target);
|
||||||
Ok(target)
|
Ok(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,10 +219,10 @@ pub fn get_episode(
|
|||||||
progress,
|
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));
|
ep.set_local_uri(Some(&path));
|
||||||
|
|
||||||
// Over-write episode lenght
|
// Over-write episode length
|
||||||
let size = fs::metadata(path);
|
let size = fs::metadata(path);
|
||||||
if let Ok(s) = size {
|
if let Ok(s) = size {
|
||||||
ep.set_length(Some(s.len() as i32))
|
ep.set_length(Some(s.len() as i32))
|
||||||
|
|||||||
@ -5,47 +5,44 @@ version = "0.1.0"
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.9"
|
chrono = "0.4.11"
|
||||||
crossbeam-channel = "0.3.9"
|
crossbeam-channel = "0.3.9"
|
||||||
gdk = "0.12.0"
|
gdk = "0.12.1"
|
||||||
gdk-pixbuf = "0.8.0"
|
gdk-pixbuf = "0.8.0"
|
||||||
gobject-sys = "0.9.1"
|
gobject-sys = "0.9.1"
|
||||||
glib-sys = "0.9.1"
|
glib-sys = "0.9.1"
|
||||||
gst = { version = "0.15.2", package = "gstreamer" }
|
gst = { version = "0.15.7", package = "gstreamer" }
|
||||||
gst-player = { version = "0.15.0", package = "gstreamer-player" }
|
gst-player = { version = "0.15.5", package = "gstreamer-player" }
|
||||||
humansize = "1.1.0"
|
humansize = "1.1.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
loggerv = "0.7.2"
|
loggerv = "0.7.2"
|
||||||
open = "1.3.2"
|
open = "1.4.0"
|
||||||
rayon = "1.2.0"
|
rayon = "1.3.1"
|
||||||
url = "2.1.0"
|
url = "2.1.0"
|
||||||
failure = "0.1.6"
|
failure = "0.1.8"
|
||||||
failure_derive = "0.1.6"
|
failure_derive = "0.1.8"
|
||||||
fragile = "0.3.0"
|
fragile = "1.0.0"
|
||||||
regex = "1.3.1"
|
regex = "1.3.9"
|
||||||
reqwest = "0.9.22"
|
reqwest = "0.9.22"
|
||||||
serde_json = "1.0.41"
|
serde_json = "1.0.55"
|
||||||
# html2text = "0.1.8"
|
html2text = "0.1.12"
|
||||||
html2text = { git = "https://github.com/jugglerchris/rust-html2text" }
|
|
||||||
mpris-player = "0.5.0"
|
mpris-player = "0.5.0"
|
||||||
pango = "0.8.0"
|
pango = "0.8.0"
|
||||||
|
glib = "0.9.3"
|
||||||
|
|
||||||
[dependencies.gettext-rs]
|
[dependencies.gettext-rs]
|
||||||
git = "https://github.com/danigm/gettext-rs"
|
git = "https://github.com/danigm/gettext-rs"
|
||||||
branch = "no-gettext"
|
branch = "no-gettext"
|
||||||
features = ["gettext-system"]
|
features = ["gettext-system"]
|
||||||
|
|
||||||
[dependencies.glib]
|
|
||||||
version = "0.9.1"
|
|
||||||
|
|
||||||
[dependencies.gio]
|
[dependencies.gio]
|
||||||
features = ["v2_50"]
|
features = ["v2_50"]
|
||||||
version = "0.8.0"
|
version = "0.8.1"
|
||||||
|
|
||||||
[dependencies.gtk]
|
[dependencies.gtk]
|
||||||
features = ["v3_24"]
|
features = ["v3_24"]
|
||||||
version = "0.8.0"
|
version = "0.8.1"
|
||||||
|
|
||||||
[dependencies.libhandy]
|
[dependencies.libhandy]
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
|||||||
@ -2,22 +2,23 @@
|
|||||||
# Copyright (C) 2018 podcasts's COPYRIGHT HOLDER
|
# Copyright (C) 2018 podcasts's COPYRIGHT HOLDER
|
||||||
# This file is distributed under the same license as the podcasts package.
|
# This file is distributed under the same license as the podcasts package.
|
||||||
# arverne73 <arverne@wanadoo.fr>, 2018.
|
# 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 ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: podcasts master\n"
|
"Project-Id-Version: podcasts master\n"
|
||||||
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n"
|
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n"
|
||||||
"POT-Creation-Date: 2018-10-23 10:23+0000\n"
|
"POT-Creation-Date: 2020-06-03 20:13+0000\n"
|
||||||
"PO-Revision-Date: 2018-10-29 13:53+0100\n"
|
"PO-Revision-Date: 2020-06-19 17:15+0200\n"
|
||||||
"Last-Translator: Alexandre Franke <alexandre.franke@gmail.com>\n"
|
"Last-Translator: Thibault Martin <mail@thibaultmart.in>\n"
|
||||||
"Language-Team: French <gnomefr@traduc.org>\n"
|
"Language-Team: French <gnomefr@traduc.org>\n"
|
||||||
"Language: fr\n"
|
"Language: fr\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
|
||||||
"X-Generator: Poedit 2.0.6\n"
|
"X-Generator: Gtranslator 3.36.0\n"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:15
|
#: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:15
|
||||||
msgid "Top position of the last open main window"
|
msgid "Top position of the last open main window"
|
||||||
@ -49,8 +50,7 @@ msgstr "Indique s’il faut actualiser périodiquement le contenu"
|
|||||||
|
|
||||||
#: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:46
|
#: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:46
|
||||||
msgid "How many periods of time to wait between automatic refreshes"
|
msgid "How many periods of time to wait between automatic refreshes"
|
||||||
msgstr ""
|
msgstr "Nombre de délais à attendre entre les actualisations automatiques"
|
||||||
"Nombre de périodes de délais à attendre entre les actualisations automatiques"
|
|
||||||
|
|
||||||
#: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:50
|
#: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:50
|
||||||
msgid "What period of time to wait between automatic refreshes"
|
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.
|
#. 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.desktop.in.in:3
|
||||||
#: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4
|
#: 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/resources/gtk/headerbar.ui:158 podcasts-gtk/src/app.rs:353
|
||||||
#: podcasts-gtk/src/widgets/aboutdialog.rs:37
|
#: podcasts-gtk/src/widgets/aboutdialog.rs:56 podcasts-gtk/src/window.rs:82
|
||||||
msgid "Podcasts"
|
msgid "Podcasts"
|
||||||
msgstr "Podcasts"
|
msgstr "Podcasts"
|
||||||
|
|
||||||
@ -81,11 +81,6 @@ msgstr "Podcasts"
|
|||||||
msgid "Listen to your favorite podcasts, right from your desktop."
|
msgid "Listen to your favorite podcasts, right from your desktop."
|
||||||
msgstr "Écouter vos podcasts favoris directement sur votre bureau."
|
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!
|
#. 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
|
#: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13
|
||||||
msgid "Podcast;RSS;"
|
msgid "Podcast;RSS;"
|
||||||
@ -95,13 +90,13 @@ msgstr "Podcast;RSS;Baladodiffusion;Émissions;"
|
|||||||
msgid "Podcast app for GNOME"
|
msgid "Podcast app for GNOME"
|
||||||
msgstr "Application de podcast pour 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"
|
msgid "Jordan Petridis"
|
||||||
msgstr "Jordan Petridis"
|
msgstr "Jordan Petridis"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/empty_view.ui:46
|
#: podcasts-gtk/resources/gtk/empty_view.ui:46
|
||||||
msgid "This show does not have episodes yet"
|
msgid "This show does not have episodes yet"
|
||||||
msgstr "Cette émission n’a pas encore d’épisodes"
|
msgstr "Cette émission n’a pas encore d’épisode"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/empty_view.ui:62
|
#: podcasts-gtk/resources/gtk/empty_view.ui:62
|
||||||
msgid "If you think this is an error, please consider writing a bug report."
|
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"
|
msgid "Import shows from another device"
|
||||||
msgstr "Importer des émissions à partir d’un autre appareil"
|
msgstr "Importer des émissions à partir d’un autre appareil"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/episode_widget.ui:180
|
#: podcasts-gtk/resources/gtk/episode_widget.ui:79
|
||||||
|
msgid "You’ve 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…"
|
msgid "Calculating episode size…"
|
||||||
msgstr "Calcul de la taille de l’épisode…"
|
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"
|
msgid "Play this episode"
|
||||||
msgstr "Lire cet épisode"
|
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"
|
msgid "Cancel the download process"
|
||||||
msgstr "Annuler le téléchargement"
|
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"
|
msgid "Download this episode"
|
||||||
msgstr "Télécharger cet épisode"
|
msgstr "Télécharger cet épisode"
|
||||||
|
|
||||||
@ -143,20 +142,20 @@ msgstr "_Chercher de nouveaux épisodes"
|
|||||||
msgid "_Import Shows"
|
msgid "_Import Shows"
|
||||||
msgstr "_Importer les émissions"
|
msgstr "_Importer les émissions"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/hamburger.ui:22
|
#: podcasts-gtk/resources/gtk/hamburger.ui:16
|
||||||
msgid "_Preferences"
|
msgid "_Export Shows"
|
||||||
msgstr "_Préférences"
|
msgstr "_Exporter les émissions"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/hamburger.ui:27
|
#: podcasts-gtk/resources/gtk/hamburger.ui:22
|
||||||
msgid "_Keyboard Shortcuts"
|
msgid "_Keyboard Shortcuts"
|
||||||
msgstr "_Raccourcis clavier"
|
msgstr "_Raccourcis clavier"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/hamburger.ui:35
|
#: podcasts-gtk/resources/gtk/hamburger.ui:30
|
||||||
msgid "_About Podcasts"
|
msgid "_About Podcasts"
|
||||||
msgstr "À _propos de Podcasts"
|
msgstr "À _propos de Podcasts"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/headerbar.ui:35
|
#: 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"
|
msgid "Add a new feed"
|
||||||
msgstr "Ajouter un nouveau flux"
|
msgstr "Ajouter un nouveau flux"
|
||||||
|
|
||||||
@ -168,15 +167,11 @@ msgstr "Entrer l’adresse du flux à ajouter"
|
|||||||
msgid "Add"
|
msgid "Add"
|
||||||
msgstr "Ajouter"
|
msgstr "Ajouter"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/headerbar.ui:133
|
#: podcasts-gtk/resources/gtk/headerbar.ui:171
|
||||||
msgid "You are already subscribed to that feed!"
|
|
||||||
msgstr "Vous êtes déjà abonné à ce flux !"
|
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/headerbar.ui:169
|
|
||||||
msgid "Show Title"
|
msgid "Show Title"
|
||||||
msgstr "Titre de l’émission"
|
msgstr "Titre de l’émission"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/headerbar.ui:210
|
#: podcasts-gtk/resources/gtk/headerbar.ui:207
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "Retour"
|
msgstr "Retour"
|
||||||
|
|
||||||
@ -191,11 +186,6 @@ msgstr "Chercher de nouveaux épisodes"
|
|||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/help-overlay.ui:25
|
#: podcasts-gtk/resources/gtk/help-overlay.ui:25
|
||||||
msgctxt "shortcut window"
|
msgctxt "shortcut window"
|
||||||
msgid "Preferences"
|
|
||||||
msgstr "Préférences"
|
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/help-overlay.ui:32
|
|
||||||
msgctxt "shortcut window"
|
|
||||||
msgid "Quit the application"
|
msgid "Quit the application"
|
||||||
msgstr "Quitter l’application"
|
msgstr "Quitter l’application"
|
||||||
|
|
||||||
@ -227,71 +217,54 @@ msgstr "Une notification d’action intégrée à l’application"
|
|||||||
msgid "Undo"
|
msgid "Undo"
|
||||||
msgstr "Annuler"
|
msgstr "Annuler"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/player_toolbar.ui:72
|
#: podcasts-gtk/resources/gtk/player_dialog.ui:14
|
||||||
msgid "Rewind 10 seconds"
|
msgid "Now Playing"
|
||||||
msgstr "Reculer de 10 secondes"
|
msgstr "Lecture en cours"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/player_toolbar.ui:87
|
#: podcasts-gtk/resources/gtk/player_rate.ui:32
|
||||||
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
|
|
||||||
msgid "Change the playback speed"
|
msgid "Change the playback speed"
|
||||||
msgstr "Changer la vitesse de lecture"
|
msgstr "Changer la vitesse de lecture"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/player_toolbar.ui:300
|
#: podcasts-gtk/resources/gtk/player_rate.ui:47
|
||||||
#: podcasts-gtk/resources/gtk/player_toolbar.ui:380
|
#: podcasts-gtk/resources/gtk/player_rate.ui:122
|
||||||
msgid "1.00×"
|
msgid "1.00×"
|
||||||
msgstr "1,00×"
|
msgstr "1,00×"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/player_toolbar.ui:344
|
#: podcasts-gtk/resources/gtk/player_rate.ui:86
|
||||||
msgid "1.50×"
|
msgid "1.50×"
|
||||||
msgstr "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"
|
msgid "1.5 speed rate"
|
||||||
msgstr "Débit × 1,5"
|
msgstr "Débit × 1,5"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/player_toolbar.ui:362
|
#: podcasts-gtk/resources/gtk/player_rate.ui:104
|
||||||
msgid "1.25×"
|
msgid "1.25×"
|
||||||
msgstr "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"
|
msgid "1.25 speed rate"
|
||||||
msgstr "Débit × 1,25"
|
msgstr "Débit × 1,25"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/player_toolbar.ui:384
|
#: podcasts-gtk/resources/gtk/player_rate.ui:126
|
||||||
msgid "Normal speed"
|
msgid "Normal speed"
|
||||||
msgstr "Vitesse normale"
|
msgstr "Vitesse normale"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/prefs.ui:42
|
#: podcasts-gtk/resources/gtk/player_toolbar.ui:97
|
||||||
#: podcasts-gtk/resources/gtk/prefs.ui:295
|
msgid "Rewind 10 seconds"
|
||||||
msgid "Preferences"
|
msgstr "Reculer de 10 secondes"
|
||||||
msgstr "Préférences"
|
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/prefs.ui:76
|
#: podcasts-gtk/resources/gtk/player_toolbar.ui:112
|
||||||
msgid "Appearance"
|
msgid "Play"
|
||||||
msgstr "Apparence"
|
msgstr "Lire"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/prefs.ui:120
|
#: podcasts-gtk/resources/gtk/player_toolbar.ui:128
|
||||||
msgid "Dark Theme"
|
msgid "Pause"
|
||||||
msgstr "Thème sombre"
|
msgstr "Pause"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/prefs.ui:166
|
#: podcasts-gtk/resources/gtk/player_toolbar.ui:144
|
||||||
msgid "Delete played episodes"
|
msgid "Fast forward 10 seconds"
|
||||||
msgstr "Supprimer les épisodes lus"
|
msgstr "Avancer de 10 secondes"
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/prefs.ui:211
|
|
||||||
msgid "After"
|
|
||||||
msgstr "Après"
|
|
||||||
|
|
||||||
#: podcasts-gtk/resources/gtk/secondary_menu.ui:7
|
#: podcasts-gtk/resources/gtk/secondary_menu.ui:7
|
||||||
msgid "_Mark All Episodes as Played"
|
msgid "_Mark All Episodes as Played"
|
||||||
@ -317,91 +290,135 @@ msgstr "Marquer tout comme lu"
|
|||||||
msgid "Unsubscribe"
|
msgid "Unsubscribe"
|
||||||
msgstr "Se désabonner"
|
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"
|
msgid "Fetching new episodes"
|
||||||
msgstr "Récupérer les nouveaux épisodes"
|
msgstr "Récupérer les nouveaux épisodes"
|
||||||
|
|
||||||
#: podcasts-gtk/src/headerbar.rs:98
|
#: podcasts-gtk/src/stacks/content.rs:54
|
||||||
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
|
|
||||||
msgid "New"
|
msgid "New"
|
||||||
msgstr "Nouveau"
|
msgstr "Nouveau"
|
||||||
|
|
||||||
#: podcasts-gtk/src/stacks/content.rs:36
|
#: podcasts-gtk/src/stacks/content.rs:55
|
||||||
msgid "Shows"
|
msgid "Shows"
|
||||||
msgstr "Émissions"
|
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."
|
msgid "Select the file from which to you want to import shows."
|
||||||
msgstr "Sélectionnez le fichier à partir duquel importer les émissions."
|
msgstr "Sélectionnez le fichier à partir duquel importer les émissions."
|
||||||
|
|
||||||
#: podcasts-gtk/src/utils.rs:360
|
#: podcasts-gtk/src/utils.rs:401
|
||||||
msgid "_Import"
|
msgid "_Import"
|
||||||
msgstr "_Importer"
|
msgstr "_Importer"
|
||||||
|
|
||||||
#: podcasts-gtk/src/utils.rs:369
|
#: podcasts-gtk/src/utils.rs:410 podcasts-gtk/src/utils.rs:457
|
||||||
msgid "OPML file"
|
msgid "OPML file"
|
||||||
msgstr "Fichier OPML"
|
msgstr "Fichier OPML"
|
||||||
|
|
||||||
#: podcasts-gtk/src/utils.rs:386
|
#: podcasts-gtk/src/utils.rs:427
|
||||||
msgid "Failed to parse the imported file"
|
msgid "Failed to parse the imported file"
|
||||||
msgstr "Échec de l’analyse du fichier importé"
|
msgstr "Échec de l’analyse 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."
|
msgid "Selected file could not be accessed."
|
||||||
msgstr "Le fichier sélectionné n’est pas accessible."
|
msgstr "Le fichier sélectionné n’est 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 l’exportation des podcasts"
|
||||||
|
|
||||||
|
#: podcasts-gtk/src/widgets/aboutdialog.rs:51
|
||||||
msgid "Podcast Client for the GNOME Desktop."
|
msgid "Podcast Client for the GNOME Desktop."
|
||||||
msgstr "Client de podcast pour le bureau GNOME."
|
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"
|
msgid "Learn more about GNOME Podcasts"
|
||||||
msgstr "En apprendre plus sur 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"
|
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"
|
msgid "{} min"
|
||||||
msgstr "{} min"
|
msgstr "{} min"
|
||||||
|
|
||||||
#. sender.send(Action::ErrorNotification(format!("Player Error: {}", error)));
|
#. 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."
|
msgid "The media player was unable to execute an action."
|
||||||
msgstr "Le lecteur n’a pas pu réaliser l’action."
|
msgstr "Le lecteur n’a pas pu réaliser l’action."
|
||||||
|
|
||||||
#: podcasts-gtk/src/widgets/show_menu.rs:150
|
#: podcasts-gtk/src/widgets/show_menu.rs:174
|
||||||
msgid "Marked all episodes as listened"
|
msgid "Marked all episodes as listened"
|
||||||
msgstr "Marquer tous les épisodes comme écoutés"
|
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 {}"
|
msgid "Unsubscribed from {}"
|
||||||
msgstr "Se désabonner de {}"
|
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"
|
||||||
|
|||||||
@ -110,6 +110,9 @@ Tobias Bernard
|
|||||||
<style>
|
<style>
|
||||||
<class name="dim-label"/>
|
<class name="dim-label"/>
|
||||||
</style>
|
</style>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="font-features" value="tnum=1"/>
|
||||||
|
</attributes>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
@ -143,6 +146,9 @@ Tobias Bernard
|
|||||||
<style>
|
<style>
|
||||||
<class name="dim-label"/>
|
<class name="dim-label"/>
|
||||||
</style>
|
</style>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="font-features" value="tnum=1"/>
|
||||||
|
</attributes>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
@ -177,6 +183,9 @@ Tobias Bernard
|
|||||||
<style>
|
<style>
|
||||||
<class name="dim-label"/>
|
<class name="dim-label"/>
|
||||||
</style>
|
</style>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="font-features" value="tnum=1"/>
|
||||||
|
</attributes>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
@ -212,6 +221,9 @@ Tobias Bernard
|
|||||||
<style>
|
<style>
|
||||||
<class name="dim-label"/>
|
<class name="dim-label"/>
|
||||||
</style>
|
</style>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="font-features" value="tnum=1"/>
|
||||||
|
</attributes>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
|
|||||||
@ -132,6 +132,9 @@
|
|||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="width_chars">5</property>
|
<property name="width_chars">5</property>
|
||||||
<property name="xalign">1</property>
|
<property name="xalign">1</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="font-features" value="tnum=1"/>
|
||||||
|
</attributes>
|
||||||
<style>
|
<style>
|
||||||
<class name="dim-label"/>
|
<class name="dim-label"/>
|
||||||
<class name="small-label"/>
|
<class name="small-label"/>
|
||||||
@ -154,6 +157,9 @@
|
|||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="width_chars">5</property>
|
<property name="width_chars">5</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="font-features" value="tnum=1"/>
|
||||||
|
</attributes>
|
||||||
<style>
|
<style>
|
||||||
<class name="dim-label"/>
|
<class name="dim-label"/>
|
||||||
<class name="small-label"/>
|
<class name="small-label"/>
|
||||||
|
|||||||
@ -45,6 +45,9 @@ Tobias Bernard
|
|||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="label" translatable="yes">1.00×</property>
|
<property name="label" translatable="yes">1.00×</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="font-features" value="tnum=1"/>
|
||||||
|
</attributes>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
@ -75,23 +78,15 @@ Tobias Bernard
|
|||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="margin_start">6</property>
|
<property name="margin">6</property>
|
||||||
<property name="margin_end">6</property>
|
|
||||||
<property name="margin_top">6</property>
|
|
||||||
<property name="margin_bottom">6</property>
|
|
||||||
<property name="orientation">vertical</property>
|
<property name="orientation">vertical</property>
|
||||||
<property name="spacing">3</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkRadioButton" id="rate_3_00">
|
<object class="GtkModelButton" id="rate_4_00">
|
||||||
<property name="label" translatable="yes">3.00×</property>
|
<property name="text"><span font-features="tnum=1">4.00×</span></property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="tooltip_text" translatable="yes">4.0 speed rate</property>
|
||||||
<property name="receives_default">False</property>
|
<property name="role">radio</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>
|
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">True</property>
|
<property name="expand">True</property>
|
||||||
@ -100,16 +95,12 @@ Tobias Bernard
|
|||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkRadioButton" id="rate_2_75">
|
<object class="GtkModelButton" id="rate_3_75">
|
||||||
<property name="label" translatable="yes">2.75×</property>
|
<property name="text"><span font-features="tnum=1">3.75×</span></property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="tooltip_text" translatable="yes">3.75 speed rate</property>
|
||||||
<property name="receives_default">False</property>
|
<property name="role">radio</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>
|
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">True</property>
|
<property name="expand">True</property>
|
||||||
@ -118,16 +109,12 @@ Tobias Bernard
|
|||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkRadioButton" id="rate_2_50">
|
<object class="GtkModelButton" id="rate_3_50">
|
||||||
<property name="label" translatable="yes">2.50×</property>
|
<property name="text"><span font-features="tnum=1">3.50×</span></property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="tooltip_text" translatable="yes">3.5 speed rate</property>
|
||||||
<property name="receives_default">False</property>
|
<property name="role">radio</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>
|
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">True</property>
|
<property name="expand">True</property>
|
||||||
@ -136,16 +123,12 @@ Tobias Bernard
|
|||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkRadioButton" id="rate_2_25">
|
<object class="GtkModelButton" id="rate_3_25">
|
||||||
<property name="label" translatable="yes">2.25×</property>
|
<property name="text"><span font-features="tnum=1">3.25×</span></property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="tooltip_text" translatable="yes">3.25 speed rate</property>
|
||||||
<property name="receives_default">False</property>
|
<property name="role">radio</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>
|
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">True</property>
|
<property name="expand">True</property>
|
||||||
@ -154,16 +137,12 @@ Tobias Bernard
|
|||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkRadioButton" id="rate_2_00">
|
<object class="GtkModelButton" id="rate_3_00">
|
||||||
<property name="label" translatable="yes">2.00×</property>
|
<property name="text"><span font-features="tnum=1">3.00×</span></property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="tooltip_text" translatable="yes">3.0 speed rate</property>
|
||||||
<property name="receives_default">False</property>
|
<property name="role">radio</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>
|
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">True</property>
|
<property name="expand">True</property>
|
||||||
@ -172,16 +151,12 @@ Tobias Bernard
|
|||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkRadioButton" id="rate_1_75">
|
<object class="GtkModelButton" id="rate_2_75">
|
||||||
<property name="label" translatable="yes">1.75×</property>
|
<property name="text"><span font-features="tnum=1">2.75×</span></property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="tooltip_text" translatable="yes">2.75 speed rate</property>
|
||||||
<property name="receives_default">False</property>
|
<property name="role">radio</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>
|
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">True</property>
|
<property name="expand">True</property>
|
||||||
@ -190,16 +165,12 @@ Tobias Bernard
|
|||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkRadioButton" id="rate_1_50">
|
<object class="GtkModelButton" id="rate_2_50">
|
||||||
<property name="label" translatable="yes">1.50×</property>
|
<property name="text"><span font-features="tnum=1">2.50×</span></property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="tooltip_text" translatable="yes">2.5 speed rate</property>
|
||||||
<property name="receives_default">False</property>
|
<property name="role">radio</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>
|
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">True</property>
|
<property name="expand">True</property>
|
||||||
@ -208,16 +179,12 @@ Tobias Bernard
|
|||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkRadioButton" id="rate_1_25">
|
<object class="GtkModelButton" id="rate_2_25">
|
||||||
<property name="label" translatable="yes">1.25×</property>
|
<property name="text"><span font-features="tnum=1">2.25×</span></property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="tooltip_text" translatable="yes">2.25 speed rate</property>
|
||||||
<property name="receives_default">False</property>
|
<property name="role">radio</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>
|
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">True</property>
|
<property name="expand">True</property>
|
||||||
@ -226,16 +193,12 @@ Tobias Bernard
|
|||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkRadioButton" id="normal_rate">
|
<object class="GtkModelButton" id="rate_2_00">
|
||||||
<property name="label" translatable="yes">1.00×</property>
|
<property name="text"><span font-features="tnum=1">2.00×</span></property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="tooltip_text" translatable="yes">2.0 speed rate</property>
|
||||||
<property name="receives_default">False</property>
|
<property name="role">radio</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>
|
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">True</property>
|
<property name="expand">True</property>
|
||||||
@ -243,6 +206,63 @@ Tobias Bernard
|
|||||||
<property name="position">8</property>
|
<property name="position">8</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkModelButton" id="rate_1_75">
|
||||||
|
<property name="text"><span font-features="tnum=1">1.75×</span></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"><span font-features="tnum=1">1.50×</span></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"><span font-features="tnum=1">1.25×</span></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"><span font-features="tnum=1">1.00×</span></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>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|||||||
@ -263,6 +263,9 @@ Tobias Bernard
|
|||||||
<property name="halign">start</property>
|
<property name="halign">start</property>
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
<property name="label">0:00</property>
|
<property name="label">0:00</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="font-features" value="tnum=1"/>
|
||||||
|
</attributes>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
@ -291,6 +294,9 @@ Tobias Bernard
|
|||||||
<property name="halign">start</property>
|
<property name="halign">start</property>
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
<property name="label">0:00</property>
|
<property name="label">0:00</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="font-features" value="tnum=1"/>
|
||||||
|
</attributes>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
|
|||||||
@ -51,13 +51,13 @@
|
|||||||
<release version="0.4.5" date="2018-08-31">
|
<release version="0.4.5" date="2018-08-31">
|
||||||
<description>
|
<description>
|
||||||
<p>
|
<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>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Finish, Polish, Turkish, Spanish, German, Galician, Indonesian and Korean Translations were added.</li>
|
<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>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>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>
|
<li>Improved handling of HTTP Redirects</li>
|
||||||
</ul>
|
</ul>
|
||||||
</description>
|
</description>
|
||||||
|
|||||||
@ -54,12 +54,14 @@ podcasts_sources = files(
|
|||||||
'main.rs',
|
'main.rs',
|
||||||
'manager.rs',
|
'manager.rs',
|
||||||
'settings.rs',
|
'settings.rs',
|
||||||
'utils.rs'
|
'utils.rs',
|
||||||
|
'window.rs',
|
||||||
)
|
)
|
||||||
|
|
||||||
cargo_release = custom_target('cargo-build',
|
cargo_release = custom_target('cargo-build',
|
||||||
build_by_default: true,
|
build_by_default: true,
|
||||||
input: [
|
input: [
|
||||||
|
podcast_toml,
|
||||||
data_sources,
|
data_sources,
|
||||||
downloader_sources,
|
downloader_sources,
|
||||||
podcasts_sources,
|
podcasts_sources,
|
||||||
|
|||||||
@ -88,7 +88,7 @@ use crate::i18n::i18n;
|
|||||||
pub(crate) fn lazy_load<T, C, F, W, U>(
|
pub(crate) fn lazy_load<T, C, F, W, U>(
|
||||||
data: T,
|
data: T,
|
||||||
container: WeakRef<C>,
|
container: WeakRef<C>,
|
||||||
mut contructor: F,
|
mut constructor: F,
|
||||||
callback: U,
|
callback: U,
|
||||||
) where
|
) where
|
||||||
T: IntoIterator + 'static,
|
T: IntoIterator + 'static,
|
||||||
@ -104,7 +104,7 @@ pub(crate) fn lazy_load<T, C, F, W, U>(
|
|||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
let widget = contructor(x);
|
let widget = constructor(x);
|
||||||
container.add(&widget);
|
container.add(&widget);
|
||||||
widget.show();
|
widget.show();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
use glib;
|
use glib;
|
||||||
|
use glib::clone;
|
||||||
use gtk;
|
use gtk;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
|
|
||||||
@ -82,21 +83,17 @@ impl InAppNotification {
|
|||||||
let notif = InAppNotification::default();
|
let notif = InAppNotification::default();
|
||||||
notif.text.set_text(&text);
|
notif.text.set_text(&text);
|
||||||
|
|
||||||
let revealer_weak = notif.revealer.downgrade();
|
|
||||||
let mut time = 0;
|
let mut time = 0;
|
||||||
let id = timeout_add(250, move || {
|
let id = timeout_add(
|
||||||
if time < timer {
|
250,
|
||||||
time += 250;
|
clone!(@weak notif.revealer as revealer => @default-return glib::Continue(false), move || {
|
||||||
return glib::Continue(true);
|
if time < timer {
|
||||||
};
|
time += 250;
|
||||||
|
return glib::Continue(true);
|
||||||
let revealer = match revealer_weak.upgrade() {
|
};
|
||||||
Some(r) => r,
|
callback(revealer)
|
||||||
None => return glib::Continue(false),
|
}),
|
||||||
};
|
);
|
||||||
|
|
||||||
callback(revealer)
|
|
||||||
});
|
|
||||||
let id = Rc::new(RefCell::new(Some(id)));
|
let id = Rc::new(RefCell::new(Some(id)));
|
||||||
|
|
||||||
if undo_callback.is_some() {
|
if undo_callback.is_some() {
|
||||||
|
|||||||
@ -22,6 +22,8 @@ use failure::Error;
|
|||||||
|
|
||||||
use gtk::{self, prelude::*, Adjustment};
|
use gtk::{self, prelude::*, Adjustment};
|
||||||
|
|
||||||
|
use glib::clone;
|
||||||
|
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use libhandy::{Column, ColumnExt};
|
use libhandy::{Column, ColumnExt};
|
||||||
use podcasts_data::dbqueries;
|
use podcasts_data::dbqueries;
|
||||||
@ -135,17 +137,11 @@ impl HomeView {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let home_weak = Rc::downgrade(&home);
|
let callback = clone!(@weak home => move || {
|
||||||
let callback = move || {
|
|
||||||
let home = match home_weak.upgrade() {
|
|
||||||
Some(h) => h,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(ref v) = vadj {
|
if let Some(ref v) = vadj {
|
||||||
home.view.set_adjustments(None, Some(v))
|
home.view.set_adjustments(None, Some(v))
|
||||||
};
|
};
|
||||||
};
|
});
|
||||||
|
|
||||||
lazy_load_full(episodes, func, callback);
|
lazy_load_full(episodes, func, callback);
|
||||||
home.view.container().show_all();
|
home.view.container().show_all();
|
||||||
|
|||||||
@ -208,15 +208,19 @@ fn format_duration(seconds: u32) -> String {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct PlayerRate {
|
struct PlayerRate {
|
||||||
radio300: gtk::RadioButton,
|
radio400: gtk::ModelButton,
|
||||||
radio275: gtk::RadioButton,
|
radio375: gtk::ModelButton,
|
||||||
radio250: gtk::RadioButton,
|
radio350: gtk::ModelButton,
|
||||||
radio225: gtk::RadioButton,
|
radio325: gtk::ModelButton,
|
||||||
radio200: gtk::RadioButton,
|
radio300: gtk::ModelButton,
|
||||||
radio175: gtk::RadioButton,
|
radio275: gtk::ModelButton,
|
||||||
radio150: gtk::RadioButton,
|
radio250: gtk::ModelButton,
|
||||||
radio125: gtk::RadioButton,
|
radio225: gtk::ModelButton,
|
||||||
radio_normal: gtk::RadioButton,
|
radio200: gtk::ModelButton,
|
||||||
|
radio175: gtk::ModelButton,
|
||||||
|
radio150: gtk::ModelButton,
|
||||||
|
radio125: gtk::ModelButton,
|
||||||
|
radio_normal: gtk::ModelButton,
|
||||||
popover: gtk::Popover,
|
popover: gtk::Popover,
|
||||||
btn: gtk::MenuButton,
|
btn: gtk::MenuButton,
|
||||||
label: gtk::Label,
|
label: gtk::Label,
|
||||||
@ -226,20 +230,28 @@ impl PlayerRate {
|
|||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
let builder = gtk::Builder::new_from_resource("/org/gnome/Podcasts/gtk/player_rate.ui");
|
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 radio400: gtk::ModelButton = builder.get_object("rate_4_00").unwrap();
|
||||||
let radio275: gtk::RadioButton = builder.get_object("rate_2_75").unwrap();
|
let radio375: gtk::ModelButton = builder.get_object("rate_3_75").unwrap();
|
||||||
let radio250: gtk::RadioButton = builder.get_object("rate_2_50").unwrap();
|
let radio350: gtk::ModelButton = builder.get_object("rate_3_50").unwrap();
|
||||||
let radio225: gtk::RadioButton = builder.get_object("rate_2_25").unwrap();
|
let radio325: gtk::ModelButton = builder.get_object("rate_3_25").unwrap();
|
||||||
let radio200: gtk::RadioButton = builder.get_object("rate_2_00").unwrap();
|
let radio300: gtk::ModelButton = builder.get_object("rate_3_00").unwrap();
|
||||||
let radio175: gtk::RadioButton = builder.get_object("rate_1_75").unwrap();
|
let radio275: gtk::ModelButton = builder.get_object("rate_2_75").unwrap();
|
||||||
let radio150: gtk::RadioButton = builder.get_object("rate_1_50").unwrap();
|
let radio250: gtk::ModelButton = builder.get_object("rate_2_50").unwrap();
|
||||||
let radio125: gtk::RadioButton = builder.get_object("rate_1_25").unwrap();
|
let radio225: gtk::ModelButton = builder.get_object("rate_2_25").unwrap();
|
||||||
let radio_normal: gtk::RadioButton = builder.get_object("normal_rate").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 popover = builder.get_object("rate_popover").unwrap();
|
||||||
let btn = builder.get_object("rate_button").unwrap();
|
let btn = builder.get_object("rate_button").unwrap();
|
||||||
let label = builder.get_object("rate_label").unwrap();
|
let label = builder.get_object("rate_label").unwrap();
|
||||||
|
|
||||||
PlayerRate {
|
PlayerRate {
|
||||||
|
radio400,
|
||||||
|
radio375,
|
||||||
|
radio350,
|
||||||
|
radio325,
|
||||||
radio300,
|
radio300,
|
||||||
radio275,
|
radio275,
|
||||||
radio250,
|
radio250,
|
||||||
@ -257,45 +269,74 @@ impl PlayerRate {
|
|||||||
|
|
||||||
fn set_rate(&self, rate: f64) {
|
fn set_rate(&self, rate: f64) {
|
||||||
self.label.set_text(&format!("{:.2}×", rate));
|
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>) {
|
fn connect_signals(&self, widget: &Rc<PlayerWidget>) {
|
||||||
self.radio_normal
|
self.radio_normal
|
||||||
.connect_toggled(clone!(@weak widget => move |_| {
|
.connect_clicked(clone!(@weak widget => move |_| {
|
||||||
widget.on_rate_changed(1.00);
|
widget.on_rate_changed(1.00);
|
||||||
}));
|
}));
|
||||||
self.radio125
|
self.radio125
|
||||||
.connect_toggled(clone!(@weak widget => move |_| {
|
.connect_clicked(clone!(@weak widget => move |_| {
|
||||||
widget.on_rate_changed(1.25);
|
widget.on_rate_changed(1.25);
|
||||||
}));
|
}));
|
||||||
self.radio150
|
self.radio150
|
||||||
.connect_toggled(clone!(@weak widget => move |_| {
|
.connect_clicked(clone!(@weak widget => move |_| {
|
||||||
widget.on_rate_changed(1.50);
|
widget.on_rate_changed(1.50);
|
||||||
}));
|
}));
|
||||||
self.radio175
|
self.radio175
|
||||||
.connect_toggled(clone!(@weak widget => move |_| {
|
.connect_clicked(clone!(@weak widget => move |_| {
|
||||||
widget.on_rate_changed(1.75);
|
widget.on_rate_changed(1.75);
|
||||||
}));
|
}));
|
||||||
self.radio200
|
self.radio200
|
||||||
.connect_toggled(clone!(@weak widget => move |_| {
|
.connect_clicked(clone!(@weak widget => move |_| {
|
||||||
widget.on_rate_changed(2.00);
|
widget.on_rate_changed(2.00);
|
||||||
}));
|
}));
|
||||||
self.radio225
|
self.radio225
|
||||||
.connect_toggled(clone!(@weak widget => move |_| {
|
.connect_clicked(clone!(@weak widget => move |_| {
|
||||||
widget.on_rate_changed(2.25);
|
widget.on_rate_changed(2.25);
|
||||||
}));
|
}));
|
||||||
self.radio250
|
self.radio250
|
||||||
.connect_toggled(clone!(@weak widget => move |_| {
|
.connect_clicked(clone!(@weak widget => move |_| {
|
||||||
widget.on_rate_changed(2.50);
|
widget.on_rate_changed(2.50);
|
||||||
}));
|
}));
|
||||||
self.radio275
|
self.radio275
|
||||||
.connect_toggled(clone!(@weak widget => move |_| {
|
.connect_clicked(clone!(@weak widget => move |_| {
|
||||||
widget.on_rate_changed(2.75);
|
widget.on_rate_changed(2.75);
|
||||||
}));
|
}));
|
||||||
self.radio300
|
self.radio300
|
||||||
.connect_toggled(clone!(@weak widget => move |_| {
|
.connect_clicked(clone!(@weak widget => move |_| {
|
||||||
widget.on_rate_changed(3.00);
|
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);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
use glib::clone;
|
||||||
use gtk::{self, prelude::*, Adjustment, Align, SelectionMode};
|
use gtk::{self, prelude::*, Adjustment, Align, SelectionMode};
|
||||||
|
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
@ -83,16 +84,14 @@ impl ShowsView {
|
|||||||
fn populate_flowbox(shows: &Rc<ShowsView>, vadj: Option<Adjustment>) -> Result<(), Error> {
|
fn populate_flowbox(shows: &Rc<ShowsView>, vadj: Option<Adjustment>) -> Result<(), Error> {
|
||||||
let ignore = get_ignored_shows()?;
|
let ignore = get_ignored_shows()?;
|
||||||
let podcasts = dbqueries::get_podcasts_filter(&ignore)?;
|
let podcasts = dbqueries::get_podcasts_filter(&ignore)?;
|
||||||
let show_weak = Rc::downgrade(&shows);
|
|
||||||
let flowbox_weak = shows.flowbox.downgrade();
|
let flowbox_weak = shows.flowbox.downgrade();
|
||||||
|
|
||||||
let constructor = move |parent| ShowsChild::new(&parent).child;
|
let constructor = move |parent| ShowsChild::new(&parent).child;
|
||||||
let callback = move || {
|
let callback = clone!(@weak shows => move || {
|
||||||
match (show_weak.upgrade(), &vadj) {
|
if vadj.is_some() {
|
||||||
(Some(ref shows), Some(ref v)) => shows.view.set_adjustments(None, Some(v)),
|
shows.view.set_adjustments(None, vadj.as_ref())
|
||||||
_ => (),
|
}
|
||||||
};
|
});
|
||||||
};
|
|
||||||
|
|
||||||
lazy_load(podcasts, flowbox_weak, constructor, callback);
|
lazy_load(podcasts, flowbox_weak, constructor, callback);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@ -48,7 +48,7 @@ use crate::i18n::i18n;
|
|||||||
fn action<T, F>(thing: &T, name: &str, action: F)
|
fn action<T, F>(thing: &T, name: &str, action: F)
|
||||||
where
|
where
|
||||||
T: ActionMapExt,
|
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
|
// Create a stateless, parameterless action
|
||||||
let act = gio::SimpleAction::new(name, None);
|
let act = gio::SimpleAction::new(name, None);
|
||||||
@ -84,27 +84,17 @@ impl MainWindow {
|
|||||||
window.get_style_context().add_class("devel");
|
window.get_style_context().add_class("devel");
|
||||||
}
|
}
|
||||||
|
|
||||||
let weak_s = settings.downgrade();
|
window.connect_delete_event(
|
||||||
let weak_app = app.downgrade();
|
clone!(@strong settings, @weak app => @default-return Inhibit(false), move |window, _| {
|
||||||
window.connect_delete_event(move |window, _| {
|
info!("Saving window position");
|
||||||
let app = match weak_app.upgrade() {
|
WindowGeometry::from_window(&window).write(&settings);
|
||||||
Some(a) => a,
|
|
||||||
None => return Inhibit(false),
|
|
||||||
};
|
|
||||||
|
|
||||||
let settings = match weak_s.upgrade() {
|
info!("Application is exiting");
|
||||||
Some(s) => s,
|
let app = app.upcast::<gio::Application>();
|
||||||
None => return Inhibit(false),
|
app.quit();
|
||||||
};
|
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)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create a content instance
|
// Create a content instance
|
||||||
let content = Content::new(&sender).expect("Content initialization failed.");
|
let content = Content::new(&sender).expect("Content initialization failed.");
|
||||||
@ -128,8 +118,6 @@ impl MainWindow {
|
|||||||
|
|
||||||
wrap.add(&header.bottom_switcher);
|
wrap.add(&header.bottom_switcher);
|
||||||
|
|
||||||
let updater = RefCell::new(None);
|
|
||||||
|
|
||||||
window.add(&wrap);
|
window.add(&wrap);
|
||||||
|
|
||||||
// Retrieve the previous window position and size.
|
// Retrieve the previous window position and size.
|
||||||
@ -138,20 +126,19 @@ impl MainWindow {
|
|||||||
// Update the feeds right after the Window is initialized.
|
// Update the feeds right after the Window is initialized.
|
||||||
if settings.get_boolean("refresh-on-startup") {
|
if settings.get_boolean("refresh-on-startup") {
|
||||||
info!("Refresh on startup.");
|
info!("Refresh on startup.");
|
||||||
let s: Option<Vec<_>> = None;
|
utils::schedule_refresh(None, sender.clone());
|
||||||
utils::schedule_refresh(s, sender.clone());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let refresh_interval = settings::get_refresh_interval(&settings).num_seconds() as u32;
|
let refresh_interval = settings::get_refresh_interval(&settings).num_seconds() as u32;
|
||||||
info!("Auto-refresh every {:?} seconds.", refresh_interval);
|
info!("Auto-refresh every {:?} seconds.", refresh_interval);
|
||||||
|
|
||||||
let r_sender = sender.clone();
|
gtk::timeout_add_seconds(
|
||||||
gtk::timeout_add_seconds(refresh_interval, move || {
|
refresh_interval,
|
||||||
let s: Option<Vec<_>> = None;
|
clone!(@strong sender => move || {
|
||||||
utils::schedule_refresh(s, r_sender.clone());
|
utils::schedule_refresh(None, sender.clone());
|
||||||
|
glib::Continue(true)
|
||||||
glib::Continue(true)
|
}),
|
||||||
});
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
app: app.clone(),
|
app: app.clone(),
|
||||||
@ -161,7 +148,7 @@ impl MainWindow {
|
|||||||
content,
|
content,
|
||||||
player,
|
player,
|
||||||
updating: Cell::new(false),
|
updating: Cell::new(false),
|
||||||
updater,
|
updater: RefCell::new(None),
|
||||||
sender,
|
sender,
|
||||||
receiver,
|
receiver,
|
||||||
}
|
}
|
||||||
@ -176,41 +163,45 @@ impl MainWindow {
|
|||||||
// Create the `refresh` action.
|
// Create the `refresh` action.
|
||||||
//
|
//
|
||||||
// This will trigger a refresh of all the shows in the database.
|
// This will trigger a refresh of all the shows in the database.
|
||||||
action(&self.window, "refresh", clone!(@strong sender => move |_, _| {
|
action(&self.window, "refresh",
|
||||||
gtk::idle_add(clone!(@strong sender => move || {
|
clone!(@strong sender => move |_, _| {
|
||||||
let s: Option<Vec<_>> = None;
|
gtk::idle_add(
|
||||||
utils::schedule_refresh(s, sender.clone());
|
clone!(@strong sender => move || {
|
||||||
glib::Continue(false)
|
utils::schedule_refresh(None, sender.clone());
|
||||||
|
glib::Continue(false)
|
||||||
}));
|
}));
|
||||||
}));
|
}));
|
||||||
self.app.set_accels_for_action("win.refresh", &["<primary>r"]);
|
self.app.set_accels_for_action("win.refresh", &["<primary>r"]);
|
||||||
|
|
||||||
// Create the `OPML` import action
|
// Create the `OPML` import action
|
||||||
action(&self.window, "import", clone!(@strong sender, @weak self.window as win => move |_, _| {
|
action(&self.window, "import",
|
||||||
utils::on_import_clicked(&win, &sender);
|
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 |_, _| {
|
action(&self.window, "export",
|
||||||
utils::on_export_clicked(&win, &sender);
|
clone!(@strong sender, @weak self.window as window => move |_, _| {
|
||||||
|
utils::on_export_clicked(&window, &sender);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Create the action that shows a `gtk::AboutDialog`
|
// Create the action that shows a `gtk::AboutDialog`
|
||||||
action(&self.window, "about", clone!(@weak self.window as win => move |_, _| {
|
action(&self.window, "about",
|
||||||
about_dialog(&win);
|
clone!(@weak self.window as win => move |_, _| {
|
||||||
|
about_dialog(&win);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Create the quit action
|
// Create the quit action
|
||||||
let weak_instance = self.app.downgrade();
|
action(&self.window, "quit",
|
||||||
action(&self.window, "quit", move |_, _| {
|
clone!(@weak self.app as app => move |_, _| {
|
||||||
weak_instance.upgrade().map(|app| app.quit());
|
app.quit();
|
||||||
});
|
}));
|
||||||
self.app.set_accels_for_action("win.quit", &["<primary>q"]);
|
self.app.set_accels_for_action("win.quit", &["<primary>q"]);
|
||||||
|
|
||||||
// Create the menu action
|
// Create the menu actions
|
||||||
let header = Rc::downgrade(&self.headerbar);
|
action(&self.window, "menu",
|
||||||
action(&self.window, "menu", move |_, _| {
|
clone!(@weak self.headerbar as headerbar => move |_, _| {
|
||||||
header.upgrade().map(|h| h.open_menu());
|
headerbar.open_menu();
|
||||||
});
|
}));
|
||||||
// Bind the hamburger menu button to `F10`
|
// Bind the hamburger menu button to `F10`
|
||||||
self.app.set_accels_for_action("win.menu", &["F10"]);
|
self.app.set_accels_for_action("win.menu", &["F10"]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,16 +6,14 @@ set -x
|
|||||||
|
|
||||||
# $1 Passed by meson and should be the builddir
|
# $1 Passed by meson and should be the builddir
|
||||||
export CARGO_TARGET_DIR="$1/target/"
|
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
|
# If this is run inside a flatpak envrironment, append the export the rustc
|
||||||
# sdk-extension binaries to the path
|
# sdk-extension binaries to the path
|
||||||
if [ -f "/.flatpak-info" ]
|
if [ -f "/.flatpak-info" ]
|
||||||
then
|
then
|
||||||
export PATH="$PATH:/usr/lib/sdk/rust-stable/bin"
|
export PATH="$PATH:/usr/lib/sdk/rust-stable/bin"
|
||||||
# This assumes its run inside a Builder terminal
|
|
||||||
export CARGO_TARGET_DIR="$BUILDDIR/target/"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export CARGO_HOME="$CARGO_TARGET_DIR/cargo-home"
|
cargo fetch --locked
|
||||||
|
cargo test --all-features --offline -- --test-threads=1 --nocapture
|
||||||
cargo test -j 1 -- --test-threads=1 --nocapture
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user