Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development #128

Merged
merged 5 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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