Skip to content

Commit

Permalink
Merge pull request #11 from Azorlogh/feature/search
Browse files Browse the repository at this point in the history
Track search
  • Loading branch information
Azorlogh authored Oct 29, 2022
2 parents 1d61ae1 + cdc065d commit 63a7d7e
Show file tree
Hide file tree
Showing 23 changed files with 900 additions and 156 deletions.
279 changes: 271 additions & 8 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions tf-desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ directories = "4.0.1"
souvlaki = "0.5.1"
crossbeam-channel = "0.5.6"
palette = "0.6.1"
ureq = "2.5.0"
regex = "1.6.0"
serde_json = "1.0.86"
percent-encoding = "2.2.0"
serde = "1.0.145"
image = "0.24.4"
Binary file added tf-desktop/assets/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions tf-desktop/assets/settings.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion tf-desktop/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub const QUERY_PLAY: Selector = Selector::new("query.play");

pub const UI_TRACK_EDIT_OPEN: Selector<Uuid> = Selector::new("ui.track-edit.open");
pub const UI_TRACK_EDIT_CLOSE: Selector = Selector::new("ui.track-edit.close");
pub const UI_TRACK_ADD_OPEN: Selector<String> = Selector::new("ui.track-add.open");
pub const UI_TRACK_ADD_OPEN: Selector<NewTrack> = Selector::new("ui.track-add.open");
pub const UI_TRACK_ADD_CLOSE: Selector = Selector::new("ui.track-add.close");

pub const TAG_SEARCH: Selector<String> = Selector::new("tag.search");
Expand All @@ -18,3 +18,6 @@ pub const TAG_SEARCH: Selector<String> = Selector::new("tag.search");
pub const TRACK_ADD: Selector<NewTrack> = Selector::new("track.add");
pub const TRACK_DELETE: Selector<Uuid> = Selector::new("track.delete");
pub const TRACK_EDIT_TAG: Selector<(Uuid, String, f32)> = Selector::new("track.edit-tag");

// Plugin interactions
pub const PLUGIN_SEARCH_TRACK: Selector<String> = Selector::new("plugin.search-track");
37 changes: 28 additions & 9 deletions tf-desktop/src/delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,28 @@ use std::rc::Rc;

use anyhow::Result;
use druid::AppDelegate;
use tracing::error;
use tracing::{error, warn};
use uuid::Uuid;

use crate::{
command,
controller::playback,
plugins::{self, Plugin},
state::{NewTrack, TrackEdit},
State,
};

pub struct Delegate {
db: tf_db::Client,
plugins: Vec<Box<dyn Plugin>>,
}

impl Delegate {
pub fn new(db: tf_db::Client) -> Result<Self> {
Ok(Self { db })
Ok(Self {
db,
plugins: vec![Box::new(plugins::Soundcloud::new()?)],
})
}

fn apply_track_edit(&mut self, edit: TrackEdit) -> Result<()> {
Expand Down Expand Up @@ -100,12 +105,8 @@ impl AppDelegate<State> for Delegate {
druid::Handled::Yes
}
_ if cmd.is(command::UI_TRACK_ADD_OPEN) => {
let source = cmd.get::<String>(command::UI_TRACK_ADD_OPEN).unwrap();
data.new_track = Some(NewTrack {
source: source.clone(),
title: String::new(),
artist: String::new(),
});
let new_track = cmd.get_unchecked::<_>(command::UI_TRACK_ADD_OPEN);
data.new_track = Some(new_track.clone());
druid::Handled::Yes
}
_ if cmd.is(command::UI_TRACK_ADD_CLOSE) => {
Expand All @@ -124,7 +125,7 @@ impl AppDelegate<State> for Delegate {
Ok(id) => {
let track = self.db.get_track(id).unwrap();
data.tracks.push_back(track.into());
data.new_track_url = String::new();
data.new_track_search = String::new();
data.new_track = None;
}
Err(e) => error!("{:?}", e),
Expand Down Expand Up @@ -153,6 +154,24 @@ impl AppDelegate<State> for Delegate {
}
druid::Handled::Yes
}
_ if cmd.is(command::PLUGIN_SEARCH_TRACK) => {
let q = cmd.get_unchecked::<String>(command::PLUGIN_SEARCH_TRACK);
data.track_search_results.tracks.clear();
for plugin in &self.plugins {
match plugin.search(q) {
Ok(r) => {
data.track_search_results.tracks.extend(r);
}
Err(e) => warn!("{:?}", e),
}
}
druid::Handled::Yes
}
// _ if cmd.is(command::TRACK_SUGGESTION_SELECT) => {
// let search_result =
// cmd.get_unchecked::<SearchResult>(command::TRACK_SUGGESTION_SELECT);

// }
_ => druid::Handled::No,
}
}
Expand Down
1 change: 1 addition & 0 deletions tf-desktop/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use anyhow::{anyhow, Result};
use druid::{AppLauncher, WindowDesc};

mod command;
mod plugins;
mod state;
mod ui;
pub use state::State;
Expand Down
20 changes: 20 additions & 0 deletions tf-desktop/src/plugins/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use std::rc::Rc;

use anyhow::Result;

mod soundcloud;
use druid::{Data, ImageBuf, Lens};
pub use soundcloud::Soundcloud;
use url::Url;

pub trait Plugin {
fn search(&self, query: &str) -> Result<Vec<SearchResult>>;
}

#[derive(Debug, Clone, Data, Lens)]
pub struct SearchResult {
pub url: Rc<Url>,
pub artist: String,
pub title: String,
pub artwork: Option<ImageBuf>,
}
26 changes: 26 additions & 0 deletions tf-desktop/src/plugins/soundcloud/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use serde::{Deserialize, Serialize};
use url::Url;

#[derive(Deserialize, Serialize)]
pub struct SearchResponse {
pub collection: Vec<SearchResult>,
}

#[derive(Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "kind")]
pub enum SearchResult {
Track {
permalink_url: Url,
user: User,
title: String,
artwork_url: Option<Url>,
},
User,
Playlist,
}

#[derive(Deserialize, Serialize)]
pub struct User {
pub username: String,
}
99 changes: 99 additions & 0 deletions tf-desktop/src/plugins/soundcloud/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use std::rc::Rc;

use anyhow::{anyhow, Result};
use druid::piet::ImageFormat;
use percent_encoding::{AsciiSet, CONTROLS};
use tracing::debug;

use super::Plugin;

mod api;

pub struct Soundcloud {
client_id: String,
}

impl Soundcloud {
pub fn new() -> Result<Self> {
// grap soundcloud home page
let body = ureq::get("https://soundcloud.com").call()?.into_string()?;

// grab location of a specific script
let re = regex::Regex::new(r#"<script crossorigin src="([^\n]*)">"#).unwrap();
let magic_script = &re
.captures_iter(&body)
.last()
.ok_or(anyhow!("could not find url to the magic script"))?[1];

// grab that script
let body = ureq::get(magic_script).call()?.into_string()?;
let re = regex::Regex::new(r#"client_id:"([^"]*)""#).unwrap();

let client_id = re
.captures(&body)
.ok_or(anyhow!("missing client id in script"))?[1]
.to_owned();

debug!("soundcloud client id: {:?}", client_id);

Ok(Self { client_id })
}
}

const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');

impl Plugin for Soundcloud {
fn search(&self, query: &str) -> Result<Vec<super::SearchResult>> {
let query_enc = percent_encoding::utf8_percent_encode(query, FRAGMENT);
let response_json = ureq::get(&format!(
"https://api-v2.soundcloud.com/search?client_id={}&q={}&limit=10&offset=0&linked_partitioning=1&app_version=1665395834&app_locale=en",
self.client_id, query_enc
))
.call()?
.into_string()?;
let response: api::SearchResponse = serde_json::from_str(&response_json)?;

Ok(response
.collection
.into_iter()
.filter_map(|res| match res {
api::SearchResult::Track {
permalink_url,
user,
title,
artwork_url,
} => {
let artwork = artwork_url.and_then(|url| {
let mut artwork_buf = vec![];
ureq::get(url.as_str())
.call()
.ok()?
.into_reader()
.read_to_end(&mut artwork_buf)
.ok()?;
let artwork_image =
image::io::Reader::new(std::io::Cursor::new(artwork_buf))
.with_guessed_format()
.ok()?
.decode()
.ok()?
.to_rgb8();
Some(druid::ImageBuf::from_raw(
artwork_image.as_raw().as_slice(),
ImageFormat::Rgb,
artwork_image.width() as usize,
artwork_image.height() as usize,
))
});
Some(super::SearchResult {
url: Rc::new(permalink_url),
artist: user.username,
title,
artwork,
})
}
_ => None,
})
.collect())
}
}
1 change: 1 addition & 0 deletions tf-desktop/src/plugins/youtube/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// TODO
122 changes: 0 additions & 122 deletions tf-desktop/src/state.rs

This file was deleted.

Loading

0 comments on commit 63a7d7e

Please sign in to comment.