From 7e97ca1c5425c2c6593ddcedd809898761467a1b Mon Sep 17 00:00:00 2001 From: Bleak Grey Date: Sat, 20 Jun 2020 13:04:58 +0300 Subject: [PATCH] Use GLib serialization (#180) --- meson.build | 5 +- src/API/Account.vala | 83 +++------------ src/API/Attachment.vala | 46 ++------- src/API/Conversation.vala | 6 +- src/API/Entity.vala | 143 ++++++++++++++++++++++++++ src/API/Mention.vala | 30 +----- src/API/Notification.vala | 56 ++--------- src/API/Relationship.vala | 19 +--- src/API/Status.vala | 161 ++++++------------------------ src/API/Tag.vala | 15 ++- src/Dialogs/Compose.vala | 6 +- src/InstanceAccount.vala | 100 +++---------------- src/Request.vala | 9 +- src/Services/Accounts.vala | 11 +- src/Services/IStreamListener.vala | 2 +- src/Services/Network.vala | 8 +- src/Services/Streams.vala | 43 ++++---- src/Utils.vala | 16 --- src/Views/Conversations.vala | 2 +- src/Views/ExpandedStatus.vala | 47 ++++----- src/Views/Federated.vala | 2 +- src/Views/Hashtag.vala | 2 +- src/Views/Local.vala | 2 +- src/Views/NewAccount.vala | 30 +++--- src/Views/Notifications.vala | 17 +--- src/Views/Profile.vala | 30 +++--- src/Views/Search.vala | 6 +- src/Views/Timeline.vala | 31 +++--- src/Widgets/Attachment/Item.vala | 18 ++-- src/Widgets/RichLabel.vala | 12 +-- src/Widgets/Status.vala | 8 +- 31 files changed, 362 insertions(+), 604 deletions(-) create mode 100644 src/API/Entity.vala delete mode 100644 src/Utils.vala diff --git a/meson.build b/meson.build index 3bca961..f26b752 100644 --- a/meson.build +++ b/meson.build @@ -17,7 +17,6 @@ asresources = gnome.compile_resources( ) libhandy_dep = dependency('libhandy-1', version: '>= 0.80.0') - if not libhandy_dep.found() libhandy = subproject( 'libhandy', @@ -39,7 +38,6 @@ executable( 'src/Desktop.vala', 'src/Drawing.vala', 'src/Html.vala', - 'src/Utils.vala', 'src/Request.vala', 'src/InstanceAccount.vala', 'src/Services/Streams.vala', @@ -59,6 +57,7 @@ executable( 'src/API/NotificationType.vala', 'src/API/Attachment.vala', 'src/API/Conversation.vala', + 'src/API/Entity.vala', 'src/Widgets/Widgetizable.vala', 'src/Widgets/Avatar.vala', 'src/Widgets/AccountsButton.vala', @@ -90,8 +89,8 @@ executable( dependency('glib-2.0', version: '>=2.30.0'), dependency('gee-0.8', version: '>=0.8.5'), dependency('granite', version: '>=5.2.0'), - dependency('json-glib-1.0'), dependency('libsoup-2.4'), + dependency('json-glib-1.0', version: '>=1.4.4'), libhandy_dep, ], install: true, diff --git a/src/API/Account.vala b/src/API/Account.vala index f67ea2a..c8c3599 100644 --- a/src/API/Account.vala +++ b/src/API/Account.vala @@ -1,6 +1,6 @@ -public class Tootle.API.Account : GLib.Object { +public class Tootle.API.Account : Entity { - public int64 id { get; set; } + public string id { get; set; } public string username { get; set; } public string acct { get; set; } public string? _display_name = null; @@ -19,69 +19,12 @@ public class Tootle.API.Account : GLib.Object { public string created_at { get; set; } public int64 followers_count { get; set; } public int64 following_count { get; set; } - public int64 posts_count { get; set; } + public int64 statuses_count { get; set; } public Relationship? rs { get; set; default = null; } - public Account (Json.Object obj) { - Object ( - id: int64.parse (obj.get_string_member ("id")), - username: obj.get_string_member ("username"), - acct: obj.get_string_member ("acct"), - display_name: obj.get_string_member ("display_name"), - note: obj.get_string_member ("note"), - avatar: obj.get_string_member ("avatar"), - header: obj.get_string_member ("header"), - url: obj.get_string_member ("url"), - created_at: obj.get_string_member ("created_at"), - - followers_count: obj.get_int_member ("followers_count"), - following_count: obj.get_int_member ("following_count"), - posts_count: obj.get_int_member ("statuses_count") - ); - - if (obj.has_member ("fields")) { - obj.get_array_member ("fields").foreach_element ((array, i, node) => { - var field_obj = node.get_object (); - var field_name = field_obj.get_string_member ("name"); - var field_val = field_obj.get_string_member ("value"); - note += "\n"; - note += field_name + ": "; - note += field_val; - }); - } - } - - public virtual Json.Node? serialize () { - var builder = new Json.Builder (); - builder.begin_object (); - builder.set_member_name ("id"); - builder.add_string_value (id.to_string ()); - builder.set_member_name ("created_at"); - builder.add_string_value (created_at); - builder.set_member_name ("following_count"); - builder.add_int_value (following_count); - builder.set_member_name ("followers_count"); - builder.add_int_value (followers_count); - builder.set_member_name ("statuses_count"); - builder.add_int_value (posts_count); - builder.set_member_name ("display_name"); - builder.add_string_value (display_name); - builder.set_member_name ("username"); - builder.add_string_value (username); - builder.set_member_name ("acct"); - builder.add_string_value (acct); - builder.set_member_name ("note"); - builder.add_string_value (note); - builder.set_member_name ("header"); - builder.add_string_value (header); - builder.set_member_name ("avatar"); - builder.add_string_value (avatar); - builder.set_member_name ("url"); - builder.add_string_value (url); - - builder.end_object (); - return builder.get_root (); - } + public static Account from (Json.Node node) throws Error { + return Entity.from_json (typeof (API.Account), node) as API.Account; + } public bool is_self () { return id == accounts.active.id; @@ -92,7 +35,7 @@ public class Tootle.API.Account : GLib.Object { .with_account (accounts.active) .with_param ("id", id.to_string ()) .then_parse_array (node => { - rs = new Relationship (node.get_object ()); + rs = API.Relationship.from (node); }) .on_error (network.on_error) .exec (); @@ -103,8 +46,8 @@ public class Tootle.API.Account : GLib.Object { return new Request.POST (@"/api/v1/accounts/$id/$action") .with_account (accounts.active) .then ((sess, msg) => { - var root = network.parse (msg); - rs = new Relationship (root); + var node = network.parse_node (msg); + rs = API.Relationship.from (node); }) .on_error (network.on_error) .exec (); @@ -115,8 +58,8 @@ public class Tootle.API.Account : GLib.Object { return new Request.POST (@"/api/v1/accounts/$id/$action") .with_account (accounts.active) .then ((sess, msg) => { - var root = network.parse (msg); - rs = new Relationship (root); + var node = network.parse_node (msg); + rs = API.Relationship.from (node); }) .on_error (network.on_error) .exec (); @@ -127,8 +70,8 @@ public class Tootle.API.Account : GLib.Object { return new Request.POST (@"/api/v1/accounts/$id/$action") .with_account (accounts.active) .then ((sess, msg) => { - var root = network.parse (msg); - rs = new Relationship (root); + var node = network.parse_node (msg); + rs = API.Relationship.from (node); }) .on_error (network.on_error) .exec (); diff --git a/src/API/Attachment.vala b/src/API/Attachment.vala index 7c40a41..7fdfdb8 100644 --- a/src/API/Attachment.vala +++ b/src/API/Attachment.vala @@ -1,45 +1,13 @@ -public class Tootle.API.Attachment : GLib.Object { +public class Tootle.API.Attachment : Entity { - public int64 id { get; construct set; } + public string id { get; set; } public string kind { get; set; } public string url { get; set; } - public string? description { get; set; default = null; } - - public string? _preview_url = null; + public string? description { get; set; } + public string? _preview_url { get; set; } public string preview_url { - set { this._preview_url = value; } - get { return (_preview_url == null || _preview_url == "") ? url : _preview_url; } - } - - public Attachment (Json.Object obj) { - Object ( - id: int64.parse (obj.get_string_member ("id")), - kind: obj.get_string_member ("type"), - preview_url: obj.get_string_member ("preview_url"), - url: obj.get_string_member ("url"), - description: obj.get_string_member ("description") - ); - } - - public Json.Node? serialize () { - var builder = new Json.Builder (); - builder.begin_object (); - builder.set_member_name ("id"); - builder.add_string_value (id.to_string ()); - builder.set_member_name ("type"); - builder.add_string_value (kind); - builder.set_member_name ("url"); - builder.add_string_value (url); - builder.set_member_name ("preview_url"); - builder.add_string_value (preview_url); - - if (description != null) { - builder.set_member_name ("description"); - builder.add_string_value (description); - } - - builder.end_object (); - return builder.get_root (); - } + set { this._preview_url = value; } + get { return (this._preview_url == null || this._preview_url == "") ? url : _preview_url; } + } } diff --git a/src/API/Conversation.vala b/src/API/Conversation.vala index 0f8a1b7..f85b7ad 100644 --- a/src/API/Conversation.vala +++ b/src/API/Conversation.vala @@ -1,10 +1,6 @@ -public class Tootle.API.Conversation : GLib.Object, Json.Serializable, Widgetizable { +public class Tootle.API.Conversation : Entity, Widgetizable { public string id { get; construct set; } public bool unread { get; set; default = false; } - public Conversation () { - GLib.Object (); - } - } diff --git a/src/API/Entity.vala b/src/API/Entity.vala new file mode 100644 index 0000000..9d31eaa --- /dev/null +++ b/src/API/Entity.vala @@ -0,0 +1,143 @@ +using Json; + +public class Tootle.Entity : GLib.Object, Widgetizable, Json.Serializable { + + public static string[] ignore_props = {"formal", "handle", "short-instance", "has-spoiler"}; + + public new ParamSpec[] list_properties () { + ParamSpec[] specs = {}; + foreach (ParamSpec spec in get_class ().list_properties ()) { + if (!(spec.name in ignore_props)) + specs += spec; + } + return specs; + } + + + public void patch (GLib.Object with) { + var props = with.get_class ().list_properties (); + foreach (var prop in props) { + var name = prop.get_name (); + var defined = get_class ().find_property (name) != null; + var forbidden = name in ignore_props; + if (defined && !forbidden) { + var val = Value (prop.value_type); + with.get_property (name, ref val); + base.set_property (name, val); + } + } + } + + public static Entity from_json (Type type, Json.Node? node) throws Oopsie { + if (node == null) + throw new Oopsie.PARSING (@"Received Json.Node for $(type.name ()) is null!"); + + var obj = node.get_object (); + if (obj == null) + throw new Oopsie.PARSING (@"Received Json.Node for $(type.name ()) is not a Json.Object!"); + + var kind = obj.get_member ("type"); + if (kind != null) { + obj.set_member ("kind", kind); + obj.remove_member ("type"); + } + + return Json.gobject_deserialize (type, node) as Entity; + } + + public Json.Node to_json () { + return Json.gobject_serialize (this); + } + + public string to_json_data () { + size_t len; + return Json.gobject_to_data (this, out len); + } + + public override bool deserialize_property (string prop, out Value val, ParamSpec spec, Json.Node node) { + // debug (@"deserializing $prop of type $(val.type_name ())"); + var success = default_deserialize_property (prop, out val, spec, node); + + var type = spec.value_type; + if (val.type () == Type.INVALID) { // Fix for glib-json < 1.5.1 + val.init (type); + spec.set_value_default (ref val); + type = spec.value_type; + } + + if (type.is_a (typeof (Gee.ArrayList))) { + Type contains; + switch (prop) { + case "media-attachments": + contains = typeof (API.Attachment); + break; + case "mentions": + contains = typeof (API.Mention); + break; + default: + contains = typeof (Entity); + break; + } + return des_list (out val, node, contains); + } + else if (type.is_a (typeof (API.NotificationType))) + return des_notification_type (out val, node); + + return success; + } + + static bool des_notification_type (out Value val, Json.Node node) { + var str = node.get_string (); + val = API.NotificationType.from_string (str); + return true; + } + + static bool des_list (out Value val, Json.Node node, Type type) { + if (!node.is_null ()) { + var arr = new Gee.ArrayList (); + node.get_array ().foreach_element ((array, i, elem) => { + var obj = Entity.from_json (type, elem); + arr.add (obj); + }); + val = arr; + } + return true; + } + + public override Json.Node serialize_property (string prop, Value val, ParamSpec spec) { + var type = spec.value_type; + // debug (@"serializing $prop of type $(val.type_name ())"); + + if (type.is_a (typeof (Gee.ArrayList))) + return ser_list (prop, val, spec); + if (type.is_a (typeof (API.NotificationType))) + return ser_notification_type (prop, val, spec); + + return default_serialize_property (prop, val, spec); + } + + static Json.Node ser_notification_type (string prop, Value val, ParamSpec spec) { + var enum_val = (API.NotificationType) val; + var node = new Json.Node (NodeType.VALUE); + node.set_string (enum_val.to_string ()); + return node; + } + + static Json.Node ser_list (string prop, Value val, ParamSpec spec) { + var list = (Gee.ArrayList) val; + if (list == null) + return new Json.Node (NodeType.NULL); + + var arr = new Json.Array (); + list.@foreach (e => { + var enode = e.to_json (); + arr.add_element (enode); + return true; + }); + + var node = new Json.Node (NodeType.ARRAY); + node.set_array (arr); + return node; + } + +} diff --git a/src/API/Mention.vala b/src/API/Mention.vala index cee2fce..a84ddd3 100644 --- a/src/API/Mention.vala +++ b/src/API/Mention.vala @@ -1,20 +1,11 @@ -public class Tootle.API.Mention : GLib.Object { +public class Tootle.API.Mention : Entity { - public int64 id { get; construct set; } + public string id { get; construct set; } public string username { get; construct set; } public string acct { get; construct set; } public string url { get; construct set; } - public Mention (Json.Object obj) { - Object ( - id: int64.parse (obj.get_string_member ("id")), - username: obj.get_string_member ("username"), - acct: obj.get_string_member ("acct"), - url: obj.get_string_member ("url") - ); - } - - public Mention.from_account (Account account) { + public Mention.from_account (API.Account account) { Object ( id: account.id, username: account.username, @@ -23,19 +14,4 @@ public class Tootle.API.Mention : GLib.Object { ); } - public Json.Node? serialize () { - var builder = new Json.Builder (); - builder.begin_object (); - builder.set_member_name ("id"); - builder.add_string_value (id.to_string ()); - builder.set_member_name ("username"); - builder.add_string_value (username); - builder.set_member_name ("acct"); - builder.add_string_value (acct); - builder.set_member_name ("url"); - builder.add_string_value (url); - builder.end_object (); - return builder.get_root (); - } - } diff --git a/src/API/Notification.vala b/src/API/Notification.vala index 6586770..7a5cb62 100644 --- a/src/API/Notification.vala +++ b/src/API/Notification.vala @@ -1,59 +1,15 @@ -public class Tootle.API.Notification : GLib.Object, Widgetizable { +public class Tootle.API.Notification : Entity, Widgetizable { - public int64 id { get; construct set; } - public Account account { get; construct set; } - - public NotificationType kind { get; set; } + public string id { get; set; } + public API.Account account { get; set; } + public API.NotificationType kind { get; set; } public string created_at { get; set; } - public Status? status { get; set; default = null; } - - public Notification (Json.Object obj) throws Oopsie { - Object ( - id: int64.parse (obj.get_string_member ("id")), - kind: NotificationType.from_string (obj.get_string_member ("type")), - created_at: obj.get_string_member ("created_at"), - account: new Account (obj.get_object_member ("account")) - ); - - if (obj.has_member ("status")) - status = new Status (obj.get_object_member ("status")); - } - - public Notification.follow_request (Json.Object obj) { - Object ( - id: 0, - kind: NotificationType.FOLLOW_REQUEST, - account: new Account (obj) - ); - } + public API.Status? status { get; set; default = null; } public override Gtk.Widget to_widget () { return new Widgets.Notification (this); } - public Json.Node? serialize () { - var builder = new Json.Builder (); - builder.begin_object (); - builder.set_member_name ("id"); - builder.add_string_value (id.to_string ()); - builder.set_member_name ("type"); - builder.add_string_value (kind.to_string ()); - builder.set_member_name ("created_at"); - builder.add_string_value (created_at); - - if (status != null) { - builder.set_member_name ("status"); - builder.add_value (status.serialize ()); - } - if (account != null) { - builder.set_member_name ("account"); - builder.add_value (account.serialize ()); - } - - builder.end_object (); - return builder.get_root (); - } - public Soup.Message? dismiss () { if (kind == NotificationType.WATCHLIST) { if (accounts.active.cached_notifications.remove (this)) @@ -66,7 +22,7 @@ public class Tootle.API.Notification : GLib.Object, Widgetizable { var req = new Request.POST ("/api/v1/notifications/dismiss") .with_account (accounts.active) - .with_param ("id", id.to_string ()) + .with_param ("id", id) .exec (); return req; } diff --git a/src/API/Relationship.vala b/src/API/Relationship.vala index 3d6580a..9e40d6f 100644 --- a/src/API/Relationship.vala +++ b/src/API/Relationship.vala @@ -1,6 +1,6 @@ -public class Tootle.API.Relationship : GLib.Object { +public class Tootle.API.Relationship : Entity { - public int64 id { get; construct set; } + public string id { get; set; } public bool following { get; set; default = false; } public bool followed_by { get; set; default = false; } public bool muting { get; set; default = false; } @@ -9,17 +9,8 @@ public class Tootle.API.Relationship : GLib.Object { public bool blocking { get; set; default = false; } public bool domain_blocking { get; set; default = false; } - public Relationship (Json.Object obj) { - Object ( - id: int64.parse (obj.get_string_member ("id")), - following: obj.get_boolean_member ("following"), - followed_by: obj.get_boolean_member ("followed_by"), - blocking: obj.get_boolean_member ("blocking"), - muting: obj.get_boolean_member ("muting"), - muting_notifications: obj.get_boolean_member ("muting_notifications"), - requested: obj.get_boolean_member ("requested"), - domain_blocking: obj.get_boolean_member ("domain_blocking") - ); - } + public static Relationship from (Json.Node node) throws Error { + return Entity.from_json (typeof (API.Relationship), node) as API.Relationship; + } } diff --git a/src/API/Status.vala b/src/API/Status.vala index bc7c15c..89d3c41 100644 --- a/src/API/Status.vala +++ b/src/API/Status.vala @@ -1,11 +1,10 @@ using Gee; -public class Tootle.API.Status : GLib.Object, Widgetizable { +public class Tootle.API.Status : Entity, Widgetizable { - public int64 id { get; construct set; } //TODO: IDs are no longer guaranteed to be numbers. Replace with strings. - public API.Account account { get; construct set; } + public string id { get; set; } + public API.Account account { get; set; } public string uri { get; set; } - public string? url { get; set; default = null; } public string? spoiler_text { get; set; default = null; } public string? in_reply_to_id { get; set; default = null; } public string? in_reply_to_account_id { get; set; default = null; } @@ -15,93 +14,52 @@ public class Tootle.API.Status : GLib.Object, Widgetizable { public int64 favourites_count { get; set; default = 0; } public string created_at { get; set; default = "0"; } public bool reblogged { get; set; default = false; } - public bool favorited { get; set; default = false; } + public bool favourited { get; set; default = false; } public bool sensitive { get; set; default = false; } public bool muted { get; set; default = false; } public bool pinned { get; set; default = false; } - public API.Visibility visibility { get; set; default = API.Visibility.PUBLIC; } + public API.Visibility visibility { get; set; default = settings.default_post_visibility; } public API.Status? reblog { get; set; default = null; } public ArrayList? mentions { get; set; default = null; } - public ArrayList? attachments { get; set; default = null; } + public ArrayList? media_attachments { get; set; default = null; } + + public string? _url { get; set; } + public string url { + owned get { return this.get_modified_url (); } + set { this._url = value; } + } + string get_modified_url () { + if (this._url == null) { + return this.uri.replace ("/activity", ""); + } + return this._url; + } public Status formal { get { return reblog ?? this; } } - public bool has_spoiler { + public bool has_spoiler { get { - return formal.spoiler_text != null || formal.sensitive; + return formal.sensitive || + !(formal.spoiler_text == null || formal.spoiler_text == ""); } - } - - public Status (Json.Object obj) { - Object ( - id: int64.parse (obj.get_string_member ("id")), - account: new Account (obj.get_object_member ("account")), - uri: obj.get_string_member ("uri"), - created_at: obj.get_string_member ("created_at"), - content: Html.simplify ( obj.get_string_member ("content")), - sensitive: obj.get_boolean_member ("sensitive"), - visibility: Visibility.from_string (obj.get_string_member ("visibility")), - - in_reply_to_id: obj.get_string_member ("in_reply_to_id") ?? null, - in_reply_to_account_id: obj.get_string_member ("in_reply_to_account_id") ?? null, - - replies_count: obj.get_int_member ("replies_count"), - reblogs_count: obj.get_int_member ("reblogs_count"), - favourites_count: obj.get_int_member ("favourites_count") - ); - - if (obj.has_member ("url")) - url = obj.get_string_member ("url"); - else - url = obj.get_string_member ("uri").replace ("/activity", ""); - - var spoiler = obj.get_string_member ("spoiler_text"); - if (spoiler != "") - spoiler_text = Html.simplify (spoiler); - - if (obj.has_member ("reblogged")) - reblogged = obj.get_boolean_member ("reblogged"); - if (obj.has_member ("favourited")) - favorited = obj.get_boolean_member ("favourited"); - if (obj.has_member ("muted")) - muted = obj.get_boolean_member ("muted"); - if (obj.has_member ("pinned")) - pinned = obj.get_boolean_member ("pinned"); - - if (obj.has_member ("reblog") && obj.get_null_member("reblog") != true) - reblog = new Status (obj.get_object_member ("reblog")); - - obj.get_array_member ("mentions").foreach_element ((array, i, node) => { - var entity = node.get_object (); - if (entity != null) { - if (mentions == null) - mentions = new ArrayList (); - mentions.add (new API.Mention (entity)); - } - }); - - obj.get_array_member ("media_attachments").foreach_element ((array, i, node) => { - var entity = node.get_object (); - if (entity != null) { - if (attachments == null) - attachments = new ArrayList (); - attachments.add (new API.Attachment (entity)); - } - }); } + public static Status from (Json.Node node) throws Error { + return Entity.from_json (typeof (API.Status), node) as API.Status; + } + public Status.empty () { Object ( - id: 0, + id: "", visibility: settings.default_post_visibility ); } public Status.from_account (API.Account account) { Object ( - id: 0, + id: "", account: account, created_at: account.created_at ); @@ -121,61 +79,6 @@ public class Tootle.API.Status : GLib.Object, Widgetizable { return w; } - public Json.Node? serialize () { - var builder = new Json.Builder (); - builder.begin_object (); - builder.set_member_name ("id"); - builder.add_string_value (id.to_string ()); - builder.set_member_name ("uri"); - builder.add_string_value (uri); - builder.set_member_name ("url"); - builder.add_string_value (url); - builder.set_member_name ("content"); - builder.add_string_value (content); - builder.set_member_name ("created_at"); - builder.add_string_value (created_at); - builder.set_member_name ("visibility"); - builder.add_string_value (visibility.to_string ()); - builder.set_member_name ("sensitive"); - builder.add_boolean_value (sensitive); - builder.set_member_name ("sensitive"); - builder.add_boolean_value (sensitive); - builder.set_member_name ("replies_count"); - builder.add_int_value (replies_count); - builder.set_member_name ("favourites_count"); - builder.add_int_value (favourites_count); - builder.set_member_name ("reblogs_count"); - builder.add_int_value (reblogs_count); - builder.set_member_name ("account"); - builder.add_value (account.serialize ()); - - if (spoiler_text != null) { - builder.set_member_name ("spoiler_text"); - builder.add_string_value (spoiler_text); - } - if (reblog != null) { - builder.set_member_name ("reblog"); - builder.add_value (reblog.serialize ()); - } - if (attachments != null) { - builder.set_member_name ("media_attachments"); - builder.begin_array (); - foreach (API.Attachment attachment in attachments) - builder.add_value (attachment.serialize ()); - builder.end_array (); - } - if (mentions != null) { - builder.set_member_name ("mentions"); - builder.begin_array (); - foreach (API.Mention mention in mentions) - builder.add_value (mention.serialize ()); - builder.end_array (); - } - - builder.end_object (); - return builder.get_root (); - } - public bool is_owned (){ return formal.account.id == accounts.active.id; } @@ -201,12 +104,10 @@ public class Tootle.API.Status : GLib.Object, Widgetizable { public void action (string action, owned Network.ErrorCallback? err = network.on_error) { new Request.POST (@"/api/v1/statuses/$(formal.id)/$action") .with_account (accounts.active) - .then_parse_obj (obj => { - var status = new API.Status (obj).formal; - formal.reblogged = status.reblogged; - formal.favorited = status.favorited; - formal.muted = status.muted; - formal.pinned = status.pinned; + .then ((sess, msg) => { + var node = network.parse_node (msg); + var upd = API.Status.from (node).formal; + patch (upd); }) .on_error ((status, reason) => err (status, reason)) .exec (); diff --git a/src/API/Tag.vala b/src/API/Tag.vala index bc11769..b6d2d49 100644 --- a/src/API/Tag.vala +++ b/src/API/Tag.vala @@ -1,13 +1,10 @@ -public class Tootle.API.Tag : GLib.Object { +public class Tootle.API.Tag : Entity { - public string name { get; construct set; } - public string url { get; construct set; } + public string name { get; set; } + public string url { get; set; } - public Tag (Json.Object obj) { - Object ( - name: obj.get_string_member ("name"), - url: obj.get_string_member ("url") - ); - } + public static Tag from (Json.Node node) throws Error { + return Entity.from_json (typeof (API.Tag), node) as API.Tag; + } } diff --git a/src/Dialogs/Compose.vala b/src/Dialogs/Compose.vala index 726eb5b..bded204 100644 --- a/src/Dialogs/Compose.vala +++ b/src/Dialogs/Compose.vala @@ -122,7 +122,7 @@ public class Tootle.Dialogs.Compose : Window { visibility_button.sensitive = false; box.sensitive = false; - if (status.id > 0) { + if (status.id != "") { info ("Removing old status..."); status.poof (publish, on_error); } @@ -152,8 +152,8 @@ public class Tootle.Dialogs.Compose : Window { req.with_param ("in_reply_to_account_id", status.in_reply_to_account_id); req.then ((sess, mess) => { - var root = network.parse (mess); - var status = new API.Status (root); + var node = network.parse_node (mess); + var status = API.Status.from (node); info ("OK: status id is %s", status.id.to_string ()); destroy (); }) diff --git a/src/InstanceAccount.vala b/src/InstanceAccount.vala index e3f380a..ba1f385 100644 --- a/src/InstanceAccount.vala +++ b/src/InstanceAccount.vala @@ -6,7 +6,7 @@ public class Tootle.InstanceAccount : API.Account, IStreamListener { public string instance { get; set; } public string client_id { get; set; } public string client_secret { get; set; } - public string token { get; set; } + public string access_token { get; set; } public int64 last_seen_notification { get; set; default = 0; } public bool has_unread_notifications { get; set; default = false; } @@ -25,27 +25,11 @@ public class Tootle.InstanceAccount : API.Account, IStreamListener { } } - public InstanceAccount (Json.Object obj) { - Object ( - username: obj.get_string_member ("username"), - instance: obj.get_string_member ("instance"), - client_id: obj.get_string_member ("id"), - client_secret: obj.get_string_member ("secret"), - token: obj.get_string_member ("access_token"), - last_seen_notification: obj.get_int_member ("last_seen_notification"), - has_unread_notifications: obj.get_boolean_member ("has_unread_notifications") - ); - - var cached = obj.get_object_member ("cached_profile"); - var account = new API.Account (cached); - patch (account); - - var notifications = obj.get_array_member ("cached_notifications"); - notifications.foreach_element ((arr, i, node) => { - var notification = new API.Notification (node.get_object ()); - cached_notifications.add (notification); - }); + public static InstanceAccount from (Json.Node node) throws Error { + return Entity.from_json (typeof (InstanceAccount), node) as InstanceAccount; + } + public InstanceAccount () { on_notification.connect (show_notification); } ~InstanceAccount () { @@ -53,25 +37,25 @@ public class Tootle.InstanceAccount : API.Account, IStreamListener { } public InstanceAccount.empty (string instance){ - Object (id: 0, instance: instance); + Object ( + id: "", + instance: instance + ); } public InstanceAccount.from_account (API.Account account) { - Object (id: account.id); + Object ( + id: account.id + ); patch (account); } - public InstanceAccount patch (API.Account account) { - Utils.merge (this, account); - return this; - } - public bool is_current () { - return accounts.active.token == token; + return accounts.active.access_token == access_token; } public string get_stream_url () { - return @"$instance/api/v1/streaming/?stream=user&access_token=$token"; + return @"$instance/api/v1/streaming/?stream=user&access_token=$access_token"; } public void subscribe () { @@ -82,45 +66,6 @@ public class Tootle.InstanceAccount : API.Account, IStreamListener { streams.unsubscribe (stream, this); } - public override Json.Node? serialize () { - var builder = new Json.Builder (); - builder.begin_object (); - - builder.set_member_name ("hash"); - builder.add_string_value ("test"); - builder.set_member_name ("username"); - builder.add_string_value (username); - builder.set_member_name ("instance"); - builder.add_string_value (instance); - builder.set_member_name ("id"); - builder.add_string_value (client_id); - builder.set_member_name ("secret"); - builder.add_string_value (client_secret); - builder.set_member_name ("access_token"); - builder.add_string_value (token); - builder.set_member_name ("last_seen_notification"); - builder.add_int_value (last_seen_notification); - builder.set_member_name ("has_unread_notifications"); - builder.add_boolean_value (has_unread_notifications); - - var cached_profile = base.serialize (); - builder.set_member_name ("cached_profile"); - builder.add_value (cached_profile); - - builder.set_member_name ("cached_notifications"); - builder.begin_array (); - cached_notifications.@foreach (notification => { - var node = notification.serialize (); - if (node != null) - builder.add_value (node); - return true; - }); - builder.end_array (); - - builder.end_object (); - return builder.get_root (); - } - protected void show_notification (API.Notification obj) { var title = Html.remove_tags (obj.kind.get_desc (obj.account)); var notification = new GLib.Notification (title); @@ -140,21 +85,4 @@ public class Tootle.InstanceAccount : API.Account, IStreamListener { } } - // protected void on_status_added (API.Status status) { //TODO: Watchlist - // if (!is_current ()) - // return; - - // watchlist.users.@foreach (item => { - // var acct = status.account.acct; - // if (item == acct || item == "@" + acct) { - // var obj = new API.Notification (-1); - // obj.kind = API.NotificationType.WATCHLIST; - // obj.account = status.account; - // obj.status = status; - // on_notification (obj); - // } - // return true; - // }); - // } - } diff --git a/src/Request.vala b/src/Request.vala index 1d0276b..c60d437 100644 --- a/src/Request.vala +++ b/src/Request.vala @@ -3,7 +3,7 @@ using Gee; public class Tootle.Request : Soup.Message { - public string url { construct set; get; } + public string url { set; get; } private Network.SuccessCallback? cb; private Network.ErrorCallback? error_cb; private HashMap? pars; @@ -81,11 +81,10 @@ public class Tootle.Request : Soup.Message { if (needs_token) { if (account == null) { - warning (@"No account found for: $method: $url$parameters"); + warning (@"No account was specified or found for $method: $url$parameters"); return this; } - - request_headers.append ("Authorization", @"Bearer $(account.token)"); + request_headers.append ("Authorization", @"Bearer $(account.access_token)"); } if (!("://" in url)) { @@ -95,7 +94,7 @@ public class Tootle.Request : Soup.Message { this.uri = new URI (url + "" + parameters); url = uri.to_string (false); - info (@"$method: $url"); + debug (@"$method: $url"); network.queue (this, (owned) cb, (owned) error_cb); return this; diff --git a/src/Services/Accounts.vala b/src/Services/Accounts.vala index 99c217b..e47ac64 100644 --- a/src/Services/Accounts.vala +++ b/src/Services/Accounts.vala @@ -19,9 +19,9 @@ public class Tootle.Accounts : GLib.Object { new Request.GET ("/api/v1/accounts/verify_credentials") .with_account (acc) .then ((sess, mess) => { - var root = network.parse (mess); - var profile = new API.Account (root); - acc.patch (profile); + var node = network.parse_node (mess); + var updated = API.Account.from (node); + acc.patch (updated); info ("OK: Token is valid"); active = acc; settings.current_account = id; @@ -89,7 +89,7 @@ public class Tootle.Accounts : GLib.Object { var builder = new Json.Builder (); builder.begin_array (); saved.foreach ((acc) => { - var node = acc.serialize (); + var node = acc.to_json (); builder.add_value (node); return true; }); @@ -124,8 +124,7 @@ public class Tootle.Accounts : GLib.Object { var array = parser.get_root ().get_array (); array.foreach_element ((_arr, _i, node) => { - var obj = node.get_object (); - var account = new InstanceAccount (obj); + var account = InstanceAccount.from (node); if (account != null) { saved.add (account); account.subscribe (); diff --git a/src/Services/IStreamListener.vala b/src/Services/IStreamListener.vala index a4e0260..7033829 100644 --- a/src/Services/IStreamListener.vala +++ b/src/Services/IStreamListener.vala @@ -1,6 +1,6 @@ public interface Tootle.IStreamListener : GLib.Object { - public signal void on_status_removed (int64 id); + public signal void on_status_removed (string id); public signal void on_status_added (API.Status s); public signal void on_notification (API.Notification n); diff --git a/src/Services/Network.vala b/src/Services/Network.vala index 2d3ed83..8d46906 100644 --- a/src/Services/Network.vala +++ b/src/Services/Network.vala @@ -79,10 +79,14 @@ public class Tootle.Network : GLib.Object { app.error (_("Network Error"), message); } - public Json.Object parse (Soup.Message msg) throws Error { + public Json.Node parse_node (Soup.Message msg) throws Error { var parser = new Json.Parser (); parser.load_from_data ((string) msg.response_body.flatten ().data, -1); - return parser.get_root ().get_object (); + return parser.get_root (); + } + + public Json.Object parse (Soup.Message msg) throws Error { + return parse_node (msg).get_object (); } } diff --git a/src/Services/Streams.vala b/src/Services/Streams.vala index 40e7551..6160fd9 100644 --- a/src/Services/Streams.vala +++ b/src/Services/Streams.vala @@ -111,65 +111,68 @@ public class Tootle.Streams : Object { return s.get_type ().name (); } - static void decode (Bytes bytes, out string event, out Json.Object root) throws Error { + static void decode (Bytes bytes, out Json.Node root, out Json.Object obj, out string event) throws Error { var msg = (string) bytes.get_data (); var parser = new Json.Parser (); parser.load_from_data (msg, -1); - root = parser.get_root ().get_object (); - event = root.get_string_member ("event"); + root = parser.steal_root (); + obj = root.get_object (); + event = obj.get_string_member ("event"); } - static Json.Object sanitize (Json.Object root) { - var payload = root.get_string_member ("payload"); - var sanitized = Soup.URI.decode (payload); + static Json.Node payload (Json.Object obj) { + var payload = obj.get_string_member ("payload"); + var data = Soup.URI.decode (payload); var parser = new Json.Parser (); - parser.load_from_data (sanitized, -1); - return parser.get_root ().get_object (); + parser.load_from_data (data, -1); + return parser.steal_root (); } static void emit (Bytes bytes, Connection c) throws Error { if (!settings.live_updates) return; - string e; - Json.Object root; - decode (bytes, out e, out root); + Json.Node root; + Json.Object root_obj; + string ev; + decode (bytes, out root, out root_obj, out ev); // c.subscribers.@foreach (s => { // warning ("%s: %s for %s", c.name, e, get_subscriber_name (s)); // return false; // }); - switch (e) { + switch (ev) { case "update": - var obj = new API.Status (sanitize (root)); + var node = payload (root_obj); + var status = Entity.from_json (typeof (API.Status), node) as API.Status; c.subscribers.@foreach (s => { - s.on_status_added (obj); + s.on_status_added (status); return true; }); break; case "delete": - var id = int64.parse (root.get_string_member ("payload")); + var id = root_obj.get_string_member ("payload"); c.subscribers.@foreach (s => { s.on_status_removed (id); return true; }); break; case "notification": - var obj = new API.Notification (sanitize (root)); + var node = payload (root_obj); + var notif = Entity.from_json (typeof (API.Notification), node) as API.Notification; c.subscribers.@foreach (s => { - s.on_notification (obj); + s.on_notification (notif); return true; }); break; default: - warning (@"Unknown websocket event: \"$e\". Ignoring."); + warning (@"Unknown websocket event: \"$ev\". Ignoring."); break; } } - public void force_delete (int64 id) { - warning (@"Force removing status id $id"); + public void force_delete (string id) { connections.get_values ().@foreach (c => { c.subscribers.@foreach (s => { s.on_status_removed (id); diff --git a/src/Utils.vala b/src/Utils.vala deleted file mode 100644 index 24571bf..0000000 --- a/src/Utils.vala +++ /dev/null @@ -1,16 +0,0 @@ -public class Tootle.Utils { - - public static void merge (GLib.Object what, GLib.Object with) { - var props = with.get_class ().list_properties (); - foreach (var prop in props) { - var name = prop.get_name (); - var defined = what.get_class ().find_property (name) != null; - if (defined) { - var val = Value (prop.value_type); - with.get_property (name, ref val); - what.set_property (name, val) ; - } - } - } - -} diff --git a/src/Views/Conversations.vala b/src/Views/Conversations.vala index 9cff7eb..e14247e 100644 --- a/src/Views/Conversations.vala +++ b/src/Views/Conversations.vala @@ -10,7 +10,7 @@ public class Tootle.Views.Conversations : Views.Timeline { } public override string? get_stream_url () { - return @"/api/v1/streaming/?stream=direct&access_token=$(accounts.active.token)"; + return @"/api/v1/streaming/?stream=direct&access_token=$(account.access_token)"; } } diff --git a/src/Views/ExpandedStatus.vala b/src/Views/ExpandedStatus.vala index a7b5fed..4ddb559 100644 --- a/src/Views/ExpandedStatus.vala +++ b/src/Views/ExpandedStatus.vala @@ -4,24 +4,23 @@ public class Tootle.Views.ExpandedStatus : Views.Base, IAccountListener { public API.Status root_status { get; construct set; } protected InstanceAccount? account = null; - protected Widgets.Status root_widget; + protected Widget root_widget; public ExpandedStatus (API.Status status) { - Object (root_status: status, state: "content"); - - root_widget = append (status); - root_widget.avatar.button_press_event.connect (root_widget.on_avatar_clicked); + Object ( + root_status: status, + status_message: STATUS_LOADING + ); connect_account (); } - public override void on_account_changed (InstanceAccount? acc) { + public override void on_account_changed (InstanceAccount? acc) { account = acc; request (); } - private Widgets.Status prepend (API.Status status, bool to_end = false){ - var w = new Widgets.Status (status); - w.avatar.button_press_event.connect (w.on_avatar_clicked); + Widget prepend (Entity entity, bool to_end = false){ + var w = entity.to_widget () as Widgets.Status; w.revealer.reveal_child = true; if (to_end) @@ -32,8 +31,8 @@ public class Tootle.Views.ExpandedStatus : Views.Base, IAccountListener { check_resize (); return w; } - private Widgets.Status append (API.Status status) { - return prepend (status, true); + Widget append (Entity entity) { + return prepend (entity, true); } public void request () { @@ -44,25 +43,23 @@ public class Tootle.Views.ExpandedStatus : Views.Base, IAccountListener { var ancestors = root.get_array_member ("ancestors"); ancestors.foreach_element ((array, i, node) => { - var object = node.get_object (); - if (object != null) { - var status = new API.Status (object); - prepend (status); - } + var status = Entity.from_json (typeof (API.Status), node); + append (status); }); + root_widget = append (root_status); + var descendants = root.get_array_member ("descendants"); descendants.foreach_element ((array, i, node) => { - var object = node.get_object (); - if (object != null) { - var status = new API.Status (object); - append (status); - } + var status = Entity.from_json (typeof (API.Status), node); + append (status); }); + on_content_changed (); + int x,y; translate_coordinates (root_widget, 0, 0, out x, out y); - scrolled.vadjustment.value = (double)(y*-1); //TODO: Animate scrolling? + scrolled.vadjustment.value = (double)(y*-1); //content_list.select_row (root_widget); }) .exec (); @@ -76,9 +73,9 @@ public class Tootle.Views.ExpandedStatus : Views.Base, IAccountListener { .then ((sess, msg) => { var root = network.parse (msg); var statuses = root.get_array_member ("statuses"); - var object = statuses.get_element (0).get_object (); - if (object != null){ - var status = new API.Status (object); + var node = statuses.get_element (0); + if (node != null){ + var status = API.Status.from (node); window.open_view (new Views.ExpandedStatus (status)); } else diff --git a/src/Views/Federated.vala b/src/Views/Federated.vala index 0b7f639..599eacd 100644 --- a/src/Views/Federated.vala +++ b/src/Views/Federated.vala @@ -10,7 +10,7 @@ public class Tootle.Views.Federated : Views.Timeline { } public override string? get_stream_url () { - return account != null ? @"$(account.instance)/api/v1/streaming/?stream=public&access_token=$(account.token)" : null; + return account != null ? @"$(account.instance)/api/v1/streaming/?stream=public&access_token=$(account.access_token)" : null; } } diff --git a/src/Views/Hashtag.vala b/src/Views/Hashtag.vala index 9cd165e..402ba54 100644 --- a/src/Views/Hashtag.vala +++ b/src/Views/Hashtag.vala @@ -8,7 +8,7 @@ public class Tootle.Views.Hashtag : Views.Timeline { public override string? get_stream_url () { var tag = url.substring (4); - return account != null ? @"$(account.instance)/api/v1/streaming/?stream=hashtag&tag=$tag&access_token=$(account.token)" : null; + return account != null ? @"$(account.instance)/api/v1/streaming/?stream=hashtag&tag=$tag&access_token=$(account.access_token)" : null; } } diff --git a/src/Views/Local.vala b/src/Views/Local.vala index 4aa2071..837fd94 100644 --- a/src/Views/Local.vala +++ b/src/Views/Local.vala @@ -12,7 +12,7 @@ public class Tootle.Views.Local : Views.Federated { } public override string? get_stream_url () { - return account != null ? @"$(account.instance)/api/v1/streaming/?stream=public:local&access_token=$(account.token)" : null; + return account != null ? @"$(account.instance)/api/v1/streaming/?stream=public:local&access_token=$(account.access_token)" : null; } } diff --git a/src/Views/NewAccount.vala b/src/Views/NewAccount.vala index 49e2a01..d114d0f 100644 --- a/src/Views/NewAccount.vala +++ b/src/Views/NewAccount.vala @@ -42,7 +42,7 @@ public class Tootle.Views.NewAccount : Views.Base { info ("New account view was requested"); } - private bool reset () { + bool reset () { info ("State invalidated"); instance = code = client_id = client_secret = access_token = null; instance_entry.sensitive = true; @@ -50,11 +50,11 @@ public class Tootle.Views.NewAccount : Views.Base { return true; } - private void oopsie (string message) { + void oopsie (string message) { warning (message); } - private void on_next_clicked () { + void on_next_clicked () { try { step (); } @@ -63,7 +63,7 @@ public class Tootle.Views.NewAccount : Views.Base { } } - private void step () throws Error { + void step () throws Error { if (instance == null) setup_instance (); @@ -76,7 +76,7 @@ public class Tootle.Views.NewAccount : Views.Base { request_token (); } - private void setup_instance () throws Error { + void setup_instance () throws Error { info ("Checking instance URL"); var str = instance_entry.text @@ -91,7 +91,7 @@ public class Tootle.Views.NewAccount : Views.Base { throw new Oopsie.USER (_("Instance URL is invalid")); } - private void register_client () throws Error { + void register_client () throws Error { info ("Registering client"); instance_entry.sensitive = false; @@ -119,7 +119,7 @@ public class Tootle.Views.NewAccount : Views.Base { .exec (); } - private void open_confirmation_page () { + void open_confirmation_page () { info ("Opening permission request page"); var pars = @"scope=$scopes&response_type=code&redirect_uri=$redirect_uri&client_id=$client_id"; @@ -127,7 +127,7 @@ public class Tootle.Views.NewAccount : Views.Base { Desktop.open_uri (url); } - private void request_token () throws Error { + void request_token () throws Error { if (code.char_count () <= 10) throw new Oopsie.USER (_("Please paste a valid authorization code")); @@ -142,8 +142,8 @@ public class Tootle.Views.NewAccount : Views.Base { .then ((sess, msg) => { var root = network.parse (msg); access_token = root.get_string_member ("access_token"); - account.token = access_token; - account.id = 0; + account.access_token = access_token; + account.id = ""; info ("OK: received access token"); request_profile (); }) @@ -151,13 +151,13 @@ public class Tootle.Views.NewAccount : Views.Base { .exec (); } - private void request_profile () throws Error { + void request_profile () throws Error { info ("Testing received access token"); new Request.GET ("/api/v1/accounts/verify_credentials") .with_account (account) .then ((sess, msg) => { - var root = network.parse (msg); - var account = new API.Account (root); + var node = network.parse_node (msg); + var account = API.Account.from (node); info ("OK: received user profile"); save (account); }) @@ -168,13 +168,13 @@ public class Tootle.Views.NewAccount : Views.Base { .exec (); } - private void save (API.Account profile) { + void save (API.Account profile) { info ("Account validated. Saving..."); account.patch (profile); account.instance = instance; account.client_id = client_id; account.client_secret = client_secret; - account.token = access_token; + account.access_token = access_token; accounts.add (account); destroy (); diff --git a/src/Views/Notifications.vala b/src/Views/Notifications.vala index c289a91..a3c7fa6 100644 --- a/src/Views/Notifications.vala +++ b/src/Views/Notifications.vala @@ -17,7 +17,7 @@ public class Tootle.Views.Notifications : Views.Timeline, IAccountListener, IStr } public override string? get_stream_url () { - return account != null ? @"$(account.instance)/api/v1/streaming/?stream=user&access_token=$(account.token)" : null; + return account != null ? @"$(account.instance)/api/v1/streaming/?stream=user&access_token=$(account.access_token)" : null; } public override void on_shown () { @@ -34,25 +34,14 @@ public class Tootle.Views.Notifications : Views.Timeline, IAccountListener, IStr var nw = w as Widgets.Notification; var notification = nw.notification; - if (notification.id > last_id) - last_id = notification.id; + if (int64.parse (notification.id) > last_id) + last_id = int64.parse (notification.id); needs_attention = has_unread () && !current; if (needs_attention) accounts.save (); } - public override GLib.Object to_entity (Json.Node node) throws Oopsie { - if (node == null) - throw new Oopsie.PARSING ("Received null Json.Node"); - - var obj = node.get_object (); - if (obj == null) - throw new Oopsie.PARSING ("Received Json.Node is not a Json.Object!"); - - return new API.Notification (obj); - } - public override void on_account_changed (InstanceAccount? acc) { base.on_account_changed (acc); if (account == null) { diff --git a/src/Views/Profile.vala b/src/Views/Profile.vala index a84859d..f737d0e 100644 --- a/src/Views/Profile.vala +++ b/src/Views/Profile.vala @@ -57,7 +57,7 @@ public class Tootle.Views.Profile : Views.Timeline { relationship = builder.get_object ("relationship") as Label; posts_label = builder.get_object ("posts_label") as Label; - profile.bind_property ("posts_count", posts_label, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => { + profile.bind_property ("statuses_count", posts_label, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => { var val = (int64) src; target.set_string (_("%s Posts").printf (@"$val")); return true; @@ -151,28 +151,22 @@ public class Tootle.Views.Profile : Views.Timeline { } public override Request append_params (Request req) { - req.with_param ("exclude_replies", (!filter_replies.active).to_string ()); - req.with_param ("only_media", filter_media.active.to_string ()); - return base.append_params (req); + if (page_next == null) { + req.with_param ("exclude_replies", (!filter_replies.active).to_string ()); + req.with_param ("only_media", filter_media.active.to_string ()); + return base.append_params (req); + } + else + return req; } - public override GLib.Object to_entity (Json.Node node) { - var obj = node.get_object (); - if (posts_tab.active) - return new API.Status (obj); - else { - var account = new API.Account (obj); - return new API.Status.from_account (account); - } - } - - public static void open_from_id (int64 id){ - var url = "%s/api/v1/accounts/%lld".printf (accounts.active.instance, id); + public static void open_from_id (string id){ + var url = @"$(accounts.active.instance)/api/v1/accounts/$id"; var msg = new Soup.Message ("GET", url); msg.priority = Soup.MessagePriority.HIGH; network.queue (msg, (sess, mess) => { - var root = network.parse (mess); - var acc = new API.Account (root); + var node = network.parse_node (mess); + var acc = API.Account.from (node); window.open_view (new Views.Profile (acc)); }, (status, reason) => { network.on_error (status, reason); diff --git a/src/Views/Search.vala b/src/Views/Search.vala index 5fa3ab6..fa51b84 100644 --- a/src/Views/Search.vala +++ b/src/Views/Search.vala @@ -92,8 +92,7 @@ public class Tootle.Views.Search : Views.Base { if (accounts.get_length () > 0) { append_header (_("Accounts")); accounts.foreach_element ((array, i, node) => { - var obj = node.get_object (); - var acc = new API.Account (obj); + var acc = API.Account.from (node); append_account (acc); }); } @@ -101,8 +100,7 @@ public class Tootle.Views.Search : Views.Base { if (statuses.get_length () > 0) { append_header (_("Statuses")); statuses.foreach_element ((array, i, node) => { - var obj = node.get_object (); - var status = new API.Status (obj); + var status = API.Status.from (node); append_status (status); }); } diff --git a/src/Views/Timeline.vala b/src/Views/Timeline.vala index bcffb74..db3f067 100644 --- a/src/Views/Timeline.vala +++ b/src/Views/Timeline.vala @@ -30,17 +30,6 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba return status.is_owned (); } - public virtual GLib.Object to_entity (Json.Node node) throws Oopsie { - if (node == null) - throw new Oopsie.PARSING ("Received null Json.Node"); - - var obj = node.get_object (); - if (obj == null) - throw new Oopsie.PARSING ("Received Json.Node is not a Json.Object!"); - - return new API.Status (obj); - } - public void prepend (Widget? w) { append (w, true); } @@ -90,30 +79,34 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba public virtual string get_req_url () { if (page_next != null) return page_next; - return url; } public virtual Request append_params (Request req) { - return req.with_param ("limit", @"$(settings.timeline_page_size)"); + if (page_next == null) + return req.with_param ("limit", @"$(settings.timeline_page_size)"); + else + return req; } public virtual bool request () { - append_params (new Request.GET (get_req_url ())) + var req = append_params (new Request.GET (get_req_url ())) .with_account (account) .then_parse_array ((node, msg) => { try { - var e = to_entity (node); + var e = Entity.from_json (accepts, node); var w = e as Widgetizable; append (w.to_widget ()); } catch (Error e) { warning (@"Timeline item parse error: $(e.message)"); } - get_pages (msg.response_headers.get_one ("Link")); }) - .on_error (on_error) - .exec (); + .on_error (on_error); + req.finished.connect (() => { + get_pages (req.response_headers.get_one ("Link")); + }); + req.exec (); return GLib.Source.REMOVE; } @@ -152,7 +145,7 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba prepend (status.to_widget ()); } - protected virtual void remove_status (int64 id) { + protected virtual void remove_status (string id) { if (settings.live_updates) { content.get_children ().@foreach (w => { var sw = w as Widgets.Status; diff --git a/src/Widgets/Attachment/Item.vala b/src/Widgets/Attachment/Item.vala index 9260715..c0b6e1a 100644 --- a/src/Widgets/Attachment/Item.vala +++ b/src/Widgets/Attachment/Item.vala @@ -4,7 +4,7 @@ using Gdk; public class Tootle.Widgets.Attachment.Item : EventBox { public API.Attachment attachment { get; construct set; } - + private Cache.Reference? cached; public Item (API.Attachment obj) { @@ -13,15 +13,15 @@ public class Tootle.Widgets.Attachment.Item : EventBox { ~Item () { cache.unload (cached); } - + construct { get_style_context ().add_class ("attachment"); width_request = height_request = 128; hexpand = true; tooltip_text = attachment.description ?? _("No description is available"); - + button_press_event.connect (on_clicked); - + show (); on_request (); } @@ -60,7 +60,7 @@ public class Tootle.Widgets.Attachment.Item : EventBox { var h = get_allocated_height (); var style = get_style_context (); var border_radius = style.get_property (Gtk.STYLE_PROPERTY_BORDER_RADIUS, style.get_state ()).get_int (); - + if (cached != null) { if (cached.loading) { Drawing.center (ctx, w, h, 32, 32); @@ -74,7 +74,7 @@ public class Tootle.Widgets.Attachment.Item : EventBox { ctx.fill (); } } - + return Gdk.EVENT_STOP; } @@ -85,15 +85,15 @@ public class Tootle.Widgets.Attachment.Item : EventBox { } else if (ev.button == 3) { var menu = new Gtk.Menu (); - + var item_open = new Gtk.MenuItem.with_label (_("Open")); item_open.activate.connect (open); menu.add (item_open); - + var item_download = new Gtk.MenuItem.with_label (_("Download")); item_download.activate.connect (download); menu.add (item_download); - + menu.show_all (); menu.attach_widget = this; menu.popup_at_pointer (); diff --git a/src/Widgets/RichLabel.vala b/src/Widgets/RichLabel.vala index f4c6d39..ae8d920 100644 --- a/src/Widgets/RichLabel.vala +++ b/src/Widgets/RichLabel.vala @@ -69,18 +69,18 @@ public class Tootle.Widgets.RichLabel : Label { var hashtags = root.get_array_member ("hashtags"); if (accounts.get_length () > 0) { - var item = accounts.get_object_element (0); - var obj = new API.Account (item); + var node = accounts.get_element (0); + var obj = API.Account.from (node); window.open_view (new Views.Profile (obj)); } else if (statuses.get_length () > 0) { - var item = accounts.get_object_element (0); - var obj = new API.Status (item); + var node = accounts.get_element (0); + var obj = API.Status.from (node); window.open_view (new Views.ExpandedStatus (obj)); } else if (hashtags.get_length () > 0) { - var item = accounts.get_object_element (0); - var obj = new API.Tag (item); + var node = accounts.get_element (0); + var obj = API.Tag.from (node); window.open_view (new Views.Hashtag (obj.name)); } else { diff --git a/src/Widgets/Status.vala b/src/Widgets/Status.vala index 9addae5..9f3e098 100644 --- a/src/Widgets/Status.vala +++ b/src/Widgets/Status.vala @@ -90,9 +90,9 @@ public class Tootle.Widgets.Status : EventBox { kind = API.NotificationType.REBLOG_REMOTE_USER; } - status.formal.bind_property ("favorited", favorite_button, "active", BindingFlags.SYNC_CREATE); + status.formal.bind_property ("favourited", favorite_button, "active", BindingFlags.SYNC_CREATE); favorite_button.clicked.connect (() => { - status.action (status.formal.favorited ? "unfavourite" : "favourite"); + status.action (status.formal.favourited ? "unfavourite" : "favourite"); }); status.formal.bind_property ("reblogged", reblog_button, "active", BindingFlags.SYNC_CREATE); @@ -118,7 +118,7 @@ public class Tootle.Widgets.Status : EventBox { reblog_button.tooltip_text = _("This post can't be boosted"); } - if (status.id <= 0) { + if (status.id == "") { actions.destroy (); date_label.destroy (); content.single_line_mode = true; @@ -130,7 +130,7 @@ public class Tootle.Widgets.Status : EventBox { button_press_event.connect (open); } - if (!attachments.populate (status.formal.attachments) || status.id <= 0) { + if (!attachments.populate (status.formal.media_attachments) || status.id == "") { attachments.destroy (); }