Skip to content

Commit

Permalink
Merge pull request #129 from technosf/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
technosf authored Oct 10, 2024
2 parents 7b15fe4 + c8dd608 commit 0f66403
Show file tree
Hide file tree
Showing 11 changed files with 472 additions and 121 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ build-aux
*~
.vscode
.buildconfig
.flatpak-builder
.flatpak-builder
code.sh
favicon.ico
47 changes: 47 additions & 0 deletions DEVELOP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# ![icon](docs/logo_01.png) Develop, Build and Contribute to Tuner [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0)


Discover and Listen to your favourite internet radio stations.

## Overview

Tuner is hosted on Github, and linked to Flathub so as releases are pushed Flathub will automatically update its repositary. It is writen in [Vala](https://vala.dev/), a C#/Java/JavaFX-like language with a self-hosting compiler that generates C code and uses the GObject type system and wrapping a number of GTK libraries. It uses [Meson](https://mesonbuild.com/) as its build system.



### Dependencies

```bash
granite
gstreamer-1.0
gstreamer-player-1.0
gtk+-3.0
json-glib-1.0
libgee-0.8
libsoup-3.0
meson
vala
```

### Building

Make sure you have the dependencies installed:

```bash
sudo apt install git valac meson
sudo apt install libgtk-3-dev libgee-0.8-dev libgranite-dev libgstreamer1.0-dev libgstreamer-plugins-bad1.0-dev libsoup-3.0-dev libjson-glib-dev
```

Clone the repo and drop into the Tuner directory. Configure Meson for development debug build, build Tuner with Ninja, and run the result:

```bash
meson setup --buildtype=debug builddir
ninja -C builddir
./builddir/com.github.louis77.tuner
```


```bash
meson configure -Dprefix=/usr
sudo ninja install
```
36 changes: 8 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Things I need help with:

### Flathub

Tuner is available on Flathub, but there are some known bugs:
Tuner is available asa Flatpak on Flathub:
https://flathub.org/apps/details/com.github.louis77.tuner

### elementary OS
Expand Down Expand Up @@ -83,7 +83,7 @@ While I hacked on this App, I discovered so many cool and new stations, which ma

## Features

- Uses radio-browser.info catalog
- Uses [radio-browser.info](https://www.radio-browser.info/) radio station catalog
- Presets various selection of stations (random, top, trending, genres)
- Save favourite stations
- Sends a click count to radio-browser.info on station click
Expand All @@ -102,38 +102,18 @@ While I hacked on this App, I discovered so many cool and new stations, which ma
* `TUNER_API` - a `:` separated list of API servers to read from, e.g.
* `export TUNER_API="de1.api.radio-browser.info:nl1.api.radio-browser.info"; com.github.louis77.tuner`

## Dependencies

```bash
granite
gtk+-3.0
gstreamer-1.0
gstreamer-player-1.0
libsoup-3.0
json-glib-1.0
libgee-0.8
meson
vala
```

## Building
## Build, Maintance and Development of Tuner

Make sure you have the dependencies installed:
Building, developing and maintianing **Tuner** is detailed seperately and in detail in the [DEVELOP](DEVELOP.md) markdown.

```bash
sudo apt install git valac meson
sudo apt install libgtk-3-dev libgee-0.8-dev libgranite-dev libgstreamer1.0-dev libgstreamer-plugins-bad1.0-dev libsoup-3.0-dev libjson-glib-dev
```

Then clone this repo and build it locally:
## Support

Feature request, observations and Issues can be documented with tickets on [Github](https://github.com/louis77/tuner/issues)

```bash
meson build && cd build
meson configure -Dprefix=/usr
sudo ninja install
```

## Known Issues
### Known Issues

- If AAC/AAC+ streams don't play (found on Elementary OS 6) install the following dependency:

Expand Down
12 changes: 12 additions & 0 deletions data/com.github.louis77.tuner.appdata.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@
</provides>

<releases>
<release version="1.5.4" date="2024-10-10">
<description>
<p>Maintanance release:</p>
<ul>
<li>Added API server lookup via SRV and backup via JSON, plus randomization</li>
<li>Added JSON validation for empty nodes</li>
<li>Refactored libsoup calls out to HttpClient.vala</li>
<li>Refactored loading of favicons to Favicon.vala</li>
<li>Turned off TLS checks for HTTP calls to avoid non loading of favicons with invalid certs</li>
</ul>
</description>
</release>
<release version="1.5.3" date="2024-09-22">
<description>
<p>Maintanance release:</p>
Expand Down
2 changes: 1 addition & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
project (
'com.github.louis77.tuner',
'vala', 'c',
version: '1.5.3'
version: '1.5.4'
)

# if meson.get_compiler ('vala').get_id() == 'valac'
Expand Down
2 changes: 1 addition & 1 deletion po/meson.build
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Install main translations
i18n.gettext (meson.project_name (),
args: [
'--directory=' + meson.source_root (),
'--directory=' + meson.project_source_root(),
'--from-code=UTF-8',
'-cTRANSLATORS'
],
Expand Down
8 changes: 2 additions & 6 deletions src/Services/Favicon.vala
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@
*/
public class Tuner.Favicon : GLib.Object {

// private static Image INTERNET_RADIO = Image().set_from_icon_name ("internet-radio", Gtk.IconSize.DIALOG);
//private static Image INTERNET_RADIO_SYMBOLIC = new Image.from_icon_name ("internet-radio-symbolic", Gtk.IconSize.DIALOG);
/**
/**
* @brief Asynchronously load the favicon for a given station
*
* This method attempts to load the favicon from the cache first. If not found in the cache
Expand Down Expand Up @@ -64,11 +62,9 @@ public class Tuner.Favicon : GLib.Object {

return scaled_pixbuf;
} catch (Error e) {
warning("Failed to process favicon: %s", e.message);
warning("Failed to process favicon %s: %s", station.favicon_url,e.message);
}
}
return null;
}


}
105 changes: 81 additions & 24 deletions src/Services/HttpClient.vala
Original file line number Diff line number Diff line change
Expand Up @@ -46,72 +46,129 @@ public class Tuner.HttpClient : Object {
{
_session = new Soup.Session();
_session.user_agent = @"$(Application.APP_ID)/$(Application.APP_VERSION)";
_session.timeout = 3;
_session.timeout = 5;
}
return _session;
}

/**
* @brief Perform a GET request to the specified URL
*
* This method sends a GET request to the specified URL using the singleton
* Soup.Session instance. It returns the response body as an InputStream and
* outputs the status code of the response.
*
* @param url_string The URL to send the GET request to
* @param status_code Output parameter for the HTTP status code of the response
* @return InputStream containing the response body
* @return InputStream containing the response body, or null if the request failed
* @throws Error if there's an error sending the request or receiving the response
*/
public static InputStream? GET(string url_string, out uint status_code)
{
status_code = 0;
var msg = new Soup.Message("GET", url_string);

if (url_string == null || url_string.length < 4) // domains are at least 4 chars
{
warning("GET - Invalid URL: %s", url_string ?? "null");
return null;
}

string sanitized_url = ensure_https_prefix(url_string);

var msg = new Soup.Message("GET", sanitized_url);

/*
Ignore all TLS certificate errors
*/
msg.accept_certificate.connect ((msg, cert, errors) => {
return true;
});

try {

if (Uri.is_valid(url_string, NONE))
if (Uri.is_valid(sanitized_url, UriFlags.NONE))
{
var inputStream = getSession().send(msg);
status_code = msg.status_code;
return inputStream;
} else {
debug("GET - Invalid URL format: %s", sanitized_url);
}
} catch (Error e) {
warning ("GET - Couldn't render favicon: %s (%s)",
url_string ?? "unknown url",
e.message);
}
warning("GET - Error accessing URL: %s (%s)",
sanitized_url,
e.message);
}

return null;
}

/**
* @brief Perform an asynchronous GET request to the specified URL
*
* This method sends an asynchronous GET request to the specified URL using the singleton
* Soup.Session instance. It returns the response body as an InputStream and
* outputs the status code of the response.
*
* @param url_string The URL to send the GET request to
* @param status_code Output parameter for the HTTP status code of the response
* @return InputStream containing the response body, or null if the request failed
*/
public static async InputStream? GETasync(string url_string, out uint status_code)
{
status_code = 0;
var msg = new Soup.Message("GET", url_string);

if (url_string == null || url_string.length < 4 ) // domains are at least 4 chars
{
debug("GETasync - Invalid URL: %s", url_string ?? "null");
return null;
}

string sanitized_url = ensure_https_prefix(url_string);

try {
if (Uri.is_valid(url_string, NONE))
{
var inputStream = yield getSession().send_async(msg, Priority.DEFAULT, null);
status_code = msg.status_code;
return inputStream;
if (!Uri.is_valid(sanitized_url, UriFlags.NONE)) {
debug("GETasync - Invalid URL format: %s", sanitized_url);
return null;
}
} catch (GLib.UriError e) {
debug("GETasync - URI error: %s", e.message);
return null;
}

var msg = new Soup.Message("GET", sanitized_url);

/*
Ignore all TLS certificate errors
*/
msg.accept_certificate.connect ((msg, cert, errors) => {
return true;
});

try {

var inputStream = yield getSession().send_async(msg, Priority.DEFAULT, null);
status_code = msg.status_code;
return inputStream;

} catch (Error e) {
warning ("GETasync - Couldn't render favicon: %s (%s)",
url_string ?? "unknown url",
warning("GETasync - Couldn't fetch resource: %s (%s)",
sanitized_url,
e.message);
}

return null;
}

/**
* @brief Ensures that the given URL has an HTTPS prefix
*
* This method checks if the provided URL starts with either "http://" or "https://".
* If it doesn't have either prefix, it adds "https://" to the beginning of the URL.
*
* @param url The input URL string to be checked and potentially modified
* @return A string representing the URL with an HTTPS prefix
*
* @note This method does not validate the URL structure beyond checking for the protocol prefix
*/
private static string ensure_https_prefix(string url) {
// Check if the string starts with "http://" or "https://"
if (!url.has_prefix("http://") && !url.has_prefix("https://")) {
// If it doesn't, prefix the string with "https://"
return "https://" + url;
}
return url;
}
}
Loading

0 comments on commit 0f66403

Please sign in to comment.