Skip to content

Commit

Permalink
Merge pull request #126 from technosf/124-radiobrowser-needs-refactor…
Browse files Browse the repository at this point in the history
…ing-use-of-libsoup-sessions

124 radiobrowser needs refactoring use of libsoup sessions
  • Loading branch information
technosf authored Oct 9, 2024
2 parents 370ee7c + 142b2ce commit 76165cd
Show file tree
Hide file tree
Showing 11 changed files with 789 additions and 485 deletions.
31 changes: 31 additions & 0 deletions src/Application.vala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
* SPDX-FileCopyrightText: 2020-2022 Louis Brauer <[email protected]>
*/

/**
Application
Entry point for Tuner
*/
/**
* @brief Entry point for Tuner application
*/
public class Tuner.Application : Gtk.Application {

public GLib.Settings settings { get; construct; }
Expand All @@ -21,13 +29,19 @@ public class Tuner.Application : Gtk.Application {
{ "resume-window", on_resume_window }
};

/**
* @brief Constructor for the Application
*/
public Application () {
Object (
application_id: APP_ID,
flags: ApplicationFlags.FLAGS_NONE
);
}

/**
* @brief Construct block for initializing the application
*/
construct {
GLib.Intl.setlocale (LocaleCategory.ALL, "");
GLib.Intl.bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
Expand All @@ -46,8 +60,15 @@ public class Tuner.Application : Gtk.Application {
add_action_entries(ACTION_ENTRIES, this);
}

/**
* @brief Singleton instance of the Application
*/
public static Application _instance = null;

/**
* @brief Getter for the singleton instance
* @return The Application instance
*/
public static Application instance {
get {
if (_instance == null) {
Expand All @@ -57,6 +78,9 @@ public class Tuner.Application : Gtk.Application {
}
}

/**
* @brief Activates the application
*/
protected override void activate() {
if (window == null) {
window = new Window (this, player);
Expand All @@ -68,10 +92,17 @@ public class Tuner.Application : Gtk.Application {

}

/**
* @brief Resumes the window
*/
private void on_resume_window() {
window.present();
}

/**
* @brief Ensures a directory exists
* @param path The directory path to ensure
*/
private void ensure_dir (string path) {
var dir = File.new_for_path (path);

Expand Down
2 changes: 1 addition & 1 deletion src/Main.vala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2020-2022 Louis Brauer <[email protected]>
*/

public static int main (string[] args) {
Gst.init (ref args);

Expand Down
74 changes: 74 additions & 0 deletions src/Services/Favicon.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2020-2022 Louis Brauer <[email protected]>
*/

/**
* @file Favicon.vala
* @author technosf
* @date 2024-10-01
* @brief Get, cache and serve favicons
* @version 1.5.4
*
* This file contains the Tuner.Favicon class, which handles the retrieval,
* caching, and serving of favicons for radio stations.
*/

/**
* @brief Get, cache and serve favicons
*
* This class handles the retrieval, caching, and serving of favicons for radio stations.
* It provides methods to load favicons from cache or fetch them from the internet.
*
* @class Tuner.Favicon
* @extends Object
*/
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
* or if forceReload is true, it will fetch the favicon from the internet asynchronously.
*
* @param station The station for which to load the favicon
* @param forceReload If true, bypass the cache and fetch the favicon from the internet
* @return The loaded favicon as a Gdk.Pixbuf, or null if loading fails
*/
public static async Gdk.Pixbuf? load_async(Model.Station station, bool forceReload = false)
{
var favicon_cache_file = Path.build_filename(Application.instance.cache_dir, station.id);

// Check cache first if not forcing reload
if (!forceReload && FileUtils.test(favicon_cache_file, FileTest.EXISTS)) {
try {
return new Gdk.Pixbuf.from_file_at_scale(favicon_cache_file, 48, 48, true);
} catch (Error e) {
warning("Failed to load cached favicon: %s", e.message);
}
}

// If not in cache or force reload, fetch from internet
uint status_code;
InputStream? stream = yield HttpClient.GETasync(station.favicon_url, out status_code);

if (stream != null && status_code == 200) {
try {
var pixbuf = yield new Gdk.Pixbuf.from_stream_async(stream, null);
var scaled_pixbuf = pixbuf.scale_simple(48, 48, Gdk.InterpType.BILINEAR);

// Save to cache
scaled_pixbuf.save(favicon_cache_file, "png");

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


}
117 changes: 117 additions & 0 deletions src/Services/HttpClient.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2020-2022 Louis Brauer <[email protected]>
*/

/**
* @file HttpClient.vala
* @author technosf
* @date 2024-10-01
* @since 1.5.4
* @brief HTTP client implementation using Soup library
*/

using Gee;

/**
* @class Tuner.HttpClient
* @brief HTTP functions abstracting Soup library
*
* This class provides static methods for making HTTP requests using the Soup library.
* It includes a singleton Soup.Session instance for efficient request handling.
*/
public class Tuner.HttpClient : Object {

/**
* @brief Singleton instance of Soup.Session
*
* This private static variable holds the single instance of Soup.Session
* used for all HTTP requests in the application. It is initialized lazily
* in the getSession() method.
*/
private static Soup.Session _session;

/**
* @brief Get the singleton Soup.Session instance
*
* This method returns the singleton Soup.Session instance, creating it
* if it doesn't already exist. The session is configured with a custom
* user agent string and a timeout of 3 seconds.
*
* @return The singleton Soup.Session instance
*/
private static Soup.Session getSession()
{
if (_session == null)
{
_session = new Soup.Session();
_session.user_agent = @"$(Application.APP_ID)/$(Application.APP_VERSION)";
_session.timeout = 3;
}
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
* @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);
try {

if (Uri.is_valid(url_string, NONE))
{
var inputStream = getSession().send(msg);
status_code = msg.status_code;
return inputStream;
}
} catch (Error e) {
warning ("GET - Couldn't render favicon: %s (%s)",
url_string ?? "unknown 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);
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;
}
} catch (Error e) {
warning ("GETasync - Couldn't render favicon: %s (%s)",
url_string ?? "unknown url",
e.message);
}

return null;
}
}
Loading

0 comments on commit 76165cd

Please sign in to comment.