Implement Linux data models (C/Vala)
- Add FeedItem, Feed, FeedSubscription models - Add SearchResult, SearchFilters, SearchQuery models - Add NotificationPreferences, ReadingPreferences models - Implement JSON serialization/deserialization for all models - Add equality comparison methods - Following GNOME HIG naming conventions - Build system configured with Meson/Ninja
This commit is contained in:
33
native-route/linux/meson.build
Normal file
33
native-route/linux/meson.build
Normal file
@@ -0,0 +1,33 @@
|
||||
project('rssuper-linux', 'vala', 'c',
|
||||
version: '0.1.0',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
'warning_level=3',
|
||||
'werror=false',
|
||||
]
|
||||
)
|
||||
|
||||
vala = find_program('valac')
|
||||
meson_version_check = run_command(vala, '--version', check: true)
|
||||
|
||||
# Dependencies
|
||||
glib_dep = dependency('glib-2.0', version: '>= 2.58')
|
||||
gio_dep = dependency('gio-2.0', version: '>= 2.58')
|
||||
json_dep = dependency('json-glib-1.0', version: '>= 1.4')
|
||||
|
||||
# Source files
|
||||
models = files(
|
||||
'src/models/feed-item.vala',
|
||||
'src/models/feed.vala',
|
||||
'src/models/feed-subscription.vala',
|
||||
'src/models/search-result.vala',
|
||||
'src/models/search-filters.vala',
|
||||
'src/models/notification-preferences.vala',
|
||||
'src/models/reading-preferences.vala',
|
||||
)
|
||||
|
||||
# Main library
|
||||
models_lib = library('rssuper-models', models,
|
||||
dependencies: [glib_dep, gio_dep, json_dep],
|
||||
install: false
|
||||
)
|
||||
313
native-route/linux/src/models/feed-item.vala
Normal file
313
native-route/linux/src/models/feed-item.vala
Normal file
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* FeedItem.vala
|
||||
*
|
||||
* Represents a single RSS/Atom feed item (article, episode, etc.)
|
||||
* Following GNOME HIG naming conventions and Vala/GObject patterns.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enclosure metadata for media attachments (podcasts, videos, etc.)
|
||||
*/
|
||||
public struct RSSuper.Enclosure {
|
||||
public string url { get; set; }
|
||||
public string item_type { get; set; }
|
||||
public string? length { get; set; }
|
||||
|
||||
public Enclosure(string url, string type, string? length = null) {
|
||||
this.url = url;
|
||||
this.item_type = type;
|
||||
this.length = length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FeedItem - Represents a single RSS/Atom entry
|
||||
*/
|
||||
public class RSSuper.FeedItem : Object {
|
||||
public string id { get; set; }
|
||||
public string title { get; set; }
|
||||
public string? link { get; set; }
|
||||
public string? description { get; set; }
|
||||
public string? content { get; set; }
|
||||
public string? author { get; set; }
|
||||
public string? published { get; set; }
|
||||
public string? updated { get; set; }
|
||||
public string[] categories { get; set; }
|
||||
public string? enclosure_url { get; set; }
|
||||
public string? enclosure_type { get; set; }
|
||||
public string? enclosure_length { get; set; }
|
||||
public string? guid { get; set; }
|
||||
public string? subscription_title { get; set; }
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public FeedItem() {
|
||||
this.id = "";
|
||||
this.title = "";
|
||||
this.categories = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with initial values
|
||||
*/
|
||||
public FeedItem.with_values(string id, string title, string? link = null,
|
||||
string? description = null, string? content = null,
|
||||
string? author = null, string? published = null,
|
||||
string? updated = null, string[]? categories = null,
|
||||
string? enclosure_url = null, string? enclosure_type = null,
|
||||
string? enclosure_length = null, string? guid = null,
|
||||
string? subscription_title = null) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.link = link;
|
||||
this.description = description;
|
||||
this.content = content;
|
||||
this.author = author;
|
||||
this.published = published;
|
||||
this.updated = updated;
|
||||
this.categories = categories;
|
||||
this.enclosure_url = enclosure_url;
|
||||
this.enclosure_type = enclosure_type;
|
||||
this.enclosure_length = enclosure_length;
|
||||
this.guid = guid;
|
||||
this.subscription_title = subscription_title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get enclosure as struct
|
||||
*/
|
||||
public Enclosure? get_enclosure() {
|
||||
if (this.enclosure_url == null) {
|
||||
return null;
|
||||
}
|
||||
return Enclosure(this.enclosure_url, this.enclosure_type ?? "", this.enclosure_length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set enclosure from struct
|
||||
*/
|
||||
public void set_enclosure(Enclosure? enclosure) {
|
||||
if (enclosure == null) {
|
||||
this.enclosure_url = null;
|
||||
this.enclosure_type = null;
|
||||
this.enclosure_length = null;
|
||||
} else {
|
||||
this.enclosure_url = enclosure.url;
|
||||
this.enclosure_type = enclosure_type;
|
||||
this.enclosure_length = enclosure.length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize to JSON string
|
||||
*/
|
||||
public string to_json_string() {
|
||||
var sb = new StringBuilder();
|
||||
sb.append("{");
|
||||
sb.append("\"id\":\"");
|
||||
sb.append(this.id);
|
||||
sb.append("\",\"title\":\"");
|
||||
sb.append(this.title);
|
||||
sb.append("\"");
|
||||
|
||||
if (this.link != null) {
|
||||
sb.append(",\"link\":\"");
|
||||
sb.append(this.link);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.description != null) {
|
||||
sb.append(",\"description\":\"");
|
||||
sb.append(this.description);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.content != null) {
|
||||
sb.append(",\"content\":\"");
|
||||
sb.append(this.content);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.author != null) {
|
||||
sb.append(",\"author\":\"");
|
||||
sb.append(this.author);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.published != null) {
|
||||
sb.append(",\"published\":\"");
|
||||
sb.append(this.published);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.updated != null) {
|
||||
sb.append(",\"updated\":\"");
|
||||
sb.append(this.updated);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.categories.length > 0) {
|
||||
sb.append(",\"categories\":[");
|
||||
for (var i = 0; i < this.categories.length; i++) {
|
||||
if (i > 0) sb.append(",");
|
||||
sb.append("\"");
|
||||
sb.append(this.categories[i]);
|
||||
sb.append("\"");
|
||||
}
|
||||
sb.append("]");
|
||||
}
|
||||
if (this.enclosure_url != null) {
|
||||
sb.append(",\"enclosure\":{\"url\":\"");
|
||||
sb.append(this.enclosure_url);
|
||||
sb.append("\"");
|
||||
if (this.enclosure_type != null) {
|
||||
sb.append(",\"type\":\"");
|
||||
sb.append(this.enclosure_type);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.enclosure_length != null) {
|
||||
sb.append(",\"length\":\"");
|
||||
sb.append(this.enclosure_length);
|
||||
sb.append("\"");
|
||||
}
|
||||
sb.append("}");
|
||||
}
|
||||
if (this.guid != null) {
|
||||
sb.append(",\"guid\":\"");
|
||||
sb.append(this.guid);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.subscription_title != null) {
|
||||
sb.append(",\"subscription_title\":\"");
|
||||
sb.append(this.subscription_title);
|
||||
sb.append("\"");
|
||||
}
|
||||
|
||||
sb.append("}");
|
||||
return sb.str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize from JSON string (simple parser)
|
||||
*/
|
||||
public static FeedItem? from_json_string(string json_string) {
|
||||
var parser = new Json.Parser();
|
||||
try {
|
||||
if (!parser.load_from_data(json_string)) {
|
||||
return null;
|
||||
}
|
||||
} catch (Error e) {
|
||||
warning("Failed to parse JSON: %s", e.message);
|
||||
return null;
|
||||
}
|
||||
|
||||
return from_json_node(parser.get_root());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize from Json.Node
|
||||
*/
|
||||
public static FeedItem? from_json_node(Json.Node node) {
|
||||
if (node.get_node_type() != Json.NodeType.OBJECT) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var obj = node.get_object();
|
||||
|
||||
if (!obj.has_member("id") || !obj.has_member("title")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var item = new FeedItem();
|
||||
item.id = obj.get_string_member("id");
|
||||
item.title = obj.get_string_member("title");
|
||||
|
||||
if (obj.has_member("link")) {
|
||||
item.link = obj.get_string_member("link");
|
||||
}
|
||||
if (obj.has_member("description")) {
|
||||
item.description = obj.get_string_member("description");
|
||||
}
|
||||
if (obj.has_member("content")) {
|
||||
item.content = obj.get_string_member("content");
|
||||
}
|
||||
if (obj.has_member("author")) {
|
||||
item.author = obj.get_string_member("author");
|
||||
}
|
||||
if (obj.has_member("published")) {
|
||||
item.published = obj.get_string_member("published");
|
||||
}
|
||||
if (obj.has_member("updated")) {
|
||||
item.updated = obj.get_string_member("updated");
|
||||
}
|
||||
if (obj.has_member("categories")) {
|
||||
var categories_array = obj.get_array_member("categories");
|
||||
var categories = new string[categories_array.get_length()];
|
||||
for (var i = 0; i < categories_array.get_length(); i++) {
|
||||
categories[i] = categories_array.get_string_element(i);
|
||||
}
|
||||
item.categories = categories;
|
||||
}
|
||||
if (obj.has_member("enclosure")) {
|
||||
var enclosure_obj = obj.get_object_member("enclosure");
|
||||
item.enclosure_url = enclosure_obj.get_string_member("url");
|
||||
if (enclosure_obj.has_member("type")) {
|
||||
item.enclosure_type = enclosure_obj.get_string_member("type");
|
||||
}
|
||||
if (enclosure_obj.has_member("length")) {
|
||||
item.enclosure_length = enclosure_obj.get_string_member("length");
|
||||
}
|
||||
}
|
||||
if (obj.has_member("guid")) {
|
||||
item.guid = obj.get_string_member("guid");
|
||||
}
|
||||
if (obj.has_member("subscription_title")) {
|
||||
item.subscription_title = obj.get_string_member("subscription_title");
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equality comparison
|
||||
*/
|
||||
public bool equals(FeedItem? other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.id == other.id &&
|
||||
this.title == other.title &&
|
||||
this.link == other.link &&
|
||||
this.description == other.description &&
|
||||
this.content == other.content &&
|
||||
this.author == other.author &&
|
||||
this.published == other.published &&
|
||||
this.updated == other.updated &&
|
||||
this.categories_equal(other.categories) &&
|
||||
this.enclosure_url == other.enclosure_url &&
|
||||
this.enclosure_type == other.enclosure_type &&
|
||||
this.enclosure_length == other.enclosure_length &&
|
||||
this.guid == other.guid &&
|
||||
this.subscription_title == other.subscription_title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for category array comparison
|
||||
*/
|
||||
private bool categories_equal(string[] other) {
|
||||
if (this.categories.length != other.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.categories.length; i++) {
|
||||
if (this.categories[i] != other[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a human-readable summary
|
||||
*/
|
||||
public string get_summary() {
|
||||
return "%s by %s".printf(this.title, this.author ?? "Unknown");
|
||||
}
|
||||
}
|
||||
259
native-route/linux/src/models/feed-subscription.vala
Normal file
259
native-route/linux/src/models/feed-subscription.vala
Normal file
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* FeedSubscription.vala
|
||||
*
|
||||
* Represents a user's subscription to a feed with sync settings.
|
||||
* Following GNOME HIG naming conventions and Vala/GObject patterns.
|
||||
*/
|
||||
|
||||
/**
|
||||
* HTTP Authentication credentials
|
||||
*/
|
||||
public struct RSSuper.HttpAuth {
|
||||
public string username { get; set; }
|
||||
public string password { get; set; }
|
||||
|
||||
public HttpAuth(string username, string password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FeedSubscription - Represents a user's subscription to a feed
|
||||
*/
|
||||
public class RSSuper.FeedSubscription : Object {
|
||||
public string id { get; set; }
|
||||
public string url { get; set; }
|
||||
public string title { get; set; }
|
||||
public string? category { get; set; }
|
||||
public bool enabled { get; set; }
|
||||
public int fetch_interval { get; set; }
|
||||
public string created_at { get; set; }
|
||||
public string updated_at { get; set; }
|
||||
public string? last_fetched_at { get; set; }
|
||||
public string? next_fetch_at { get; set; }
|
||||
public string? error { get; set; }
|
||||
public string? http_auth_username { get; set; }
|
||||
public string? http_auth_password { get; set; }
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public FeedSubscription() {
|
||||
this.id = "";
|
||||
this.url = "";
|
||||
this.title = "";
|
||||
this.enabled = true;
|
||||
this.fetch_interval = 60;
|
||||
this.created_at = "";
|
||||
this.updated_at = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with initial values
|
||||
*/
|
||||
public FeedSubscription.with_values(string id, string url, string title,
|
||||
int fetch_interval = 60,
|
||||
string? category = null, bool enabled = true,
|
||||
string? created_at = null, string? updated_at = null,
|
||||
string? last_fetched_at = null,
|
||||
string? next_fetch_at = null,
|
||||
string? error = null,
|
||||
string? http_auth_username = null,
|
||||
string? http_auth_password = null) {
|
||||
this.id = id;
|
||||
this.url = url;
|
||||
this.title = title;
|
||||
this.category = category;
|
||||
this.enabled = enabled;
|
||||
this.fetch_interval = fetch_interval;
|
||||
this.created_at = created_at ?? "";
|
||||
this.updated_at = updated_at ?? "";
|
||||
this.last_fetched_at = last_fetched_at;
|
||||
this.next_fetch_at = next_fetch_at;
|
||||
this.error = error;
|
||||
this.http_auth_username = http_auth_username;
|
||||
this.http_auth_password = http_auth_password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTTP auth as struct
|
||||
*/
|
||||
public HttpAuth? get_http_auth() {
|
||||
if (this.http_auth_username == null) {
|
||||
return null;
|
||||
}
|
||||
return HttpAuth(this.http_auth_username, this.http_auth_password ?? "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set HTTP auth from struct
|
||||
*/
|
||||
public void set_http_auth(HttpAuth? auth) {
|
||||
if (auth == null) {
|
||||
this.http_auth_username = null;
|
||||
this.http_auth_password = null;
|
||||
} else {
|
||||
this.http_auth_username = auth.username;
|
||||
this.http_auth_password = auth.password;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if subscription has an error
|
||||
*/
|
||||
public bool has_error() {
|
||||
return this.error != null && this.error.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize to JSON string
|
||||
*/
|
||||
public string to_json_string() {
|
||||
var sb = new StringBuilder();
|
||||
sb.append("{");
|
||||
sb.append("\"id\":\"");
|
||||
sb.append(this.id);
|
||||
sb.append("\",\"url\":\"");
|
||||
sb.append(this.url);
|
||||
sb.append("\",\"title\":\"");
|
||||
sb.append(this.title);
|
||||
sb.append("\",\"enabled\":");
|
||||
sb.append(this.enabled ? "true" : "false");
|
||||
sb.append(",\"fetchInterval\":%d".printf(this.fetch_interval));
|
||||
sb.append(",\"createdAt\":\"");
|
||||
sb.append(this.created_at);
|
||||
sb.append("\",\"updatedAt\":\"");
|
||||
sb.append(this.updated_at);
|
||||
sb.append("\"");
|
||||
|
||||
if (this.category != null) {
|
||||
sb.append(",\"category\":\"");
|
||||
sb.append(this.category);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.last_fetched_at != null) {
|
||||
sb.append(",\"lastFetchedAt\":\"");
|
||||
sb.append(this.last_fetched_at);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.next_fetch_at != null) {
|
||||
sb.append(",\"nextFetchAt\":\"");
|
||||
sb.append(this.next_fetch_at);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.error != null) {
|
||||
sb.append(",\"error\":\"");
|
||||
sb.append(this.error);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.http_auth_username != null) {
|
||||
sb.append(",\"httpAuth\":{\"username\":\"");
|
||||
sb.append(this.http_auth_username);
|
||||
sb.append("\"");
|
||||
if (this.http_auth_password != null) {
|
||||
sb.append(",\"password\":\"");
|
||||
sb.append(this.http_auth_password);
|
||||
sb.append("\"");
|
||||
}
|
||||
sb.append("}");
|
||||
}
|
||||
|
||||
sb.append("}");
|
||||
return sb.str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize from JSON string
|
||||
*/
|
||||
public static FeedSubscription? from_json_string(string json_string) {
|
||||
var parser = new Json.Parser();
|
||||
try {
|
||||
if (!parser.load_from_data(json_string)) {
|
||||
return null;
|
||||
}
|
||||
} catch (Error e) {
|
||||
warning("Failed to parse JSON: %s", e.message);
|
||||
return null;
|
||||
}
|
||||
|
||||
return from_json_node(parser.get_root());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize from Json.Node
|
||||
*/
|
||||
public static FeedSubscription? from_json_node(Json.Node node) {
|
||||
if (node.get_node_type() != Json.NodeType.OBJECT) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var obj = node.get_object();
|
||||
|
||||
if (!obj.has_member("id") || !obj.has_member("url") ||
|
||||
!obj.has_member("title") || !obj.has_member("createdAt") ||
|
||||
!obj.has_member("updatedAt")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var subscription = new FeedSubscription();
|
||||
subscription.id = obj.get_string_member("id");
|
||||
subscription.url = obj.get_string_member("url");
|
||||
subscription.title = obj.get_string_member("title");
|
||||
|
||||
if (obj.has_member("category")) {
|
||||
subscription.category = obj.get_string_member("category");
|
||||
}
|
||||
if (obj.has_member("enabled")) {
|
||||
subscription.enabled = obj.get_boolean_member("enabled");
|
||||
}
|
||||
if (obj.has_member("fetchInterval")) {
|
||||
subscription.fetch_interval = (int)obj.get_int_member("fetchInterval");
|
||||
}
|
||||
|
||||
subscription.created_at = obj.get_string_member("createdAt");
|
||||
subscription.updated_at = obj.get_string_member("updatedAt");
|
||||
|
||||
if (obj.has_member("lastFetchedAt")) {
|
||||
subscription.last_fetched_at = obj.get_string_member("lastFetchedAt");
|
||||
}
|
||||
if (obj.has_member("nextFetchAt")) {
|
||||
subscription.next_fetch_at = obj.get_string_member("nextFetchAt");
|
||||
}
|
||||
if (obj.has_member("error")) {
|
||||
subscription.error = obj.get_string_member("error");
|
||||
}
|
||||
if (obj.has_member("httpAuth")) {
|
||||
var auth_obj = obj.get_object_member("httpAuth");
|
||||
subscription.http_auth_username = auth_obj.get_string_member("username");
|
||||
if (auth_obj.has_member("password")) {
|
||||
subscription.http_auth_password = auth_obj.get_string_member("password");
|
||||
}
|
||||
}
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equality comparison
|
||||
*/
|
||||
public bool equals(FeedSubscription? other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.id == other.id &&
|
||||
this.url == other.url &&
|
||||
this.title == other.title &&
|
||||
this.category == other.category &&
|
||||
this.enabled == other.enabled &&
|
||||
this.fetch_interval == other.fetch_interval &&
|
||||
this.created_at == other.created_at &&
|
||||
this.updated_at == other.updated_at &&
|
||||
this.last_fetched_at == other.last_fetched_at &&
|
||||
this.next_fetch_at == other.next_fetch_at &&
|
||||
this.error == other.error &&
|
||||
this.http_auth_username == other.http_auth_username &&
|
||||
this.http_auth_password == other.http_auth_password;
|
||||
}
|
||||
}
|
||||
282
native-route/linux/src/models/feed.vala
Normal file
282
native-route/linux/src/models/feed.vala
Normal file
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Feed.vala
|
||||
*
|
||||
* Represents an RSS/Atom feed with its metadata and items.
|
||||
* Following GNOME HIG naming conventions and Vala/GObject patterns.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Feed - Represents an RSS/Atom feed
|
||||
*/
|
||||
public class RSSuper.Feed : Object {
|
||||
public string id { get; set; }
|
||||
public string title { get; set; }
|
||||
public string? link { get; set; }
|
||||
public string? description { get; set; }
|
||||
public string? subtitle { get; set; }
|
||||
public string? language { get; set; }
|
||||
public string? last_build_date { get; set; }
|
||||
public string? updated { get; set; }
|
||||
public string? generator { get; set; }
|
||||
public int ttl { get; set; }
|
||||
public string raw_url { get; set; }
|
||||
public string? last_fetched_at { get; set; }
|
||||
public string? next_fetch_at { get; set; }
|
||||
public FeedItem[] items { get; set; }
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public Feed() {
|
||||
this.id = "";
|
||||
this.title = "";
|
||||
this.raw_url = "";
|
||||
this.ttl = 60;
|
||||
this.items = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with initial values
|
||||
*/
|
||||
public Feed.with_values(string id, string title, string raw_url,
|
||||
string? link = null, string? description = null,
|
||||
string? subtitle = null, string? language = null,
|
||||
string? last_build_date = null, string? updated = null,
|
||||
string? generator = null, int ttl = 60,
|
||||
FeedItem[]? items = null, string? last_fetched_at = null,
|
||||
string? next_fetch_at = null) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.link = link;
|
||||
this.description = description;
|
||||
this.subtitle = subtitle;
|
||||
this.language = language;
|
||||
this.last_build_date = last_build_date;
|
||||
this.updated = updated;
|
||||
this.generator = generator;
|
||||
this.ttl = ttl;
|
||||
this.items = items;
|
||||
this.raw_url = raw_url;
|
||||
this.last_fetched_at = last_fetched_at;
|
||||
this.next_fetch_at = next_fetch_at;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to the feed
|
||||
*/
|
||||
public void add_item(FeedItem item) {
|
||||
var new_items = new FeedItem[this.items.length + 1];
|
||||
for (var i = 0; i < this.items.length; i++) {
|
||||
new_items[i] = this.items[i];
|
||||
}
|
||||
new_items[this.items.length] = item;
|
||||
this.items = new_items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item count
|
||||
*/
|
||||
public int get_item_count() {
|
||||
return this.items.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize to JSON string
|
||||
*/
|
||||
public string to_json_string() {
|
||||
var sb = new StringBuilder();
|
||||
sb.append("{");
|
||||
sb.append("\"id\":\"");
|
||||
sb.append(this.id);
|
||||
sb.append("\",\"title\":\"");
|
||||
sb.append(this.title);
|
||||
sb.append("\",\"raw_url\":\"");
|
||||
sb.append(this.raw_url);
|
||||
sb.append("\"");
|
||||
|
||||
if (this.link != null) {
|
||||
sb.append(",\"link\":\"");
|
||||
sb.append(this.link);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.description != null) {
|
||||
sb.append(",\"description\":\"");
|
||||
sb.append(this.description);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.subtitle != null) {
|
||||
sb.append(",\"subtitle\":\"");
|
||||
sb.append(this.subtitle);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.language != null) {
|
||||
sb.append(",\"language\":\"");
|
||||
sb.append(this.language);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.last_build_date != null) {
|
||||
sb.append(",\"lastBuildDate\":\"");
|
||||
sb.append(this.last_build_date);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.updated != null) {
|
||||
sb.append(",\"updated\":\"");
|
||||
sb.append(this.updated);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.generator != null) {
|
||||
sb.append(",\"generator\":\"");
|
||||
sb.append(this.generator);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.ttl != 60) {
|
||||
sb.append(",\"ttl\":%d".printf(this.ttl));
|
||||
}
|
||||
if (this.items.length > 0) {
|
||||
sb.append(",\"items\":[");
|
||||
for (var i = 0; i < this.items.length; i++) {
|
||||
if (i > 0) sb.append(",");
|
||||
sb.append(this.items[i].to_json_string());
|
||||
}
|
||||
sb.append("]");
|
||||
}
|
||||
if (this.last_fetched_at != null) {
|
||||
sb.append(",\"lastFetchedAt\":\"");
|
||||
sb.append(this.last_fetched_at);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.next_fetch_at != null) {
|
||||
sb.append(",\"nextFetchAt\":\"");
|
||||
sb.append(this.next_fetch_at);
|
||||
sb.append("\"");
|
||||
}
|
||||
|
||||
sb.append("}");
|
||||
return sb.str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize from JSON string
|
||||
*/
|
||||
public static Feed? from_json_string(string json_string) {
|
||||
var parser = new Json.Parser();
|
||||
try {
|
||||
if (!parser.load_from_data(json_string)) {
|
||||
return null;
|
||||
}
|
||||
} catch (Error e) {
|
||||
warning("Failed to parse JSON: %s", e.message);
|
||||
return null;
|
||||
}
|
||||
|
||||
return from_json_node(parser.get_root());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize from Json.Node
|
||||
*/
|
||||
public static Feed? from_json_node(Json.Node node) {
|
||||
if (node.get_node_type() != Json.NodeType.OBJECT) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var obj = node.get_object();
|
||||
|
||||
if (!obj.has_member("id") || !obj.has_member("title") || !obj.has_member("raw_url")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var feed = new Feed();
|
||||
feed.id = obj.get_string_member("id");
|
||||
feed.title = obj.get_string_member("title");
|
||||
feed.raw_url = obj.get_string_member("raw_url");
|
||||
|
||||
if (obj.has_member("link")) {
|
||||
feed.link = obj.get_string_member("link");
|
||||
}
|
||||
if (obj.has_member("description")) {
|
||||
feed.description = obj.get_string_member("description");
|
||||
}
|
||||
if (obj.has_member("subtitle")) {
|
||||
feed.subtitle = obj.get_string_member("subtitle");
|
||||
}
|
||||
if (obj.has_member("language")) {
|
||||
feed.language = obj.get_string_member("language");
|
||||
}
|
||||
if (obj.has_member("lastBuildDate")) {
|
||||
feed.last_build_date = obj.get_string_member("lastBuildDate");
|
||||
}
|
||||
if (obj.has_member("updated")) {
|
||||
feed.updated = obj.get_string_member("updated");
|
||||
}
|
||||
if (obj.has_member("generator")) {
|
||||
feed.generator = obj.get_string_member("generator");
|
||||
}
|
||||
if (obj.has_member("ttl")) {
|
||||
feed.ttl = (int)obj.get_int_member("ttl");
|
||||
}
|
||||
if (obj.has_member("lastFetchedAt")) {
|
||||
feed.last_fetched_at = obj.get_string_member("lastFetchedAt");
|
||||
}
|
||||
if (obj.has_member("nextFetchAt")) {
|
||||
feed.next_fetch_at = obj.get_string_member("nextFetchAt");
|
||||
}
|
||||
|
||||
// Deserialize items
|
||||
if (obj.has_member("items")) {
|
||||
var items_array = obj.get_array_member("items");
|
||||
var items = new FeedItem[items_array.get_length()];
|
||||
for (var i = 0; i < items_array.get_length(); i++) {
|
||||
var item_node = items_array.get_element(i);
|
||||
var item = FeedItem.from_json_node(item_node);
|
||||
if (item != null) {
|
||||
items[i] = item;
|
||||
}
|
||||
}
|
||||
feed.items = items;
|
||||
}
|
||||
|
||||
return feed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equality comparison
|
||||
*/
|
||||
public bool equals(Feed? other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.id == other.id &&
|
||||
this.title == other.title &&
|
||||
this.link == other.link &&
|
||||
this.description == other.description &&
|
||||
this.subtitle == other.subtitle &&
|
||||
this.language == other.language &&
|
||||
this.last_build_date == other.last_build_date &&
|
||||
this.updated == other.updated &&
|
||||
this.generator == other.generator &&
|
||||
this.ttl == other.ttl &&
|
||||
this.raw_url == other.raw_url &&
|
||||
this.last_fetched_at == other.last_fetched_at &&
|
||||
this.next_fetch_at == other.next_fetch_at &&
|
||||
this.items_equal(other.items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for item array comparison
|
||||
*/
|
||||
private bool items_equal(FeedItem[] other) {
|
||||
if (this.items.length != other.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.items.length; i++) {
|
||||
if (!this.items[i].equals(other[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
5
native-route/linux/src/models/namespaces.vala
Normal file
5
native-route/linux/src/models/namespaces.vala
Normal file
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* Namespace definition for RSSuper Linux models
|
||||
*/
|
||||
public namespace RSSuper {
|
||||
}
|
||||
190
native-route/linux/src/models/notification-preferences.vala
Normal file
190
native-route/linux/src/models/notification-preferences.vala
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* NotificationPreferences.vala
|
||||
*
|
||||
* Represents user notification preferences.
|
||||
* Following GNOME HIG naming conventions and Vala/GObject patterns.
|
||||
*/
|
||||
|
||||
/**
|
||||
* NotificationPreferences - User notification settings
|
||||
*/
|
||||
public class RSSuper.NotificationPreferences : Object {
|
||||
public bool new_articles { get; set; }
|
||||
public bool episode_releases { get; set; }
|
||||
public bool custom_alerts { get; set; }
|
||||
public bool badge_count { get; set; }
|
||||
public bool sound { get; set; }
|
||||
public bool vibration { get; set; }
|
||||
|
||||
/**
|
||||
* Default constructor (all enabled by default)
|
||||
*/
|
||||
public NotificationPreferences() {
|
||||
this.new_articles = true;
|
||||
this.episode_releases = true;
|
||||
this.custom_alerts = true;
|
||||
this.badge_count = true;
|
||||
this.sound = true;
|
||||
this.vibration = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with initial values
|
||||
*/
|
||||
public NotificationPreferences.with_values(bool new_articles = true,
|
||||
bool episode_releases = true,
|
||||
bool custom_alerts = true,
|
||||
bool badge_count = true,
|
||||
bool sound = true,
|
||||
bool vibration = true) {
|
||||
this.new_articles = new_articles;
|
||||
this.episode_releases = episode_releases;
|
||||
this.custom_alerts = custom_alerts;
|
||||
this.badge_count = badge_count;
|
||||
this.sound = sound;
|
||||
this.vibration = vibration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable all notifications
|
||||
*/
|
||||
public void enable_all() {
|
||||
this.new_articles = true;
|
||||
this.episode_releases = true;
|
||||
this.custom_alerts = true;
|
||||
this.badge_count = true;
|
||||
this.sound = true;
|
||||
this.vibration = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable all notifications
|
||||
*/
|
||||
public void disable_all() {
|
||||
this.new_articles = false;
|
||||
this.episode_releases = false;
|
||||
this.custom_alerts = false;
|
||||
this.badge_count = false;
|
||||
this.sound = false;
|
||||
this.vibration = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any notifications are enabled
|
||||
*/
|
||||
public bool has_any_enabled() {
|
||||
return this.new_articles ||
|
||||
this.episode_releases ||
|
||||
this.custom_alerts ||
|
||||
this.badge_count ||
|
||||
this.sound ||
|
||||
this.vibration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if content notifications are enabled
|
||||
*/
|
||||
public bool has_content_notifications() {
|
||||
return this.new_articles || this.episode_releases || this.custom_alerts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize to JSON string
|
||||
*/
|
||||
public string to_json_string() {
|
||||
var sb = new StringBuilder();
|
||||
sb.append("{");
|
||||
sb.append("\"newArticles\":");
|
||||
sb.append(this.new_articles ? "true" : "false");
|
||||
sb.append(",\"episodeReleases\":");
|
||||
sb.append(this.episode_releases ? "true" : "false");
|
||||
sb.append(",\"customAlerts\":");
|
||||
sb.append(this.custom_alerts ? "true" : "false");
|
||||
sb.append(",\"badgeCount\":");
|
||||
sb.append(this.badge_count ? "true" : "false");
|
||||
sb.append(",\"sound\":");
|
||||
sb.append(this.sound ? "true" : "false");
|
||||
sb.append(",\"vibration\":");
|
||||
sb.append(this.vibration ? "true" : "false");
|
||||
sb.append("}");
|
||||
return sb.str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize from JSON string
|
||||
*/
|
||||
public static NotificationPreferences? from_json_string(string json_string) {
|
||||
var parser = new Json.Parser();
|
||||
try {
|
||||
if (!parser.load_from_data(json_string)) {
|
||||
return null;
|
||||
}
|
||||
} catch (Error e) {
|
||||
warning("Failed to parse JSON: %s", e.message);
|
||||
return null;
|
||||
}
|
||||
|
||||
return from_json_node(parser.get_root());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize from Json.Node
|
||||
*/
|
||||
public static NotificationPreferences? from_json_node(Json.Node node) {
|
||||
if (node.get_node_type() != Json.NodeType.OBJECT) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var obj = node.get_object();
|
||||
var prefs = new NotificationPreferences();
|
||||
|
||||
if (obj.has_member("newArticles")) {
|
||||
prefs.new_articles = obj.get_boolean_member("newArticles");
|
||||
}
|
||||
if (obj.has_member("episodeReleases")) {
|
||||
prefs.episode_releases = obj.get_boolean_member("episodeReleases");
|
||||
}
|
||||
if (obj.has_member("customAlerts")) {
|
||||
prefs.custom_alerts = obj.get_boolean_member("customAlerts");
|
||||
}
|
||||
if (obj.has_member("badgeCount")) {
|
||||
prefs.badge_count = obj.get_boolean_member("badgeCount");
|
||||
}
|
||||
if (obj.has_member("sound")) {
|
||||
prefs.sound = obj.get_boolean_member("sound");
|
||||
}
|
||||
if (obj.has_member("vibration")) {
|
||||
prefs.vibration = obj.get_boolean_member("vibration");
|
||||
}
|
||||
|
||||
return prefs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equality comparison
|
||||
*/
|
||||
public bool equals(NotificationPreferences? other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.new_articles == other.new_articles &&
|
||||
this.episode_releases == other.episode_releases &&
|
||||
this.custom_alerts == other.custom_alerts &&
|
||||
this.badge_count == other.badge_count &&
|
||||
this.sound == other.sound &&
|
||||
this.vibration == other.vibration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy preferences from another instance
|
||||
*/
|
||||
public void copy_from(NotificationPreferences other) {
|
||||
this.new_articles = other.new_articles;
|
||||
this.episode_releases = other.episode_releases;
|
||||
this.custom_alerts = other.custom_alerts;
|
||||
this.badge_count = other.badge_count;
|
||||
this.sound = other.sound;
|
||||
this.vibration = other.vibration;
|
||||
}
|
||||
}
|
||||
168
native-route/linux/src/models/reading-preferences.vala
Normal file
168
native-route/linux/src/models/reading-preferences.vala
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* ReadingPreferences.vala
|
||||
*
|
||||
* Represents user reading/display preferences.
|
||||
* Following GNOME HIG naming conventions and Vala/GObject patterns.
|
||||
*/
|
||||
|
||||
/**
|
||||
* FontSize - Available font size options
|
||||
*/
|
||||
public enum RSSuper.FontSize {
|
||||
SMALL,
|
||||
MEDIUM,
|
||||
LARGE,
|
||||
XLARGE
|
||||
}
|
||||
|
||||
/**
|
||||
* LineHeight - Available line height options
|
||||
*/
|
||||
public enum RSSuper.LineHeight {
|
||||
NORMAL,
|
||||
RELAXED,
|
||||
LOOSE
|
||||
}
|
||||
|
||||
/**
|
||||
* ReadingPreferences - User reading/display settings
|
||||
*/
|
||||
public class RSSuper.ReadingPreferences : Object {
|
||||
public FontSize font_size { get; set; }
|
||||
public LineHeight line_height { get; set; }
|
||||
public bool show_table_of_contents { get; set; }
|
||||
public bool show_reading_time { get; set; }
|
||||
public bool show_author { get; set; }
|
||||
public bool show_date { get; set; }
|
||||
|
||||
public ReadingPreferences() {
|
||||
this.font_size = FontSize.MEDIUM;
|
||||
this.line_height = LineHeight.NORMAL;
|
||||
this.show_table_of_contents = true;
|
||||
this.show_reading_time = true;
|
||||
this.show_author = true;
|
||||
this.show_date = true;
|
||||
}
|
||||
|
||||
public ReadingPreferences.with_values(FontSize font_size = FontSize.MEDIUM,
|
||||
LineHeight line_height = LineHeight.NORMAL,
|
||||
bool show_table_of_contents = true,
|
||||
bool show_reading_time = true,
|
||||
bool show_author = true,
|
||||
bool show_date = true) {
|
||||
this.font_size = font_size;
|
||||
this.line_height = line_height;
|
||||
this.show_table_of_contents = show_table_of_contents;
|
||||
this.show_reading_time = show_reading_time;
|
||||
this.show_author = show_author;
|
||||
this.show_date = show_date;
|
||||
}
|
||||
|
||||
public string get_font_size_string() {
|
||||
switch (this.font_size) {
|
||||
case FontSize.SMALL: return "small";
|
||||
case FontSize.MEDIUM: return "medium";
|
||||
case FontSize.LARGE: return "large";
|
||||
case FontSize.XLARGE: return "xlarge";
|
||||
default: return "medium";
|
||||
}
|
||||
}
|
||||
|
||||
public static FontSize font_size_from_string(string str) {
|
||||
switch (str) {
|
||||
case "small": return FontSize.SMALL;
|
||||
case "medium": return FontSize.MEDIUM;
|
||||
case "large": return FontSize.LARGE;
|
||||
case "xlarge": return FontSize.XLARGE;
|
||||
default: return FontSize.MEDIUM;
|
||||
}
|
||||
}
|
||||
|
||||
public string get_line_height_string() {
|
||||
switch (this.line_height) {
|
||||
case LineHeight.NORMAL: return "normal";
|
||||
case LineHeight.RELAXED: return "relaxed";
|
||||
case LineHeight.LOOSE: return "loose";
|
||||
default: return "normal";
|
||||
}
|
||||
}
|
||||
|
||||
public static LineHeight line_height_from_string(string str) {
|
||||
switch (str) {
|
||||
case "normal": return LineHeight.NORMAL;
|
||||
case "relaxed": return LineHeight.RELAXED;
|
||||
case "loose": return LineHeight.LOOSE;
|
||||
default: return LineHeight.NORMAL;
|
||||
}
|
||||
}
|
||||
|
||||
public void reset_to_defaults() {
|
||||
this.font_size = FontSize.MEDIUM;
|
||||
this.line_height = LineHeight.NORMAL;
|
||||
this.show_table_of_contents = true;
|
||||
this.show_reading_time = true;
|
||||
this.show_author = true;
|
||||
this.show_date = true;
|
||||
}
|
||||
|
||||
public string to_json_string() {
|
||||
var sb = new StringBuilder();
|
||||
sb.append("{\"fontSize\":\"");
|
||||
sb.append(this.get_font_size_string());
|
||||
sb.append("\",\"lineHeight\":\"");
|
||||
sb.append(this.get_line_height_string());
|
||||
sb.append("\",\"showTableOfContents\":");
|
||||
sb.append(this.show_table_of_contents ? "true" : "false");
|
||||
sb.append(",\"showReadingTime\":");
|
||||
sb.append(this.show_reading_time ? "true" : "false");
|
||||
sb.append(",\"showAuthor\":");
|
||||
sb.append(this.show_author ? "true" : "false");
|
||||
sb.append(",\"showDate\":");
|
||||
sb.append(this.show_date ? "true" : "false");
|
||||
sb.append("}");
|
||||
return sb.str;
|
||||
}
|
||||
|
||||
public static ReadingPreferences? from_json_string(string json_string) {
|
||||
var parser = new Json.Parser();
|
||||
try {
|
||||
if (!parser.load_from_data(json_string)) return null;
|
||||
} catch (Error e) {
|
||||
warning("Failed to parse JSON: %s", e.message);
|
||||
return null;
|
||||
}
|
||||
return from_json_node(parser.get_root());
|
||||
}
|
||||
|
||||
public static ReadingPreferences? from_json_node(Json.Node node) {
|
||||
if (node.get_node_type() != Json.NodeType.OBJECT) return null;
|
||||
var obj = node.get_object();
|
||||
var prefs = new ReadingPreferences();
|
||||
if (obj.has_member("fontSize")) prefs.font_size = font_size_from_string(obj.get_string_member("fontSize"));
|
||||
if (obj.has_member("lineHeight")) prefs.line_height = line_height_from_string(obj.get_string_member("lineHeight"));
|
||||
if (obj.has_member("showTableOfContents")) prefs.show_table_of_contents = obj.get_boolean_member("showTableOfContents");
|
||||
if (obj.has_member("showReadingTime")) prefs.show_reading_time = obj.get_boolean_member("showReadingTime");
|
||||
if (obj.has_member("showAuthor")) prefs.show_author = obj.get_boolean_member("showAuthor");
|
||||
if (obj.has_member("showDate")) prefs.show_date = obj.get_boolean_member("showDate");
|
||||
return prefs;
|
||||
}
|
||||
|
||||
public bool equals(ReadingPreferences? other) {
|
||||
if (other == null) return false;
|
||||
return this.font_size == other.font_size &&
|
||||
this.line_height == other.line_height &&
|
||||
this.show_table_of_contents == other.show_table_of_contents &&
|
||||
this.show_reading_time == other.show_reading_time &&
|
||||
this.show_author == other.show_author &&
|
||||
this.show_date == other.show_date;
|
||||
}
|
||||
|
||||
public void copy_from(ReadingPreferences other) {
|
||||
this.font_size = other.font_size;
|
||||
this.line_height = other.line_height;
|
||||
this.show_table_of_contents = other.show_table_of_contents;
|
||||
this.show_reading_time = other.show_reading_time;
|
||||
this.show_author = other.show_author;
|
||||
this.show_date = other.show_date;
|
||||
}
|
||||
}
|
||||
435
native-route/linux/src/models/search-filters.vala
Normal file
435
native-route/linux/src/models/search-filters.vala
Normal file
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
* SearchFilters.vala
|
||||
*
|
||||
* Represents search query parameters and filters.
|
||||
* Following GNOME HIG naming conventions and Vala/GObject patterns.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SearchContentType - Type of content to search for
|
||||
*/
|
||||
public enum RSSuper.SearchContentType {
|
||||
ARTICLE,
|
||||
AUDIO,
|
||||
VIDEO
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchSortOption - Sorting options for search results
|
||||
*/
|
||||
public enum RSSuper.SearchSortOption {
|
||||
RELEVANCE,
|
||||
DATE_DESC,
|
||||
DATE_ASC,
|
||||
TITLE_ASC,
|
||||
TITLE_DESC,
|
||||
FEED_ASC,
|
||||
FEED_DESC
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchFilters - Represents search filters and query parameters
|
||||
*/
|
||||
public struct RSSuper.SearchFilters {
|
||||
public string? date_from { get; set; }
|
||||
public string? date_to { get; set; }
|
||||
public string[] feed_ids { get; set; }
|
||||
public string[] authors { get; set; }
|
||||
public SearchContentType? content_type { get; set; }
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public SearchFilters(string? date_from = null, string? date_to = null,
|
||||
string[]? feed_ids = null, string[]? authors = null,
|
||||
SearchContentType? content_type = null) {
|
||||
this.date_from = date_from;
|
||||
this.date_to = date_to;
|
||||
this.feed_ids = feed_ids;
|
||||
this.authors = authors;
|
||||
this.content_type = content_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content type as string
|
||||
*/
|
||||
public string? get_content_type_string() {
|
||||
if (this.content_type == null) {
|
||||
return null;
|
||||
}
|
||||
switch (this.content_type) {
|
||||
case SearchContentType.ARTICLE:
|
||||
return "article";
|
||||
case SearchContentType.AUDIO:
|
||||
return "audio";
|
||||
case SearchContentType.VIDEO:
|
||||
return "video";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse content type from string
|
||||
*/
|
||||
public static SearchContentType? content_type_from_string(string? str) {
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
switch (str) {
|
||||
case "article":
|
||||
return SearchContentType.ARTICLE;
|
||||
case "audio":
|
||||
return SearchContentType.AUDIO;
|
||||
case "video":
|
||||
return SearchContentType.VIDEO;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sort option as string
|
||||
*/
|
||||
public static string sort_option_to_string(SearchSortOption option) {
|
||||
switch (option) {
|
||||
case SearchSortOption.RELEVANCE:
|
||||
return "relevance";
|
||||
case SearchSortOption.DATE_DESC:
|
||||
return "date_desc";
|
||||
case SearchSortOption.DATE_ASC:
|
||||
return "date_asc";
|
||||
case SearchSortOption.TITLE_ASC:
|
||||
return "title_asc";
|
||||
case SearchSortOption.TITLE_DESC:
|
||||
return "title_desc";
|
||||
case SearchSortOption.FEED_ASC:
|
||||
return "feed_asc";
|
||||
case SearchSortOption.FEED_DESC:
|
||||
return "feed_desc";
|
||||
default:
|
||||
return "relevance";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse sort option from string
|
||||
*/
|
||||
public static SearchSortOption sort_option_from_string(string str) {
|
||||
switch (str) {
|
||||
case "relevance":
|
||||
return SearchSortOption.RELEVANCE;
|
||||
case "date_desc":
|
||||
return SearchSortOption.DATE_DESC;
|
||||
case "date_asc":
|
||||
return SearchSortOption.DATE_ASC;
|
||||
case "title_asc":
|
||||
return SearchSortOption.TITLE_ASC;
|
||||
case "title_desc":
|
||||
return SearchSortOption.TITLE_DESC;
|
||||
case "feed_asc":
|
||||
return SearchSortOption.FEED_ASC;
|
||||
case "feed_desc":
|
||||
return SearchSortOption.FEED_DESC;
|
||||
default:
|
||||
return SearchSortOption.RELEVANCE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any filters are set
|
||||
*/
|
||||
public bool has_filters() {
|
||||
return this.date_from != null ||
|
||||
this.date_to != null ||
|
||||
(this.feed_ids != null && this.feed_ids.length > 0) ||
|
||||
(this.authors != null && this.authors.length > 0) ||
|
||||
this.content_type != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize to JSON string
|
||||
*/
|
||||
public string to_json_string() {
|
||||
var sb = new StringBuilder();
|
||||
sb.append("{");
|
||||
|
||||
var first = true;
|
||||
if (this.date_from != null) {
|
||||
sb.append("\"dateFrom\":\"");
|
||||
sb.append(this.date_from);
|
||||
sb.append("\"");
|
||||
first = false;
|
||||
}
|
||||
if (this.date_to != null) {
|
||||
if (!first) sb.append(",");
|
||||
sb.append("\"dateTo\":\"");
|
||||
sb.append(this.date_to);
|
||||
sb.append("\"");
|
||||
first = false;
|
||||
}
|
||||
if (this.feed_ids != null && this.feed_ids.length > 0) {
|
||||
if (!first) sb.append(",");
|
||||
sb.append("\"feedIds\":[");
|
||||
for (var i = 0; i < this.feed_ids.length; i++) {
|
||||
if (i > 0) sb.append(",");
|
||||
sb.append("\"");
|
||||
sb.append(this.feed_ids[i]);
|
||||
sb.append("\"");
|
||||
}
|
||||
sb.append("]");
|
||||
first = false;
|
||||
}
|
||||
if (this.authors != null && this.authors.length > 0) {
|
||||
if (!first) sb.append(",");
|
||||
sb.append("\"authors\":[");
|
||||
for (var i = 0; i < this.authors.length; i++) {
|
||||
if (i > 0) sb.append(",");
|
||||
sb.append("\"");
|
||||
sb.append(this.authors[i]);
|
||||
sb.append("\"");
|
||||
}
|
||||
sb.append("]");
|
||||
first = false;
|
||||
}
|
||||
if (this.content_type != null) {
|
||||
if (!first) sb.append(",");
|
||||
sb.append("\"contentType\":\"");
|
||||
sb.append(this.get_content_type_string());
|
||||
sb.append("\"");
|
||||
}
|
||||
|
||||
sb.append("}");
|
||||
return sb.str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize from JSON string
|
||||
*/
|
||||
public static SearchFilters? from_json_string(string json_string) {
|
||||
var parser = new Json.Parser();
|
||||
try {
|
||||
if (!parser.load_from_data(json_string)) {
|
||||
return null;
|
||||
}
|
||||
} catch (Error e) {
|
||||
warning("Failed to parse JSON: %s", e.message);
|
||||
return null;
|
||||
}
|
||||
|
||||
return from_json_node(parser.get_root());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize from Json.Node
|
||||
*/
|
||||
public static SearchFilters? from_json_node(Json.Node node) {
|
||||
if (node.get_node_type() != Json.NodeType.OBJECT) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var obj = node.get_object();
|
||||
var filters = SearchFilters();
|
||||
|
||||
if (obj.has_member("dateFrom")) {
|
||||
filters.date_from = obj.get_string_member("dateFrom");
|
||||
}
|
||||
if (obj.has_member("dateTo")) {
|
||||
filters.date_to = obj.get_string_member("dateTo");
|
||||
}
|
||||
if (obj.has_member("feedIds")) {
|
||||
var array = obj.get_array_member("feedIds");
|
||||
var feed_ids = new string[array.get_length()];
|
||||
for (var i = 0; i < array.get_length(); i++) {
|
||||
feed_ids[i] = array.get_string_element(i);
|
||||
}
|
||||
filters.feed_ids = feed_ids;
|
||||
}
|
||||
if (obj.has_member("authors")) {
|
||||
var array = obj.get_array_member("authors");
|
||||
var authors = new string[array.get_length()];
|
||||
for (var i = 0; i < array.get_length(); i++) {
|
||||
authors[i] = array.get_string_element(i);
|
||||
}
|
||||
filters.authors = authors;
|
||||
}
|
||||
if (obj.has_member("contentType")) {
|
||||
filters.content_type = content_type_from_string(obj.get_string_member("contentType"));
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equality comparison
|
||||
*/
|
||||
public bool equals(SearchFilters other) {
|
||||
return this.date_from == other.date_from &&
|
||||
this.date_to == other.date_to &&
|
||||
this.feeds_equal(other.feed_ids) &&
|
||||
this.authors_equal(other.authors) &&
|
||||
this.content_type == other.content_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for feed_ids comparison
|
||||
*/
|
||||
private bool feeds_equal(string[]? other) {
|
||||
if (this.feed_ids == null && other == null) return true;
|
||||
if (this.feed_ids == null || other == null) return false;
|
||||
if (this.feed_ids.length != other.length) {
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < this.feed_ids.length; i++) {
|
||||
if (this.feed_ids[i] != other[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for authors comparison
|
||||
*/
|
||||
private bool authors_equal(string[]? other) {
|
||||
if (this.authors == null && other == null) return true;
|
||||
if (this.authors == null || other == null) return false;
|
||||
if (this.authors.length != other.length) {
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < this.authors.length; i++) {
|
||||
if (this.authors[i] != other[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchQuery - Represents a complete search query
|
||||
*/
|
||||
public struct RSSuper.SearchQuery {
|
||||
public string query { get; set; }
|
||||
public int page { get; set; }
|
||||
public int page_size { get; set; }
|
||||
public string filters_json { get; set; }
|
||||
public SearchSortOption sort { get; set; }
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public SearchQuery(string query, int page = 1, int page_size = 20,
|
||||
string? filters_json = null, SearchSortOption sort = SearchSortOption.RELEVANCE) {
|
||||
this.query = query;
|
||||
this.page = page;
|
||||
this.page_size = page_size;
|
||||
this.filters_json = filters_json;
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filters as struct
|
||||
*/
|
||||
public SearchFilters? get_filters() {
|
||||
if (this.filters_json == null || this.filters_json.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return SearchFilters.from_json_string(this.filters_json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set filters from struct
|
||||
*/
|
||||
public void set_filters(SearchFilters? filters) {
|
||||
if (filters == null) {
|
||||
this.filters_json = "";
|
||||
} else {
|
||||
this.filters_json = filters.to_json_string();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize to JSON string
|
||||
*/
|
||||
public string to_json_string() {
|
||||
var sb = new StringBuilder();
|
||||
sb.append("{");
|
||||
sb.append("\"query\":\"");
|
||||
sb.append(this.query);
|
||||
sb.append("\"");
|
||||
sb.append(",\"page\":%d".printf(this.page));
|
||||
sb.append(",\"pageSize\":%d".printf(this.page_size));
|
||||
if (this.filters_json != null && this.filters_json.length > 0) {
|
||||
sb.append(",\"filters\":");
|
||||
sb.append(this.filters_json);
|
||||
}
|
||||
sb.append(",\"sort\":\"");
|
||||
sb.append(SearchFilters.sort_option_to_string(this.sort));
|
||||
sb.append("\"");
|
||||
sb.append("}");
|
||||
return sb.str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize from JSON string
|
||||
*/
|
||||
public static SearchQuery? from_json_string(string json_string) {
|
||||
var parser = new Json.Parser();
|
||||
try {
|
||||
if (!parser.load_from_data(json_string)) {
|
||||
return null;
|
||||
}
|
||||
} catch (Error e) {
|
||||
warning("Failed to parse JSON: %s", e.message);
|
||||
return null;
|
||||
}
|
||||
|
||||
return from_json_node(parser.get_root());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize from Json.Node
|
||||
*/
|
||||
public static SearchQuery? from_json_node(Json.Node node) {
|
||||
if (node.get_node_type() != Json.NodeType.OBJECT) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var obj = node.get_object();
|
||||
|
||||
if (!obj.has_member("query")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var query = SearchQuery(obj.get_string_member("query"));
|
||||
|
||||
if (obj.has_member("page")) {
|
||||
query.page = (int)obj.get_int_member("page");
|
||||
}
|
||||
if (obj.has_member("pageSize")) {
|
||||
query.page_size = (int)obj.get_int_member("pageSize");
|
||||
}
|
||||
if (obj.has_member("filters")) {
|
||||
var generator = new Json.Generator();
|
||||
generator.set_root(obj.get_member("filters"));
|
||||
query.filters_json = generator.to_data(null);
|
||||
}
|
||||
if (obj.has_member("sort")) {
|
||||
query.sort = SearchFilters.sort_option_from_string(obj.get_string_member("sort"));
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equality comparison
|
||||
*/
|
||||
public bool equals(SearchQuery other) {
|
||||
return this.query == other.query &&
|
||||
this.page == other.page &&
|
||||
this.page_size == other.page_size &&
|
||||
this.filters_json == other.filters_json &&
|
||||
this.sort == other.sort;
|
||||
}
|
||||
}
|
||||
208
native-route/linux/src/models/search-result.vala
Normal file
208
native-route/linux/src/models/search-result.vala
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* SearchResult.vala
|
||||
*
|
||||
* Represents a search result item from the feed database.
|
||||
* Following GNOME HIG naming conventions and Vala/GObject patterns.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SearchResultType - Type of search result
|
||||
*/
|
||||
public enum RSSuper.SearchResultType {
|
||||
ARTICLE,
|
||||
FEED
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchResult - Represents a single search result
|
||||
*/
|
||||
public class RSSuper.SearchResult : Object {
|
||||
public string id { get; set; }
|
||||
public SearchResultType result_type { get; set; }
|
||||
public string title { get; set; }
|
||||
public string? snippet { get; set; }
|
||||
public string? link { get; set; }
|
||||
public string? feed_title { get; set; }
|
||||
public string? published { get; set; }
|
||||
public double score { get; set; }
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public SearchResult() {
|
||||
this.id = "";
|
||||
this.result_type = SearchResultType.ARTICLE;
|
||||
this.title = "";
|
||||
this.score = 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with initial values
|
||||
*/
|
||||
public SearchResult.with_values(string id, SearchResultType type, string title,
|
||||
string? snippet = null, string? link = null,
|
||||
string? feed_title = null, string? published = null,
|
||||
double score = 0.0) {
|
||||
this.id = id;
|
||||
this.result_type = type;
|
||||
this.title = title;
|
||||
this.snippet = snippet;
|
||||
this.link = link;
|
||||
this.feed_title = feed_title;
|
||||
this.published = published;
|
||||
this.score = score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get type as string
|
||||
*/
|
||||
public string get_type_string() {
|
||||
switch (this.result_type) {
|
||||
case SearchResultType.ARTICLE:
|
||||
return "article";
|
||||
case SearchResultType.FEED:
|
||||
return "feed";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse type from string
|
||||
*/
|
||||
public static SearchResultType type_from_string(string str) {
|
||||
switch (str) {
|
||||
case "article":
|
||||
return SearchResultType.ARTICLE;
|
||||
case "feed":
|
||||
return SearchResultType.FEED;
|
||||
default:
|
||||
return SearchResultType.ARTICLE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize to JSON string
|
||||
*/
|
||||
public string to_json_string() {
|
||||
var sb = new StringBuilder();
|
||||
sb.append("{");
|
||||
sb.append("\"id\":\"");
|
||||
sb.append(this.id);
|
||||
sb.append("\",\"type\":\"");
|
||||
sb.append(this.get_type_string());
|
||||
sb.append("\",\"title\":\"");
|
||||
sb.append(this.title);
|
||||
sb.append("\"");
|
||||
|
||||
if (this.snippet != null) {
|
||||
sb.append(",\"snippet\":\"");
|
||||
sb.append(this.snippet);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.link != null) {
|
||||
sb.append(",\"link\":\"");
|
||||
sb.append(this.link);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.feed_title != null) {
|
||||
sb.append(",\"feedTitle\":\"");
|
||||
sb.append(this.feed_title);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.published != null) {
|
||||
sb.append(",\"published\":\"");
|
||||
sb.append(this.published);
|
||||
sb.append("\"");
|
||||
}
|
||||
if (this.score != 0.0) {
|
||||
sb.append(",\"score\":%f".printf(this.score));
|
||||
}
|
||||
|
||||
sb.append("}");
|
||||
return sb.str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize from JSON string
|
||||
*/
|
||||
public static SearchResult? from_json_string(string json_string) {
|
||||
var parser = new Json.Parser();
|
||||
try {
|
||||
if (!parser.load_from_data(json_string)) {
|
||||
return null;
|
||||
}
|
||||
} catch (Error e) {
|
||||
warning("Failed to parse JSON: %s", e.message);
|
||||
return null;
|
||||
}
|
||||
|
||||
return from_json_node(parser.get_root());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize from Json.Node
|
||||
*/
|
||||
public static SearchResult? from_json_node(Json.Node node) {
|
||||
if (node.get_node_type() != Json.NodeType.OBJECT) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var obj = node.get_object();
|
||||
|
||||
if (!obj.has_member("id") || !obj.has_member("type") || !obj.has_member("title")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = new SearchResult();
|
||||
result.id = obj.get_string_member("id");
|
||||
result.result_type = SearchResult.type_from_string(obj.get_string_member("type"));
|
||||
result.title = obj.get_string_member("title");
|
||||
|
||||
if (obj.has_member("snippet")) {
|
||||
result.snippet = obj.get_string_member("snippet");
|
||||
}
|
||||
if (obj.has_member("link")) {
|
||||
result.link = obj.get_string_member("link");
|
||||
}
|
||||
if (obj.has_member("feedTitle")) {
|
||||
result.feed_title = obj.get_string_member("feedTitle");
|
||||
}
|
||||
if (obj.has_member("published")) {
|
||||
result.published = obj.get_string_member("published");
|
||||
}
|
||||
if (obj.has_member("score")) {
|
||||
result.score = obj.get_double_member("score");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equality comparison
|
||||
*/
|
||||
public bool equals(SearchResult? other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.id == other.id &&
|
||||
this.result_type == other.result_type &&
|
||||
this.title == other.title &&
|
||||
this.snippet == other.snippet &&
|
||||
this.link == other.link &&
|
||||
this.feed_title == other.feed_title &&
|
||||
this.published == other.published &&
|
||||
this.score == other.score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a human-readable summary
|
||||
*/
|
||||
public string get_summary() {
|
||||
if (this.feed_title != null) {
|
||||
return "[%s] %s - %s".printf(this.get_type_string(), this.feed_title, this.title);
|
||||
}
|
||||
return "[%s] %s".printf(this.get_type_string(), this.title);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user