mirror of
https://gitlab.gnome.org/World/tootle
synced 2025-02-08 23:58:45 +01:00
Fix circular references
This commit is contained in:
parent
92dd1e044a
commit
3d0bd9e48e
@ -46,8 +46,10 @@ public class Tootle.API.Account : Entity, Widgetizable {
|
||||
return new Request.GET ("/api/v1/accounts/relationships")
|
||||
.with_account (accounts.active)
|
||||
.with_param ("id", id.to_string ())
|
||||
.then_parse_array (node => {
|
||||
rs = API.Relationship.from (node);
|
||||
.then ((sess, msg) => {
|
||||
Network.parse_array (msg, node => {
|
||||
rs = API.Relationship.from (node);
|
||||
});
|
||||
})
|
||||
.on_error (network.on_error)
|
||||
.exec ();
|
||||
|
@ -4,11 +4,14 @@ using Gee;
|
||||
public class Tootle.Request : Soup.Message {
|
||||
|
||||
public string url { set; get; }
|
||||
private Network.SuccessCallback? cb;
|
||||
private Network.ErrorCallback? error_cb;
|
||||
private HashMap<string, string>? pars;
|
||||
private weak InstanceAccount? account;
|
||||
private bool needs_token = false;
|
||||
Network.SuccessCallback? cb;
|
||||
Network.ErrorCallback? error_cb;
|
||||
HashMap<string, string>? pars;
|
||||
weak InstanceAccount? account;
|
||||
bool needs_token = false;
|
||||
|
||||
weak Gtk.Widget? ctx;
|
||||
bool has_ctx = false;
|
||||
|
||||
public Request.GET (string url) {
|
||||
Object (method: "GET", url: url);
|
||||
@ -20,23 +23,22 @@ public class Tootle.Request : Soup.Message {
|
||||
Object (method: "DELETE", url: url);
|
||||
}
|
||||
|
||||
public Request with_ctx (Gtk.Widget ctx) {
|
||||
this.has_ctx = true;
|
||||
this.ctx = ctx;
|
||||
this.ctx.destroy.connect (() => {
|
||||
network.cancel (this);
|
||||
this.ctx = null;
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public Request then (owned Network.SuccessCallback cb) {
|
||||
this.cb = (owned) cb;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Request then_parse_array (owned Network.NodeCallback _cb) {
|
||||
this.cb = (sess, msg) => {
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data ((string) msg.response_body.flatten ().data, -1);
|
||||
parser.get_root ().get_array ().foreach_element ((array, i, node) => _cb (node, msg));
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
public Request then_parse_obj (owned Network.ObjectCallback _cb) {
|
||||
this.cb = (sess, msg) => {
|
||||
_cb (network.parse (msg));
|
||||
this.cb = (s, m) => {
|
||||
Idle.add (() => {
|
||||
cb (s, m);
|
||||
return false;
|
||||
});
|
||||
};
|
||||
return this;
|
||||
}
|
||||
@ -60,8 +62,7 @@ public class Tootle.Request : Soup.Message {
|
||||
return this;
|
||||
}
|
||||
|
||||
// Should be used for requests with default priority
|
||||
public Request queue () {
|
||||
public Request exec () {
|
||||
var parameters = "";
|
||||
if (pars != null) {
|
||||
parameters = "?";
|
||||
@ -100,10 +101,4 @@ public class Tootle.Request : Soup.Message {
|
||||
return this;
|
||||
}
|
||||
|
||||
// Should be used for real-time user interactions (liking, removing and browsing posts)
|
||||
public Request exec () {
|
||||
this.priority = MessagePriority.HIGH;
|
||||
return this.queue ();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,9 +26,9 @@ public class Tootle.Cache : GLib.Object {
|
||||
|
||||
protected class Item : GLib.Object {
|
||||
public Pixbuf data { get; construct set; }
|
||||
public int64 references { get; construct set; }
|
||||
public int references { get; construct set; }
|
||||
|
||||
public Item (Pixbuf d, int64 r) {
|
||||
public Item (Pixbuf d, int r) {
|
||||
Object (data: d, references: r);
|
||||
}
|
||||
}
|
||||
@ -45,12 +45,14 @@ public class Tootle.Cache : GLib.Object {
|
||||
return;
|
||||
|
||||
item.references--;
|
||||
//info (@"DEREF $(r.key) $(item.references)");
|
||||
if (item.references <= 0) {
|
||||
//info ("REMOVE %s", r.key);
|
||||
// message (@"[X] $(r.key)");
|
||||
items.remove (r.key);
|
||||
items_in_progress.remove (r.key);
|
||||
}
|
||||
// else {
|
||||
// message (@"[-] $(r.key) - $(item.references)");
|
||||
// }
|
||||
}
|
||||
|
||||
public void load (string? url, owned CachedResultCallback cb) {
|
||||
@ -59,9 +61,9 @@ public class Tootle.Cache : GLib.Object {
|
||||
|
||||
var key = url;
|
||||
if (items.contains (key)) {
|
||||
//info (@"LOAD $key");
|
||||
var item = items.@get (key);
|
||||
item.references++;
|
||||
// message (@"[+] $key - $(item.references)");
|
||||
cb (Reference () {
|
||||
data = item.data,
|
||||
key = key,
|
||||
@ -72,19 +74,19 @@ public class Tootle.Cache : GLib.Object {
|
||||
|
||||
//var item = items.@get (key);
|
||||
|
||||
var message = items_in_progress.@get (key);
|
||||
if (message == null) {
|
||||
message = new Soup.Message ("GET", url);
|
||||
var msg = items_in_progress.@get (key);
|
||||
if (msg == null) {
|
||||
msg = new Soup.Message ("GET", url);
|
||||
ulong id = 0;
|
||||
id = message.finished.connect (() => {
|
||||
id = msg.finished.connect (() => {
|
||||
Pixbuf? pixbuf = null;
|
||||
|
||||
var data = message.response_body.flatten ().data;
|
||||
var data = msg.response_body.flatten ().data;
|
||||
var stream = new MemoryInputStream.from_data (data);
|
||||
pixbuf = new Pixbuf.from_stream (stream);
|
||||
stream.close ();
|
||||
|
||||
//info (@"< STORE $key");
|
||||
// message (@"[*] $key");
|
||||
items[key] = new Item (pixbuf, 1);
|
||||
items_in_progress.remove (key);
|
||||
|
||||
@ -94,10 +96,10 @@ public class Tootle.Cache : GLib.Object {
|
||||
loading = false
|
||||
});
|
||||
|
||||
message.disconnect (id);
|
||||
msg.disconnect (id);
|
||||
});
|
||||
|
||||
network.queue (message, (sess, msg) => {
|
||||
network.queue (msg, (sess, mess) => {
|
||||
// no one cares
|
||||
},
|
||||
(code, reason) => {
|
||||
@ -110,12 +112,12 @@ public class Tootle.Cache : GLib.Object {
|
||||
loading = true
|
||||
});
|
||||
|
||||
items_in_progress.insert (key, message);
|
||||
items_in_progress.insert (key, msg);
|
||||
}
|
||||
else {
|
||||
//info ("AWAIT: %s", key);
|
||||
//message ("[/]: %s", key);
|
||||
ulong id = 0;
|
||||
id = message.finished.connect_after (() => {
|
||||
id = msg.finished.connect_after (() => {
|
||||
var it = items.@get (key);
|
||||
cb (Reference () {
|
||||
data = it.data,
|
||||
@ -123,13 +125,13 @@ public class Tootle.Cache : GLib.Object {
|
||||
loading = false
|
||||
});
|
||||
it.references++;
|
||||
message.disconnect (id);
|
||||
msg.disconnect (id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
info ("PURGE");
|
||||
// message ("[ CLEARED ALL ]");
|
||||
items.remove_all ();
|
||||
items_in_progress.remove_all ();
|
||||
}
|
||||
|
@ -1,10 +1,24 @@
|
||||
public interface Tootle.IAccountListener : GLib.Object {
|
||||
|
||||
protected void connect_account () {
|
||||
accounts.notify["active"].connect (() => on_account_changed (accounts.active));
|
||||
accounts.saved.notify["size"].connect (() => on_accounts_changed (accounts.saved));
|
||||
//TODO: Refactor into AccountHolder
|
||||
|
||||
protected void account_listener_init () {
|
||||
accounts.notify["active"].connect (_on_active_acc_update);
|
||||
accounts.saved.notify["size"].connect (_on_saved_accs_update);
|
||||
on_account_changed (accounts.active);
|
||||
}
|
||||
protected void account_listener_free () {
|
||||
accounts.notify["active"].disconnect (_on_active_acc_update);
|
||||
accounts.saved.notify["size"].disconnect (_on_saved_accs_update);
|
||||
}
|
||||
|
||||
void _on_active_acc_update (ParamSpec s) {
|
||||
on_account_changed (accounts.active);
|
||||
}
|
||||
|
||||
void _on_saved_accs_update (ParamSpec s) {
|
||||
on_accounts_changed (accounts.saved);
|
||||
}
|
||||
|
||||
public virtual void on_account_changed (InstanceAccount? account) {}
|
||||
public virtual void on_accounts_changed (Gee.ArrayList<InstanceAccount> accounts) {}
|
||||
|
@ -29,17 +29,18 @@ public class Tootle.Network : GLib.Object {
|
||||
});
|
||||
}
|
||||
|
||||
// public void cancel_request (Soup.Message? msg) {
|
||||
// if (msg == null)
|
||||
// return;
|
||||
public void cancel (Soup.Message? msg) {
|
||||
if (msg == null)
|
||||
return;
|
||||
|
||||
// switch (msg.status_code) {
|
||||
// case Soup.Status.CANCELLED:
|
||||
// case Soup.Status.OK:
|
||||
// return;
|
||||
// }
|
||||
// session.cancel_message (msg, Soup.Status.CANCELLED);
|
||||
// }
|
||||
switch (msg.status_code) {
|
||||
case Soup.Status.CANCELLED:
|
||||
case Soup.Status.OK:
|
||||
return;
|
||||
}
|
||||
debug ("Cancelling message");
|
||||
session.cancel_message (msg, Soup.Status.CANCELLED);
|
||||
}
|
||||
|
||||
public void queue (owned Soup.Message message, owned SuccessCallback? cb, owned ErrorCallback? errcb = null) {
|
||||
requests_processing++;
|
||||
@ -57,6 +58,9 @@ public class Tootle.Network : GLib.Object {
|
||||
errcb (Soup.Status.NONE, e.message);
|
||||
}
|
||||
}
|
||||
else if (status == Soup.Status.CANCELLED) {
|
||||
debug ("Message is cancelled. Ignoring callback invocation.");
|
||||
}
|
||||
else {
|
||||
if (errcb != null)
|
||||
errcb ((int32)status, describe_error ((int32)status));
|
||||
@ -89,4 +93,12 @@ public class Tootle.Network : GLib.Object {
|
||||
return parse_node (msg).get_object ();
|
||||
}
|
||||
|
||||
public static void parse_array (Soup.Message msg, owned NodeCallback cb) throws Error {
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data ((string) msg.response_body.flatten ().data, -1);
|
||||
parser.get_root ().get_array ().foreach_element ((array, i, node) => {
|
||||
cb (node, msg);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -71,8 +71,8 @@ public class Tootle.Views.Base : Box {
|
||||
}
|
||||
|
||||
public virtual void clear (){
|
||||
content_list.forall (widget => {
|
||||
widget.destroy ();
|
||||
content_list.forall (w => {
|
||||
w.destroy ();
|
||||
});
|
||||
state = "status";
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ public class Tootle.Views.ExpandedStatus : Views.Base, IAccountListener {
|
||||
root_status: status,
|
||||
status_message: STATUS_LOADING
|
||||
);
|
||||
connect_account ();
|
||||
account_listener_init ();
|
||||
}
|
||||
|
||||
public override void on_account_changed (InstanceAccount? acc) {
|
||||
@ -38,8 +38,9 @@ public class Tootle.Views.ExpandedStatus : Views.Base, IAccountListener {
|
||||
public void request () {
|
||||
new Request.GET (@"/api/v1/statuses/$(root_status.id)/context")
|
||||
.with_account (account)
|
||||
.then_parse_obj (root => {
|
||||
if (scrolled == null) return;
|
||||
.with_ctx (this)
|
||||
.then ((sess, msg) => {
|
||||
var root = network.parse (msg);
|
||||
|
||||
var ancestors = root.get_array_member ("ancestors");
|
||||
ancestors.foreach_element ((array, i, node) => {
|
||||
|
@ -12,6 +12,8 @@ public class Tootle.Views.Profile : Views.Timeline {
|
||||
Button rs_button;
|
||||
Label rs_button_label;
|
||||
|
||||
weak ListBoxRow note_row;
|
||||
|
||||
public bool exclude_replies { get; set; default = true; }
|
||||
public bool only_media { get; set; default = false; }
|
||||
|
||||
@ -39,7 +41,7 @@ public class Tootle.Views.Profile : Views.Timeline {
|
||||
return true;
|
||||
});
|
||||
|
||||
var note_row = builder.get_object ("note_row") as ListBoxRow;
|
||||
note_row = builder.get_object ("note_row") as ListBoxRow;
|
||||
var note = builder.get_object ("note") as Widgets.RichLabel;
|
||||
profile.bind_property ("note", note, "text", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
|
||||
var text = Html.simplify ((string) src);
|
||||
@ -83,6 +85,9 @@ public class Tootle.Views.Profile : Views.Timeline {
|
||||
);
|
||||
profile.get_relationship ();
|
||||
}
|
||||
~Profile () {
|
||||
filter.destroy ();
|
||||
}
|
||||
|
||||
public override void on_shown () {
|
||||
window.header.custom_title = filter;
|
||||
|
@ -17,7 +17,7 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba
|
||||
construct {
|
||||
app.refresh.connect (on_refresh);
|
||||
status_button.clicked.connect (on_refresh);
|
||||
connect_account ();
|
||||
account_listener_init ();
|
||||
|
||||
on_status_added.connect (add_status);
|
||||
on_status_removed.connect (remove_status);
|
||||
@ -92,20 +92,22 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba
|
||||
public virtual bool request () {
|
||||
var req = append_params (new Request.GET (get_req_url ()))
|
||||
.with_account (account)
|
||||
.then_parse_array ((node, msg) => {
|
||||
try {
|
||||
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)");
|
||||
}
|
||||
.with_ctx (this)
|
||||
.then ((sess, msg) => {
|
||||
Network.parse_array (msg, node => {
|
||||
try {
|
||||
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);
|
||||
req.finished.connect (() => {
|
||||
get_pages (req.response_headers.get_one ("Link"));
|
||||
});
|
||||
req.exec ();
|
||||
return GLib.Source.REMOVE;
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ public class Tootle.Widgets.AccountsButton : Gtk.MenuButton, IAccountListener {
|
||||
Button item_bookmarks;
|
||||
|
||||
construct {
|
||||
connect_account ();
|
||||
account_listener_init ();
|
||||
|
||||
item_refresh.clicked.connect (() => {
|
||||
app.refresh ();
|
||||
@ -101,6 +101,9 @@ public class Tootle.Widgets.AccountsButton : Gtk.MenuButton, IAccountListener {
|
||||
|
||||
account_list.row_activated.connect (on_selection_changed);
|
||||
}
|
||||
~AccountsButton () {
|
||||
account_listener_free ();
|
||||
}
|
||||
|
||||
protected void on_selection_changed (ListBoxRow r) {
|
||||
var i = r.get_index ();
|
||||
|
@ -12,7 +12,7 @@ public class Tootle.Widgets.Avatar : EventBox {
|
||||
get_style_context ().add_class ("avatar");
|
||||
notify["url"].connect (on_url_updated);
|
||||
notify["size"].connect (on_redraw);
|
||||
Screen.get_default ().monitors_changed.connect (on_redraw);
|
||||
// Screen.get_default ().monitors_changed.connect (on_redraw);
|
||||
on_url_updated ();
|
||||
}
|
||||
|
||||
@ -23,11 +23,13 @@ public class Tootle.Widgets.Avatar : EventBox {
|
||||
|
||||
~Avatar () {
|
||||
notify["url"].disconnect (on_url_updated);
|
||||
Screen.get_default ().monitors_changed.disconnect (on_redraw);
|
||||
// Screen.get_default ().monitors_changed.disconnect (on_redraw);
|
||||
cache.unload (cached);
|
||||
}
|
||||
|
||||
private void on_url_updated () {
|
||||
if (cached != null)
|
||||
cache.unload (cached);
|
||||
cached = null;
|
||||
on_redraw ();
|
||||
cache.load (url, on_cache_result);
|
||||
@ -39,8 +41,7 @@ public class Tootle.Widgets.Avatar : EventBox {
|
||||
}
|
||||
|
||||
public int get_scaled_size () {
|
||||
return size;
|
||||
//return size * get_scale_factor ();
|
||||
return size; //return size * get_scale_factor ();
|
||||
}
|
||||
|
||||
private void on_redraw () {
|
||||
|
@ -112,7 +112,7 @@ public class Tootle.Widgets.Status : ListBoxRow {
|
||||
reblog_button.clicked.connect (() => {
|
||||
status.action (status.formal.reblogged ? "unreblog" : "reblog");
|
||||
});
|
||||
|
||||
|
||||
status.formal.bind_property ("bookmarked", bookmark_button, "active", BindingFlags.SYNC_CREATE);
|
||||
bookmark_button.clicked.connect (() => {
|
||||
status.action (status.formal.bookmarked ? "unbookmark" : "bookmark");
|
||||
|
@ -3,6 +3,8 @@ using Gtk;
|
||||
[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/widgets/timeline_filter.ui")]
|
||||
public class Tootle.Widgets.TimelineFilter : MenuButton {
|
||||
|
||||
weak Views.Profile view;
|
||||
|
||||
[GtkChild]
|
||||
public Label title;
|
||||
|
||||
@ -22,7 +24,8 @@ public class Tootle.Widgets.TimelineFilter : MenuButton {
|
||||
radio_source.bind_property ("active", post_filter, "reveal-child", BindingFlags.SYNC_CREATE);
|
||||
}
|
||||
|
||||
public TimelineFilter.with_profile (Views.Profile view) {
|
||||
public TimelineFilter.with_profile (Views.Profile profile) {
|
||||
this.view = profile;
|
||||
radio_source.get_group ().@foreach (w => {
|
||||
w.toggled.connect (() => {
|
||||
if (w.active) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user