gtk: implement get_attachment_fd and texture/attachment cache, viewport loading
This commit is contained in:
@@ -144,4 +144,35 @@ public class Repository : DBusServiceProxy {
|
||||
var info = dbus_repository.get_attachment_info(attachment_guid);
|
||||
return new AttachmentInfo(info.attr1, info.attr2, info.attr3, info.attr4);
|
||||
}
|
||||
|
||||
public int open_attachment_fd(string attachment_guid, bool preview) throws DBusServiceProxyError, GLib.Error {
|
||||
if (dbus_repository == null) {
|
||||
throw new DBusServiceProxyError.NOT_CONNECTED("Repository not connected");
|
||||
}
|
||||
|
||||
var connection = Bus.get_sync(BusType.SESSION);
|
||||
UnixFDList? out_fd_list = null;
|
||||
var result = connection.call_with_unix_fd_list_sync(
|
||||
DBUS_NAME,
|
||||
DBUS_PATH,
|
||||
"net.buzzert.kordophone.Repository",
|
||||
"OpenAttachmentFd",
|
||||
new Variant("(sb)", attachment_guid, preview),
|
||||
new VariantType("(h)"),
|
||||
DBusCallFlags.NONE,
|
||||
120000,
|
||||
null,
|
||||
out out_fd_list,
|
||||
null
|
||||
);
|
||||
|
||||
int fd_handle = -1;
|
||||
result.get("(h)", out fd_handle);
|
||||
|
||||
if (out_fd_list == null) {
|
||||
throw new DBusServiceProxyError.NOT_CONNECTED("Missing UnixFDList from OpenAttachmentFd");
|
||||
}
|
||||
|
||||
return out_fd_list.get(fd_handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,30 +13,52 @@ private class SizeCache
|
||||
return instance;
|
||||
}
|
||||
|
||||
public Graphene.Size? get_size(string image_path) {
|
||||
return size_cache.get(image_path);
|
||||
public Graphene.Size? get_size(string attachment_guid) {
|
||||
return size_cache.get(attachment_guid);
|
||||
}
|
||||
|
||||
public void set_size(string image_path, Graphene.Size size) {
|
||||
size_cache.set(image_path, size);
|
||||
public void set_size(string attachment_guid, Graphene.Size size) {
|
||||
size_cache.set(attachment_guid, size);
|
||||
}
|
||||
}
|
||||
|
||||
private class TextureCache
|
||||
{
|
||||
private static TextureCache instance = null;
|
||||
private HashMap<string, Gdk.Texture> texture_cache = new HashMap<string, Gdk.Texture>();
|
||||
|
||||
public static TextureCache get_instance() {
|
||||
if (instance == null) {
|
||||
instance = new TextureCache();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public Gdk.Texture? get_texture(string attachment_guid) {
|
||||
return texture_cache.get(attachment_guid);
|
||||
}
|
||||
|
||||
public void set_texture(string attachment_guid, Gdk.Texture texture) {
|
||||
texture_cache.set(attachment_guid, texture);
|
||||
}
|
||||
}
|
||||
|
||||
private class ImageBubbleLayout : BubbleLayout
|
||||
{
|
||||
public string image_path;
|
||||
public string attachment_guid;
|
||||
public bool is_downloaded;
|
||||
public string? attachment_guid;
|
||||
|
||||
private Graphene.Size image_size;
|
||||
private Gdk.Texture? cached_texture = null;
|
||||
private bool preview_download_queued = false;
|
||||
|
||||
public ImageBubbleLayout(string image_path, bool from_me, Widget parent, float max_width, Graphene.Size? image_size = null) {
|
||||
public ImageBubbleLayout(string attachment_guid, bool from_me, Widget parent, float max_width, Graphene.Size? image_size = null) {
|
||||
base(parent, max_width);
|
||||
|
||||
this.from_me = from_me;
|
||||
this.image_path = image_path;
|
||||
this.attachment_guid = attachment_guid;
|
||||
this.is_downloaded = false;
|
||||
this.cached_texture = TextureCache.get_instance().get_texture(attachment_guid);
|
||||
|
||||
// Calculate image dimensions for layout
|
||||
calculate_image_dimensions(image_size);
|
||||
@@ -48,27 +70,26 @@ private class ImageBubbleLayout : BubbleLayout
|
||||
return;
|
||||
}
|
||||
|
||||
var cached_size = SizeCache.get_instance().get_size(image_path);
|
||||
var cached_size = SizeCache.get_instance().get_size(attachment_guid);
|
||||
if (cached_size != null) {
|
||||
this.image_size = cached_size;
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to load the image to get its dimensions
|
||||
try {
|
||||
warning("No image size provided, loading image to get dimensions");
|
||||
|
||||
var texture = Gdk.Texture.from_filename(image_path);
|
||||
var original_width = (float)texture.get_width();
|
||||
var original_height = (float)texture.get_height();
|
||||
|
||||
this.image_size = Graphene.Size() { width = original_width, height = original_height };
|
||||
SizeCache.get_instance().set_size(image_path, this.image_size);
|
||||
} catch (Error e) {
|
||||
// Fallback dimensions if image can't be loaded
|
||||
warning("Failed to load image %s: %s", image_path, e.message);
|
||||
this.image_size = Graphene.Size() { width = 200.0f, height = 150.0f };
|
||||
}
|
||||
|
||||
private void queue_preview_download_if_needed() {
|
||||
if (is_downloaded || preview_download_queued || attachment_guid == "") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Repository.get_instance().download_attachment(attachment_guid, true);
|
||||
preview_download_queued = true;
|
||||
} catch (GLib.Error e) {
|
||||
warning("Failed to queue preview download for %s: %s", attachment_guid, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
private void load_image_if_needed() {
|
||||
@@ -81,9 +102,22 @@ private class ImageBubbleLayout : BubbleLayout
|
||||
}
|
||||
|
||||
try {
|
||||
cached_texture = Gdk.Texture.from_filename(image_path);
|
||||
int fd = Repository.get_instance().open_attachment_fd(attachment_guid, true);
|
||||
var stream = new UnixInputStream(fd, true);
|
||||
var pixbuf = new Gdk.Pixbuf.from_stream(stream, null);
|
||||
cached_texture = Gdk.Texture.for_pixbuf(pixbuf);
|
||||
|
||||
if (cached_texture != null) {
|
||||
TextureCache.get_instance().set_texture(attachment_guid, cached_texture);
|
||||
this.image_size = Graphene.Size() {
|
||||
width = (float)cached_texture.get_width(),
|
||||
height = (float)cached_texture.get_height()
|
||||
};
|
||||
SizeCache.get_instance().set_size(attachment_guid, this.image_size);
|
||||
parent.queue_allocate();
|
||||
}
|
||||
} catch (Error e) {
|
||||
warning("Failed to load image %s: %s", image_path, e.message);
|
||||
warning("Failed to load preview image for %s: %s", attachment_guid, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +144,7 @@ private class ImageBubbleLayout : BubbleLayout
|
||||
}
|
||||
|
||||
public override void draw_content(Snapshot snapshot) {
|
||||
queue_preview_download_if_needed();
|
||||
load_image_if_needed();
|
||||
|
||||
snapshot.save();
|
||||
|
||||
@@ -62,34 +62,36 @@ public class MessageListModel : Object, ListModel
|
||||
}
|
||||
}
|
||||
|
||||
public void load_messages() {
|
||||
public void load_messages(bool force_full_reload = false) {
|
||||
var previous_messages = new HashSet<Message>();
|
||||
previous_messages.add_all(_messages);
|
||||
|
||||
try {
|
||||
bool first_load = _messages.size == 0;
|
||||
string last_message_id = (first_load || force_full_reload) ? "" : _messages.get(_messages.size - 1).guid;
|
||||
|
||||
Message[] messages = Repository.get_instance().get_messages(conversation.guid);
|
||||
Message[] messages = Repository.get_instance().get_messages(conversation.guid, last_message_id);
|
||||
|
||||
// Clear existing set
|
||||
bool fallback_full_reload = first_load || force_full_reload;
|
||||
if (!first_load && messages.length > 0 && previous_messages.contains(messages[0])) {
|
||||
fallback_full_reload = true;
|
||||
}
|
||||
|
||||
if (fallback_full_reload) {
|
||||
uint old_count = _messages.size;
|
||||
_messages.clear();
|
||||
participants.clear();
|
||||
|
||||
// Notify of removal
|
||||
if (old_count > 0) {
|
||||
items_changed(0, old_count, 0);
|
||||
}
|
||||
|
||||
// Process each conversation
|
||||
uint position = 0;
|
||||
|
||||
for (int i = 0; i < messages.length; i++) {
|
||||
var message = messages[i];
|
||||
participants.add(message.sender);
|
||||
|
||||
if (!first_load && !previous_messages.contains(message)) {
|
||||
// This is a new message according to the UI, schedule an animation for it.
|
||||
message.should_animate = true;
|
||||
}
|
||||
|
||||
@@ -97,10 +99,29 @@ public class MessageListModel : Object, ListModel
|
||||
position++;
|
||||
}
|
||||
|
||||
// Notify of additions
|
||||
if (position > 0) {
|
||||
items_changed(0, 0, position);
|
||||
}
|
||||
} else {
|
||||
uint old_count = _messages.size;
|
||||
uint appended = 0;
|
||||
|
||||
for (int i = 0; i < messages.length; i++) {
|
||||
var message = messages[i];
|
||||
if (previous_messages.contains(message)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
participants.add(message.sender);
|
||||
message.should_animate = true;
|
||||
_messages.add(message);
|
||||
appended++;
|
||||
}
|
||||
|
||||
if (appended > 0) {
|
||||
items_changed(old_count, 0, appended);
|
||||
}
|
||||
}
|
||||
} catch (Error e) {
|
||||
warning("Failed to load messages: %s", e.message);
|
||||
}
|
||||
|
||||
@@ -363,49 +363,23 @@ private class TranscriptDrawingArea : Widget
|
||||
|
||||
// Check for attachments. For each one, add an image layout bubble
|
||||
foreach (var attachment in message.attachments) {
|
||||
string preview_path = attachment.preview_path;
|
||||
bool preview_downloaded = attachment.preview_downloaded;
|
||||
|
||||
if (preview_path == null || preview_path == "") {
|
||||
try {
|
||||
var attachment_info = Repository.get_instance().get_attachment_info(attachment.guid);
|
||||
if (attachment_info.preview_path != null) {
|
||||
preview_path = attachment_info.preview_path;
|
||||
}
|
||||
preview_downloaded = attachment_info.preview_downloaded == true;
|
||||
} catch (GLib.Error e) {
|
||||
warning("Failed to load attachment info for %s: %s", attachment.guid, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
Graphene.Size? image_size = null;
|
||||
if (attachment.metadata != null) {
|
||||
if (attachment.metadata != null && attachment.metadata.attribution_info != null) {
|
||||
image_size = Graphene.Size() {
|
||||
width = attachment.metadata.attribution_info.width,
|
||||
height = attachment.metadata.attribution_info.height
|
||||
};
|
||||
}
|
||||
|
||||
var image_layout = new ImageBubbleLayout(preview_path, message.from_me, this, max_width, image_size);
|
||||
var image_layout = new ImageBubbleLayout(attachment.guid, message.from_me, this, max_width, image_size);
|
||||
image_layout.id = @"image-$(attachment.guid)";
|
||||
image_layout.attachment_guid = attachment.guid;
|
||||
|
||||
if (animate) {
|
||||
start_animation(image_layout.id);
|
||||
}
|
||||
|
||||
image_layout.is_downloaded = preview_downloaded;
|
||||
image_layout.is_downloaded = attachment.preview_downloaded;
|
||||
items.add(image_layout);
|
||||
|
||||
// If the attachment isn't downloaded, queue a download since we are going to be showing it here.
|
||||
// TODO: Probably would be better if we only did this for stuff in the viewport.
|
||||
if (!preview_downloaded) {
|
||||
try {
|
||||
Repository.get_instance().download_attachment(attachment.guid, true);
|
||||
} catch (GLib.Error e) {
|
||||
warning("Wasn't able to message daemon about queuing attachment download: %s", e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
last_sender = message.sender;
|
||||
|
||||
@@ -148,7 +148,7 @@ public class TranscriptView : Adw.Bin
|
||||
GLib.Idle.add(() => {
|
||||
if (needs_reload) {
|
||||
debug("Reloading messages for attachment download");
|
||||
model.load_messages();
|
||||
model.load_messages(true);
|
||||
needs_reload = false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user