4 Commits
8 changed files with 198 additions and 80 deletions
+20
View File
@@ -0,0 +1,20 @@
Detailed description of the issue. Put as much information as you can, potentially
with images showing the issue.
Steps to reproduce:
1. Open Hammond
2. Do an action
3. ...
## Design Tasks
* [ ] design tasks
## Development Tasks
* [ ] development tasks
## QA Tasks
* [ ] qa (quality assurance) tasks
+17
View File
@@ -0,0 +1,17 @@
Detailed description of the feature. Put as much information as you can.
Proposed Mockups:
(Add mockups of the proposed feature)
## Design Tasks
* [ ] design tasks
## Development Tasks
* [ ] development tasks
## QA Tasks
* [ ] qa (quality assurance) tasks
+46
View File
@@ -1,3 +1,5 @@
> Adapted from Gnome-TODO
> https://gitlab.gnome.org/GNOME/gnome-todo/blob/582f9a57b84f92dc629b2042b887188878578cdb/CONTRIBUTING.md
## Contributing ## Contributing
Contributing Contributing
@@ -22,6 +24,50 @@ It is recommended to add a pre-commit hook to run cargo test and cargo fmt
cargo test --all && cargo fmt --all -- --write-mode=diff cargo test --all && cargo fmt --all -- --write-mode=diff
``` ```
# Issues, issues and more issues!
There are many ways you can contribute to Hammond, and all of them involve creating issues
in [Hammond issue tracker](https://gitlab.gnome.org/alatiera/Hammond/issues). This is the
entry point for your contribution.
To create an effective and high quality ticket, try to put the following information on your
ticket:
1. A detailed description of the issue or feature request
- For issues, please add the necessary steps to reproduce the issue.
- For feature requests, add a detailed description of your proposal.
2. A checklist of Development tasks
3. A checklist of Design tasks
4. A checklist of QA tasks
## Issue template
```
[Title of the issue or feature request]
Detailed description of the issue. Put as much information as you can, potentially
with images showing the issue or mockups of the proposed feature.
If it's an issue, add the steps to reproduce like this:
Steps to reproduce:
1. Open Hammond
2. Do an Action
3. ...
## Design Tasks
* [ ] design tasks
## Development Tasks
* [ ] development tasks
## QA Tasks
* [ ] qa (quality assurance) tasks
```
## Pull Request Process ## Pull Request Process
1. Ensure your code compiles. Run `make` before creating the pull request. 1. Ensure your code compiles. Run `make` before creating the pull request.
+1 -1
View File
@@ -3,7 +3,7 @@
**General:** **General:**
- [x] Add CONTRIBUTING.md - [x] Add CONTRIBUTING.md
- [ ] Add Issues and Pull Request templates - [x] Add Issues and Pull Request templates
- [ ] Write docs - [ ] Write docs
+88 -77
View File
@@ -14,68 +14,77 @@ use hammond_data::index_feed::Database;
use hammond_data::models::{Episode, Podcast}; use hammond_data::models::{Episode, Podcast};
use hammond_data::{DL_DIR, HAMMOND_CACHE}; use hammond_data::{DL_DIR, HAMMOND_CACHE};
// TODO: Replace path that are of type &str with std::path.
// TODO: Have a convention/document absolute/relative paths, if they should end with / or not.
// Adapted from https://github.com/mattgathu/rget . // Adapted from https://github.com/mattgathu/rget .
// I never wanted to write a custom downloader. // I never wanted to write a custom downloader.
// 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 cant seem to find one.
// TODO: Write unit-tests.
fn download_into(dir: &str, file_title: &str, url: &str) -> Result<String> { fn download_into(dir: &str, file_title: &str, url: &str) -> Result<String> {
info!("GET request to: {}", url); info!("GET request to: {}", url);
let client = reqwest::Client::builder().referer(false).build()?; let client = reqwest::Client::builder().referer(false).build()?;
let mut resp = client.get(url).send()?; let mut resp = client.get(url).send()?;
info!("Status Resp: {}", resp.status()); info!("Status Resp: {}", resp.status());
if resp.status().is_success() { if !resp.status().is_success() {
let headers = resp.headers().clone(); // TODO: Return an error instead of panicking.
panic!("Bad request response.");
}
let ct_len = headers.get::<ContentLength>().map(|ct_len| **ct_len); let headers = resp.headers().clone();
let ct_type = headers.get::<ContentType>();
ct_len.map(|x| info!("File Lenght: {}", x));
ct_type.map(|x| info!("Content Type: {}", x));
// This could be prettier. let ct_len = headers.get::<ContentLength>().map(|ct_len| **ct_len);
let ext = if let Some(t) = ct_type { let ct_type = headers.get::<ContentType>();
let mime = mime_guess::get_extensions(t.type_().as_ref(), t.subtype().as_ref()); ct_len.map(|x| info!("File Lenght: {}", x));
if let Some(m) = mime { ct_type.map(|x| info!("Content Type: {}", x));
if m.contains(&t.subtype().as_ref()) {
t.subtype().as_ref().to_string() // This could be prettier.
} else { // Determine the file extension from the http content-type header.
m.first().unwrap().to_string() let ext = if let Some(t) = ct_type {
} let mime = mime_guess::get_extensions(t.type_().as_ref(), t.subtype().as_ref());
if let Some(m) = mime {
if m.contains(&t.subtype().as_ref()) {
t.subtype().as_ref().to_string()
} else { } else {
error!("Unkown mime type. {}", t); m.first().unwrap().to_string()
"unkown".to_string()
} }
} else { } else {
error!("Unkown mime type."); error!("Unkown mime type. {}", t);
"unkown".to_string() "unkown".to_string()
}; }
info!("Extension: {}", ext); } else {
error!("Unkown mime type.");
"unkown".to_string()
};
info!("Extension: {}", ext);
// Construct a temp file to save desired content. // Construct a temp file to save desired content.
let tempdir = TempDir::new_in(dir, "")?; let tempdir = TempDir::new_in(dir, "")?;
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let out_file = format!( let out_file = format!(
"{}/{}.part", "{}/{}.part",
tempdir.path().to_str().unwrap(), tempdir.path().to_str().unwrap(),
rng.gen::<usize>() rng.gen::<usize>()
); );
save_io(&out_file, &mut resp, ct_len)?; // Save requested content into the file.
save_io(&out_file, &mut resp, ct_len)?;
// Construct the desired path. // Construct the desired path.
let target = format!("{}/{}.{}", dir, file_title, ext); let target = format!("{}/{}.{}", dir, file_title, ext);
// Rename/move the tempfile into a permanent place. // 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 succesfully.", &target);
return Ok(target); Ok(target)
}
// Ok(String::from(""))
panic!("Bad request response.");
} }
// TODO: Write unit-tests.
/// Handles the I/O of fetching a remote file and saving into a Buffer and A File.
fn save_io(file: &str, resp: &mut reqwest::Response, content_lenght: Option<u64>) -> Result<()> { fn save_io(file: &str, resp: &mut reqwest::Response, content_lenght: Option<u64>) -> Result<()> {
info!("Downloading into: {}", file); info!("Downloading into: {}", file);
let chunk_size = match content_lenght { let chunk_size = match content_lenght {
@@ -116,6 +125,7 @@ pub fn get_episode(connection: &Database, ep: &mut Episode, download_folder: &st
return Ok(()); return Ok(());
} }
// If the path is not valid, then set it to None.
ep.set_local_uri(None); ep.set_local_uri(None);
ep.save(connection)?; ep.save(connection)?;
}; };
@@ -134,50 +144,51 @@ pub fn get_episode(connection: &Database, ep: &mut Episode, download_folder: &st
} }
pub fn cache_image(pd: &Podcast) -> Option<String> { pub fn cache_image(pd: &Podcast) -> Option<String> {
if pd.image_uri().is_some() { if pd.image_uri().is_none() {
let url = pd.image_uri().unwrap().to_owned(); return None;
if url == "" { }
return None;
}
let download_fold = format!( let url = pd.image_uri().unwrap().to_owned();
"{}{}", if url == "" {
HAMMOND_CACHE.to_str().unwrap(), return None;
pd.title().to_owned() }
);
// Hacky way let download_fold = format!(
// TODO: make it so it returns the first cover.* file encountered. "{}{}",
let png = format!("{}/cover.png", download_fold); HAMMOND_CACHE.to_str().unwrap(),
let jpg = format!("{}/cover.jpg", download_fold); pd.title().to_owned()
let jpe = format!("{}/cover.jpe", download_fold); );
let jpeg = format!("{}/cover.jpeg", download_fold);
if Path::new(&png).exists() {
return Some(png);
} else if Path::new(&jpe).exists() {
return Some(jpe);
} else if Path::new(&jpg).exists() {
return Some(jpg);
} else if Path::new(&jpeg).exists() {
return Some(jpeg);
};
DirBuilder::new() // Hacky way
.recursive(true) // TODO: make it so it returns the first cover.* file encountered.
.create(&download_fold) let png = format!("{}/cover.png", download_fold);
.unwrap(); let jpg = format!("{}/cover.jpg", download_fold);
let jpe = format!("{}/cover.jpe", download_fold);
let jpeg = format!("{}/cover.jpeg", download_fold);
if Path::new(&png).exists() {
return Some(png);
} else if Path::new(&jpe).exists() {
return Some(jpe);
} else if Path::new(&jpg).exists() {
return Some(jpg);
} else if Path::new(&jpeg).exists() {
return Some(jpeg);
};
let dlpath = download_into(&download_fold, "cover", &url); DirBuilder::new()
if let Ok(path) = dlpath { .recursive(true)
info!("Cached img into: {}", &path); .create(&download_fold)
return Some(path); .unwrap();
} else {
error!("Failed to get feed image."); let dlpath = download_into(&download_fold, "cover", &url);
error!("Error: {}", dlpath.unwrap_err()); if let Ok(path) = dlpath {
return None; info!("Cached img into: {}", &path);
}; Some(path)
} else {
error!("Failed to get feed image.");
error!("Error: {}", dlpath.unwrap_err());
None
} }
None
} }
#[cfg(test)] #[cfg(test)]
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop">
<id>org.gnome.Hammond</id>
<name>Hammond</name>
<project_license>GPL-3.0</project_license>
<metadata_license>CC0-1.0</metadata_license>
<developer_name>Daniel García Moreno</developer_name>
<summary>Gtk+ Matrix.org client</summary>
<url type="homepage">https://gitlab.gnome.org/alatiera/Hammond</url>
<description>
Hammond is a Fast, Safe and Reliable Gtk+ Podcast client written in Rust
</description>
<screenshots>
<screenshot>
<image type="source">https://gitlab.gnome.org/alatiera/Hammond/raw/master/assets/podcasts_view.png</image>
<image type="source">https://gitlab.gnome.org/alatiera/Hammond/raw/master/assets/podcast_widget.png</image>
</screenshot>
</screenshots>
<releases>
<release version="0.1.1" date="2017-11-13"/>
</releases>
<update_contact>jordanpetridis@protonmail.com</update_contact>
</component>
+3 -2
View File
@@ -3,7 +3,7 @@
project( project(
'hammond', 'rust', 'hammond', 'rust',
version: '0.1.0', version: '0.1.1',
license: 'GPLv3', license: 'GPLv3',
) )
@@ -16,7 +16,8 @@ hammond_version_micro = version_array[2].to_int()
hammond_prefix = get_option('prefix') hammond_prefix = get_option('prefix')
hammond_bindir = join_paths(hammond_prefix, get_option('bindir')) hammond_bindir = join_paths(hammond_prefix, get_option('bindir'))
install_data('assets/org.gnome.Hammond.desktop', install_dir : get_option('datadir') + '/applications') install_data('hammond-gtk/resources/org.gnome.Hammond.desktop', install_dir : get_option('datadir') + '/applications')
install_data('hammond-gtk/resources/org.gnome.Hammond.appdata.xml', install_dir : get_option('datadir') + '/appdata')
cargo = find_program('cargo', required: false) cargo = find_program('cargo', required: false)
gresource = find_program('glib-compile-resources', required: false) gresource = find_program('glib-compile-resources', required: false)