Compare commits

...

18 Commits

Author SHA1 Message Date
fiaxh
dbedfe3f49 Conversation details: Allow topic edits for moderators 2025-06-15 11:59:23 +02:00
fiaxh
b2163a7ca5 File Send Dialog: Make file widget static 2025-06-14 21:48:14 +02:00
fiaxh
bd8f277b91 Add basic support for BOB thumbnails in SFS 2025-06-14 17:50:07 +02:00
fiaxh
1dae4e1abd Support media elements in data forms 2025-06-14 17:06:45 +02:00
fiaxh
1254a753eb Support media elements in data forms 2025-06-14 17:02:01 +02:00
fiaxh
5eabd7ac38 xmpp-vala: Add basic BOB support 2025-06-14 17:00:24 +02:00
fiaxh
2956401e48 xmpp-vala: Fix SFS mime_type wire-format 2025-06-14 16:59:18 +02:00
fiaxh
915b18789f Disregard SFS fallback messages even without source attachment given a full-body fallback indication 2025-06-13 19:40:09 +02:00
fiaxh
2e6454a92e Add support for whole body fallbacks 2025-06-13 19:37:27 +02:00
Jacques Comeaux
715b874801 Voice processor: add back call to set stream delay 2025-05-23 15:08:28 -06:00
fabrixxm
22616c7458 FileTransfer fix store source
Store source in db and add sources to `sfs_sources` is divided
in two functions. `persists()` now calls `store_source`, because
the sources it want to persist are already added to `sfs_sources`.
2025-05-23 19:05:53 +02:00
fabrixxm
0a564f80ab Fix parse incoming stateless file sharing sources 2025-05-23 18:18:50 +02:00
fiaxh
c40577ed76 Conversation details: Show used account if multiple are active 2025-05-05 14:32:17 +02:00
fiaxh
953b384ef7 Account details: Make own jid selectable 2025-05-05 14:12:30 +02:00
fiaxh
c8da1cfb53 Remove pixbuf from libdino api surface and xmpp-vala 2025-04-18 16:52:46 +02:00
Marvin W
5af1b2cd0d
AppData: Set display_length to 360
"small" was removed about a year ago.
2025-04-11 21:46:32 +02:00
Marvin W
af420fe443
Update flatpak dependencies 2025-04-11 20:50:52 +02:00
Marvin W
618af16bc7
Strip leading "v" from tag when generating display version 2025-04-11 19:22:51 +02:00
37 changed files with 290 additions and 205 deletions

View File

@ -28,9 +28,15 @@
], ],
"sources": [ "sources": [
{ {
"type": "git", "type": "archive",
"url": "https://github.com/protocolbuffers/protobuf.git", "url": "https://github.com/protocolbuffers/protobuf/releases/download/v30.2/protobuf-30.2.tar.gz",
"tag": "v29.4" "sha512": "555d1b18d175eeaf17f3879f124d33080f490367840d35b34bfc4e4a5b383bf6a1d09f1570acb6af9c53ac4940a14572d46423b6e3dd0c712e7802c986fb6be6",
"x-checker-data": {
"type": "anitya",
"project-id": 3715,
"stable-only": true,
"url-template": "https://github.com/protocolbuffers/protobuf/releases/download/v$version/protobuf-$version.tar.gz"
}
} }
] ]
}, },
@ -48,9 +54,15 @@
], ],
"sources": [ "sources": [
{ {
"type": "git", "type": "archive",
"url": "https://github.com/protobuf-c/protobuf-c.git", "url": "https://github.com/protobuf-c/protobuf-c/releases/download/v1.5.2/protobuf-c-1.5.2.tar.gz",
"tag": "v1.5.1" "sha512": "78dc72988d7e8232c1b967849aa00939bc05ab7d39b86a8e2af005e38aa4ef4c9b03920d51fb5337399d980e65f35d11bd4742bea745a893ecc909f56a51c9ac",
"x-checker-data": {
"type": "anitya",
"project-id": 3716,
"stable-only": true,
"url-template": "https://github.com/protobuf-c/protobuf-c/releases/download/v$version/protobuf-c-$version.tar.gz"
}
} }
] ]
}, },
@ -67,9 +79,15 @@
], ],
"sources": [ "sources": [
{ {
"type": "git", "type": "archive",
"url": "https://github.com/dino/libomemo-c.git", "url": "https://github.com/dino/libomemo-c/releases/download/v0.5.1/libomemo-c-0.5.1.tar.gz",
"tag": "v0.5.1" "sha512": "ff59565406c51663f2944e9a7c12c5b0e3fa01073039f5161472dd81f59194b1cf2685bc1e0cc930a141bc409b965c5d93313cfc3e0e237250102af3b5e88826",
"x-checker-data": {
"type": "anitya",
"project-id": 359676,
"stable-only": true,
"url-template": "https://github.com/dino/libomemo-c/releases/download/v$version/libomemo-c-$version.tar.gz"
}
} }
] ]
}, },
@ -85,8 +103,14 @@
"sources": [ "sources": [
{ {
"type": "archive", "type": "archive",
"url": "https://fukuchi.org/works/qrencode/qrencode-4.1.1.tar.gz", "url": "https://github.com/fukuchi/libqrencode/archive/refs/tags/v4.1.1.tar.gz",
"sha512": "209bb656ae3f391b03c7b3ceb03e34f7320b0105babf48b619e7a299528b8828449e0e7696f0b5db0d99170a81709d0518e34835229a748701e7df784e58a9ce" "sha512": "584106e7bcaaa1ef2efe63d653daad38d4ff436eb4b185a1db3c747169c1ffa74149c3b1329bb0b8ae007903db0a7034aabf135cc196d91a37b5c61348154a65",
"x-checker-data": {
"type": "anitya",
"project-id": 12834,
"stable-only": true,
"url-template": "https://github.com/fukuchi/libqrencode/archive/refs/tags/v$version.tar.gz"
}
} }
] ]
}, },

View File

@ -160,7 +160,7 @@ public class FileTransfer : Object {
foreach(var thumbnail_row in db.file_thumbnails.select().with(db.file_thumbnails.id, "=", id)) { foreach(var thumbnail_row in db.file_thumbnails.select().with(db.file_thumbnails.id, "=", id)) {
Xep.JingleContentThumbnails.Thumbnail thumbnail = new Xep.JingleContentThumbnails.Thumbnail(); Xep.JingleContentThumbnails.Thumbnail thumbnail = new Xep.JingleContentThumbnails.Thumbnail();
thumbnail.uri = thumbnail_row[db.file_thumbnails.uri]; thumbnail.data = Xmpp.get_data_for_uri(thumbnail_row[db.file_thumbnails.uri]);
thumbnail.media_type = thumbnail_row[db.file_thumbnails.mime_type]; thumbnail.media_type = thumbnail_row[db.file_thumbnails.mime_type];
thumbnail.width = thumbnail_row[db.file_thumbnails.width]; thumbnail.width = thumbnail_row[db.file_thumbnails.width];
thumbnail.height = thumbnail_row[db.file_thumbnails.height]; thumbnail.height = thumbnail_row[db.file_thumbnails.height];
@ -214,9 +214,11 @@ public class FileTransfer : Object {
.perform(); .perform();
} }
foreach (Xep.JingleContentThumbnails.Thumbnail thumbnail in thumbnails) { foreach (Xep.JingleContentThumbnails.Thumbnail thumbnail in thumbnails) {
string data_uri = "data:image/png;base64," + Base64.encode(thumbnail.data.get_data());
db.file_thumbnails.insert() db.file_thumbnails.insert()
.value(db.file_thumbnails.id, id) .value(db.file_thumbnails.id, id)
.value(db.file_thumbnails.uri, thumbnail.uri) .value(db.file_thumbnails.uri, data_uri)
.value(db.file_thumbnails.mime_type, thumbnail.media_type) .value(db.file_thumbnails.mime_type, thumbnail.media_type)
.value(db.file_thumbnails.width, thumbnail.width) .value(db.file_thumbnails.width, thumbnail.width)
.value(db.file_thumbnails.height, thumbnail.height) .value(db.file_thumbnails.height, thumbnail.height)
@ -224,7 +226,7 @@ public class FileTransfer : Object {
} }
foreach (Xep.StatelessFileSharing.Source source in sfs_sources) { foreach (Xep.StatelessFileSharing.Source source in sfs_sources) {
add_sfs_source(source); persist_source(source);
} }
notify.connect(on_update); notify.connect(on_update);
@ -234,7 +236,13 @@ public class FileTransfer : Object {
if (sfs_sources.contains(source)) return; // Don't add the same source twice. Might happen due to MAM and lacking deduplication. if (sfs_sources.contains(source)) return; // Don't add the same source twice. Might happen due to MAM and lacking deduplication.
sfs_sources.add(source); sfs_sources.add(source);
if (id != -1) {
persist_source(source);
}
sources_changed();
}
private void persist_source(Xep.StatelessFileSharing.Source source) {
Xep.StatelessFileSharing.HttpSource? http_source = source as Xep.StatelessFileSharing.HttpSource; Xep.StatelessFileSharing.HttpSource? http_source = source as Xep.StatelessFileSharing.HttpSource;
if (http_source != null) { if (http_source != null) {
db.sfs_sources.insert() db.sfs_sources.insert()
@ -243,8 +251,6 @@ public class FileTransfer : Object {
.value(db.sfs_sources.data, http_source.url) .value(db.sfs_sources.data, http_source.url)
.perform(); .perform();
} }
sources_changed();
} }
public File? get_file() { public File? get_file() {

View File

@ -200,7 +200,7 @@ public class Message : Object {
if (!fallbacks_by_ns.has_key(ns_uri)) { if (!fallbacks_by_ns.has_key(ns_uri)) {
fallbacks_by_ns[ns_uri] = new ArrayList<Xep.FallbackIndication.FallbackLocation>(); fallbacks_by_ns[ns_uri] = new ArrayList<Xep.FallbackIndication.FallbackLocation>();
} }
fallbacks_by_ns[ns_uri].add(new Xep.FallbackIndication.FallbackLocation(row[db.body_meta.from_char], row[db.body_meta.to_char])); fallbacks_by_ns[ns_uri].add(new Xep.FallbackIndication.FallbackLocation.partial_body(row[db.body_meta.from_char], row[db.body_meta.to_char]));
break; break;
case Xep.MessageMarkup.NS_URI: case Xep.MessageMarkup.NS_URI:
var types = new ArrayList<Xep.MessageMarkup.SpanType>(); var types = new ArrayList<Xep.MessageMarkup.SpanType>();
@ -212,7 +212,7 @@ public class Message : Object {
var fallbacks = new ArrayList<Xep.FallbackIndication.Fallback>(); var fallbacks = new ArrayList<Xep.FallbackIndication.Fallback>();
foreach (string ns_uri in fallbacks_by_ns.keys) { foreach (string ns_uri in fallbacks_by_ns.keys) {
fallbacks.add(new Xep.FallbackIndication.Fallback(ns_uri, fallbacks_by_ns[ns_uri].to_array())); fallbacks.add(new Xep.FallbackIndication.Fallback(ns_uri, fallbacks_by_ns[ns_uri]));
} }
this.fallbacks = fallbacks; this.fallbacks = fallbacks;
this.markups = markups; this.markups = markups;

View File

@ -24,8 +24,6 @@ public class AvatarManager : StreamInteractionModule, Object {
private string folder = null; private string folder = null;
private HashMap<Jid, string> user_avatars = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func); private HashMap<Jid, string> user_avatars = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func);
private HashMap<Jid, string> vcard_avatars = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func); private HashMap<Jid, string> vcard_avatars = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func);
private HashMap<string, Pixbuf> cached_pixbuf = new HashMap<string, Pixbuf>();
private HashMap<string, Gee.List<SourceFuncWrapper>> pending_pixbuf = new HashMap<string, Gee.List<SourceFuncWrapper>>();
private HashSet<string> pending_fetch = new HashSet<string>(); private HashSet<string> pending_fetch = new HashSet<string>();
private const int MAX_PIXEL = 192; private const int MAX_PIXEL = 192;
@ -104,72 +102,10 @@ public class AvatarManager : StreamInteractionModule, Object {
} }
} }
[Version (deprecated = true)]
public bool has_avatar_cached(Account account, Jid jid) {
string? hash = get_avatar_hash(account, jid);
return hash != null && cached_pixbuf.has_key(hash);
}
public bool has_avatar(Account account, Jid jid) { public bool has_avatar(Account account, Jid jid) {
return get_avatar_hash(account, jid) != null; return get_avatar_hash(account, jid) != null;
} }
[Version (deprecated = true)]
public Pixbuf? get_cached_avatar(Account account, Jid jid_) {
string? hash = get_avatar_hash(account, jid_);
if (hash == null) return null;
if (cached_pixbuf.has_key(hash)) return cached_pixbuf[hash];
return null;
}
[Version (deprecated = true)]
public async Pixbuf? get_avatar(Account account, Jid jid_) {
Jid jid = jid_;
if (!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid_, account)) {
jid = jid_.bare_jid;
}
int source = -1;
string? hash = null;
if (user_avatars.has_key(jid)) {
hash = user_avatars[jid];
source = 1;
} else if (vcard_avatars.has_key(jid)) {
hash = vcard_avatars[jid];
source = 2;
}
if (hash == null) return null;
if (cached_pixbuf.has_key(hash)) {
return cached_pixbuf[hash];
}
XmppStream? stream = stream_interactor.get_stream(account);
if (stream == null || !stream.negotiation_complete) return null;
if (pending_pixbuf.has_key(hash)) {
pending_pixbuf[hash].add(new SourceFuncWrapper(get_avatar.callback));
yield;
return cached_pixbuf[hash];
}
pending_pixbuf[hash] = new ArrayList<SourceFuncWrapper>();
Pixbuf? image = yield get_image(hash);
if (image != null) {
cached_pixbuf[hash] = image;
} else {
if (yield fetch_and_store(stream, account, jid, source, hash)) {
image = yield get_image(hash);
}
cached_pixbuf[hash] = image;
}
foreach (SourceFuncWrapper sfw in pending_pixbuf[hash]) {
sfw.sfun();
}
return image;
}
public void publish(Account account, string file) { public void publish(Account account, string file) {
try { try {
Pixbuf pixbuf = new Pixbuf.from_file(file); Pixbuf pixbuf = new Pixbuf.from_file(file);
@ -329,29 +265,6 @@ public class AvatarManager : StreamInteractionModule, Object {
File file = File.new_for_path(Path.build_filename(folder, id)); File file = File.new_for_path(Path.build_filename(folder, id));
return file.query_exists(); return file.query_exists();
} }
public async Pixbuf? get_image(string id) {
try {
File file = File.new_for_path(Path.build_filename(folder, id));
FileInputStream stream = yield file.read_async(Priority.LOW);
uint8 fbuf[1024];
size_t size;
Checksum checksum = new Checksum (ChecksumType.SHA1);
while ((size = yield stream.read_async(fbuf, Priority.LOW)) > 0) {
checksum.update(fbuf, size);
}
if (checksum.get_string() != id) {
FileUtils.remove(file.get_path());
}
stream.seek(0, SeekType.SET);
return yield new Pixbuf.from_stream_async(stream, null);
} catch (Error e) {
return null;
}
}
} }
} }

View File

@ -223,6 +223,7 @@ public class Database : Qlite.Database {
public class FileThumbnailsTable : Table { public class FileThumbnailsTable : Table {
public Column<int> id = new Column.Integer("id"); public Column<int> id = new Column.Integer("id");
// TODO store data as bytes, not as data uri
public Column<string> uri = new Column.Text("uri") { not_null = true }; public Column<string> uri = new Column.Text("uri") { not_null = true };
public Column<string> mime_type = new Column.Text("mime_type"); public Column<string> mime_type = new Column.Text("mime_type");
public Column<int> width = new Column.Integer("width"); public Column<int> width = new Column.Integer("width");

View File

@ -59,6 +59,7 @@ public class ModuleManager {
module_map[account].add(new Xmpp.MessageModule()); module_map[account].add(new Xmpp.MessageModule());
module_map[account].add(new Xmpp.MessageArchiveManagement.Module()); module_map[account].add(new Xmpp.MessageArchiveManagement.Module());
module_map[account].add(new Xep.MessageCarbons.Module()); module_map[account].add(new Xep.MessageCarbons.Module());
module_map[account].add(new Xep.BitsOfBinary.Module());
module_map[account].add(new Xep.Muc.Module()); module_map[account].add(new Xep.Muc.Module());
module_map[account].add(new Xep.Pubsub.Module()); module_map[account].add(new Xep.Pubsub.Module());
module_map[account].add(new Xep.MessageDeliveryReceipts.Module()); module_map[account].add(new Xep.MessageDeliveryReceipts.Module());

View File

@ -141,6 +141,7 @@ public class Register : StreamInteractionModule, Object{
Gee.List<XmppStreamModule> list = new ArrayList<XmppStreamModule>(); Gee.List<XmppStreamModule> list = new ArrayList<XmppStreamModule>();
list.add(new Iq.Module()); list.add(new Iq.Module());
list.add(new Xep.InBandRegistration.Module()); list.add(new Xep.InBandRegistration.Module());
list.add(new Xep.BitsOfBinary.Module());
XmppStreamResult stream_result = yield Xmpp.establish_stream(jid.domain_jid, list, Application.print_xmpp, XmppStreamResult stream_result = yield Xmpp.establish_stream(jid.domain_jid, list, Application.print_xmpp,
(peer_cert, errors) => { return ConnectionManager.on_invalid_certificate(jid.domainpart, peer_cert, errors); } (peer_cert, errors) => { return ConnectionManager.on_invalid_certificate(jid.domainpart, peer_cert, errors); }

View File

@ -68,10 +68,8 @@ namespace Dino {
Pixbuf thumbnail_pixbuf = pixbuf.scale_simple(thumbnail_width, thumbnail_height, InterpType.BILINEAR); Pixbuf thumbnail_pixbuf = pixbuf.scale_simple(thumbnail_width, thumbnail_height, InterpType.BILINEAR);
uint8[] buffer; uint8[] buffer;
thumbnail_pixbuf.save_to_buffer(out buffer, IMAGE_TYPE); thumbnail_pixbuf.save_to_buffer(out buffer, IMAGE_TYPE);
string base_64 = GLib.Base64.encode(buffer); var thumbnail = new Xep.JingleContentThumbnails.Thumbnail();
string uri = @"data:$MIME_TYPE;base64,$base_64"; thumbnail.data = new Bytes.take(buffer);
Xep.JingleContentThumbnails.Thumbnail thumbnail = new Xep.JingleContentThumbnails.Thumbnail();
thumbnail.uri = uri;
thumbnail.media_type = MIME_TYPE; thumbnail.media_type = MIME_TYPE;
thumbnail.width = thumbnail_width; thumbnail.width = thumbnail_width;
thumbnail.height = thumbnail_height; thumbnail.height = thumbnail_height;

View File

@ -156,6 +156,12 @@ public class Dino.StatelessFileSharing : StreamInteractionModule, Object {
return true; return true;
} }
} }
// Don't process messages that are fallback for legacy clients
if (Xep.StatelessFileSharing.is_sfs_fallback_message(stanza)) {
return true;
}
return false; return false;
} }
} }

View File

@ -26,9 +26,10 @@ namespace Dino {
out_message.body = fallback + out_message.body; out_message.body = fallback + out_message.body;
// Store fallback location // Store fallback location
var fallback_location = new Xep.FallbackIndication.FallbackLocation(0, (int)fallback.char_count()); var fallback_locations = new ArrayList<Xep.FallbackIndication.FallbackLocation>();
fallback_locations.add(new Xep.FallbackIndication.FallbackLocation.partial_body(0, (int)fallback.char_count()));
var fallback_list = new ArrayList<Xep.FallbackIndication.Fallback>(); var fallback_list = new ArrayList<Xep.FallbackIndication.Fallback>();
fallback_list.add(new Xep.FallbackIndication.Fallback(Xep.Replies.NS_URI, new Xep.FallbackIndication.FallbackLocation[] { fallback_location })); fallback_list.add(new Xep.FallbackIndication.Fallback(Xep.Replies.NS_URI, fallback_locations));
out_message.set_fallbacks(fallback_list); out_message.set_fallbacks(fallback_list);
// Adjust markups to new prefix // Adjust markups to new prefix

View File

@ -26,7 +26,7 @@ def compute_version_from_git(git_repo, git):
return None return None
git_describe = subprocess.check_output([git, "describe", "--tags"], cwd=git_repo, text=True).strip() git_describe = subprocess.check_output([git, "describe", "--tags"], cwd=git_repo, text=True).strip()
if git_release_tag == git_describe: if git_release_tag == git_describe:
return git_release_tag return git_release_tag[1:]
matches = re.match("^.*-([0-9]+)-g([0-9a-f]+)$", git_describe) matches = re.match("^.*-([0-9]+)-g([0-9a-f]+)$", git_describe)
if matches is None: if matches is None:
return None return None
@ -34,7 +34,7 @@ def compute_version_from_git(git_repo, git):
git_commit_hash = matches.groups()[1] git_commit_hash = matches.groups()[1]
git_commit_time = subprocess.check_output([git, "show", "--format=%cd", "--date=format:%Y%m%d", "-s"], git_commit_time = subprocess.check_output([git, "show", "--format=%cd", "--date=format:%Y%m%d", "-s"],
cwd=git_repo, text=True).strip() cwd=git_repo, text=True).strip()
return "%s~git%s.%s.%s" % (git_release_tag, git_tag_offset, git_commit_time, git_commit_hash) return "%s~git%s.%s.%s" % (git_release_tag[1:], git_tag_offset, git_commit_time, git_commit_hash)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
pass pass
return None return None

View File

@ -227,7 +227,7 @@
<control>pointing</control> <control>pointing</control>
<control>keyboard</control> <control>keyboard</control>
<control>touch</control> <control>touch</control>
<display_length>small</display_length> <display_length>360</display_length>
</supports> </supports>
<project_license>GPL-3.0+</project_license> <project_license>GPL-3.0+</project_license>
<developer id="im.dino"> <developer id="im.dino">

View File

@ -64,7 +64,7 @@
<control>pointing</control> <control>pointing</control>
<control>keyboard</control> <control>keyboard</control>
<control>touch</control> <control>touch</control>
<display_length>small</display_length> <display_length>360</display_length>
</supports> </supports>
<project_license>GPL-3.0+</project_license> <project_license>GPL-3.0+</project_license>
<developer id="im.dino"> <developer id="im.dino">

View File

@ -69,6 +69,7 @@
<child> <child>
<object class="AdwActionRow" id="xmpp_address"> <object class="AdwActionRow" id="xmpp_address">
<property name="title" translatable="yes">XMPP Address</property> <property name="title" translatable="yes">XMPP Address</property>
<property name="subtitle_selectable">True</property>
<style> <style>
<class name="property"/> <class name="property"/>
</style> </style>

View File

@ -20,7 +20,7 @@ public class FileDefaultWidget : Box {
private FileTransfer.State state; private FileTransfer.State state;
public FileDefaultWidget() { public void init_updating_file_info() {
EventControllerMotion this_motion_events = new EventControllerMotion(); EventControllerMotion this_motion_events = new EventControllerMotion();
this.add_controller(this_motion_events); this.add_controller(this_motion_events);
this_motion_events.enter.connect(on_pointer_entered_event); this_motion_events.enter.connect(on_pointer_entered_event);
@ -39,6 +39,14 @@ public class FileDefaultWidget : Box {
}); });
} }
public void set_static_file_info(string? mime_type) {
spinner.stop(); // A hidden spinning spinner still uses CPU. Deactivate asap
image_stack.set_visible_child_name("content_type_image");
content_type_image.icon_name = get_file_icon_name(mime_type);
mime_label.label = mime_type != null ? ContentType.get_description(mime_type) : null;
}
public void update_file_info(string? mime_type, FileTransfer.State state, bool direction, int64 size, int64 transferred_bytes) { public void update_file_info(string? mime_type, FileTransfer.State state, bool direction, int64 size, int64 transferred_bytes) {
this.state = state; this.state = state;

View File

@ -217,37 +217,8 @@ public class FileImageWidget : Widget {
} }
public static Pixbuf? parse_thumbnail(Xep.JingleContentThumbnails.Thumbnail thumbnail) { public static Pixbuf? parse_thumbnail(Xep.JingleContentThumbnails.Thumbnail thumbnail) {
string[] splits = thumbnail.uri.split(":", 2); MemoryInputStream input_stream = new MemoryInputStream.from_data(thumbnail.data.get_data());
if (splits.length != 2) { return new Pixbuf.from_stream(input_stream);
warning("Thumbnail parsing error: ':' not found");
return null;
}
if (splits[0] != "data") {
warning("Unsupported thumbnail: unimplemented uri type\n");
return null;
}
splits = splits[1].split(";", 2);
if (splits.length != 2) {
warning("Thumbnail parsing error: ';' not found");
return null;
}
if (splits[0] != "image/png") {
warning("Unsupported thumbnail: unsupported mime-type\n");
return null;
}
splits = splits[1].split(",", 2);
if (splits.length != 2) {
warning("Thumbnail parsing error: ',' not found");
return null;
}
if (splits[0] != "base64") {
warning("Unsupported thumbnail: data is not base64 encoded\n");
return null;
}
uint8[] data = Base64.decode(splits[1]);
MemoryInputStream input_stream = new MemoryInputStream.from_data(data);
Pixbuf pixbuf = new Pixbuf.from_stream(input_stream);
return pixbuf;
} }
public static bool can_display(FileTransfer file_transfer) { public static bool can_display(FileTransfer file_transfer) {

View File

@ -213,6 +213,7 @@ public class FileDefaultWidgetController : Object {
public void set_file_transfer(FileTransfer file_transfer) { public void set_file_transfer(FileTransfer file_transfer) {
this.file_transfer = file_transfer; this.file_transfer = file_transfer;
widget.init_updating_file_info();
widget.name_label.label = file_transfer.file_name; widget.name_label.label = file_transfer.file_name;
file_transfer.bind_property("state", this, "file-transfer-state"); file_transfer.bind_property("state", this, "file-transfer-state");

View File

@ -23,6 +23,7 @@ namespace Dino.Ui.ConversationDetails {
} }
public void bind_dialog(Model.ConversationDetails model, ViewModel.ConversationDetails view_model, StreamInteractor stream_interactor) { public void bind_dialog(Model.ConversationDetails model, ViewModel.ConversationDetails view_model, StreamInteractor stream_interactor) {
// Set some data once
view_model.avatar = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(model.conversation); view_model.avatar = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(model.conversation);
view_model.show_blocked = model.conversation.type_ == Conversation.Type.CHAT && stream_interactor.get_module(BlockingManager.IDENTITY).is_supported(model.conversation.account); view_model.show_blocked = model.conversation.type_ == Conversation.Type.CHAT && stream_interactor.get_module(BlockingManager.IDENTITY).is_supported(model.conversation.account);
view_model.members_sorted.set_model(model.members); view_model.members_sorted.set_model(model.members);
@ -36,6 +37,7 @@ namespace Dino.Ui.ConversationDetails {
affiliation = conference_member.affiliation affiliation = conference_member.affiliation
}; };
}); });
view_model.account_jid = stream_interactor.get_accounts().size > 1 ? model.conversation.account.bare_jid.to_string() : null;
if (model.domain_blocked) { if (model.domain_blocked) {
view_model.blocked = DOMAIN; view_model.blocked = DOMAIN;
@ -45,6 +47,7 @@ namespace Dino.Ui.ConversationDetails {
view_model.blocked = UNBLOCK; view_model.blocked = UNBLOCK;
} }
// Bind properties
model.display_name.bind_property("display-name", view_model, "name", BindingFlags.SYNC_CREATE); model.display_name.bind_property("display-name", view_model, "name", BindingFlags.SYNC_CREATE);
model.conversation.bind_property("notify-setting", view_model, "notification", BindingFlags.SYNC_CREATE, (_, from, ref to) => { model.conversation.bind_property("notify-setting", view_model, "notification", BindingFlags.SYNC_CREATE, (_, from, ref to) => {
switch (model.conversation.get_notification_setting(stream_interactor)) { switch (model.conversation.get_notification_setting(stream_interactor)) {
@ -135,38 +138,65 @@ namespace Dino.Ui.ConversationDetails {
}); });
} }
public Dialog setup_dialog(Conversation conversation, StreamInteractor stream_interactor, Window parent) { public void set_about_rows(Model.ConversationDetails model, ViewModel.ConversationDetails view_model, StreamInteractor stream_interactor) {
var dialog = new Dialog() { transient_for = parent }; view_model.about_rows.append(new ViewModel.PreferencesRow.Text() {
var model = new Model.ConversationDetails();
model.populate(stream_interactor, conversation);
// populate_dialog(model, conversation, stream_interactor);
bind_dialog(model, dialog.model, stream_interactor);
dialog.model.about_rows.append(new ViewModel.PreferencesRow.Text() {
title = _("XMPP Address"), title = _("XMPP Address"),
text = conversation.counterpart.to_string() text = model.conversation.counterpart.to_string()
}); });
if (model.conversation.type_ == Conversation.Type.CHAT) { if (model.conversation.type_ == Conversation.Type.CHAT) {
var about_row = new ViewModel.PreferencesRow.Entry() { var about_row = new ViewModel.PreferencesRow.Entry() {
title = _("Display name"), title = _("Display name"),
text = dialog.model.name text = model.display_name.display_name
}; };
about_row.changed.connect(() => { about_row.changed.connect(() => {
if (about_row.text != Util.get_conversation_display_name(stream_interactor, conversation)) { if (about_row.text != model.display_name.display_name) {
stream_interactor.get_module(RosterManager.IDENTITY).set_jid_handle(conversation.account, conversation.counterpart, about_row.text); stream_interactor.get_module(RosterManager.IDENTITY).set_jid_handle(model.conversation.account, model.conversation.counterpart, about_row.text);
} }
}); });
dialog.model.about_rows.append(about_row); view_model.about_rows.append(about_row);
} }
if (model.conversation.type_ == Conversation.Type.GROUPCHAT) { if (model.conversation.type_ == Conversation.Type.GROUPCHAT) {
var topic = stream_interactor.get_module(MucManager.IDENTITY).get_groupchat_subject(conversation.counterpart, conversation.account); var topic = stream_interactor.get_module(MucManager.IDENTITY).get_groupchat_subject(model.conversation.counterpart, model.conversation.account);
if (topic != null && topic != "") {
dialog.model.about_rows.append(new ViewModel.PreferencesRow.Text() { Ui.ViewModel.PreferencesRow.Any preferences_row = null;
Jid? own_muc_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(model.conversation.counterpart, model.conversation.account);
if (own_muc_jid != null) {
Xep.Muc.Role? own_role = stream_interactor.get_module(MucManager.IDENTITY).get_role(own_muc_jid, model.conversation.account);
if (own_role != null) {
if (own_role == MODERATOR) {
var preferences_row_entry = new ViewModel.PreferencesRow.Entry() {
title = _("Topic"),
text = topic
};
preferences_row_entry.changed.connect(() => {
if (preferences_row_entry.text != topic) {
stream_interactor.get_module(MucManager.IDENTITY).change_subject(model.conversation.account, model.conversation.counterpart, preferences_row_entry.text);
}
});
preferences_row = preferences_row_entry;
}
}
}
if (preferences_row == null && topic != null && topic != "") {
preferences_row = new ViewModel.PreferencesRow.Text() {
title = _("Topic"), title = _("Topic"),
text = Util.parse_add_markup(topic, null, true, true) text = Util.parse_add_markup(topic, null, true, true)
}); };
}
if (preferences_row != null) {
view_model.about_rows.append(preferences_row);
} }
} }
}
public Dialog setup_dialog(Conversation conversation, StreamInteractor stream_interactor, Window parent) {
var dialog = new Dialog() { transient_for = parent };
var model = new Model.ConversationDetails();
model.populate(stream_interactor, conversation);
bind_dialog(model, dialog.model, stream_interactor);
set_about_rows(model, dialog.model, stream_interactor);
dialog.close_request.connect(() => { dialog.close_request.connect(() => {
// Only send the config form if something was changed // Only send the config form if something was changed
if (model.data_form_bak != null && model.data_form_bak != model.data_form.stanza_node.to_string()) { if (model.data_form_bak != null && model.data_form_bak != model.data_form.stanza_node.to_string()) {

View File

@ -73,7 +73,7 @@ public class FileSendOverlay {
if (widget == null) { if (widget == null) {
FileDefaultWidget default_widget = new FileDefaultWidget(); FileDefaultWidget default_widget = new FileDefaultWidget();
default_widget.name_label.label = file_name; default_widget.name_label.label = file_name;
default_widget.update_file_info(mime_type, FileTransfer.State.COMPLETE, FileTransfer.DIRECTION_SENT, (long)file_info.get_size(), 0); default_widget.set_static_file_info(mime_type);
widget = default_widget; widget = default_widget;
} }

View File

@ -115,6 +115,12 @@ public static ViewModel.PreferencesRow.Any? get_data_form_field_view_model(DataF
return null; return null;
} }
var media_node = field.node.get_subnode("media", "urn:xmpp:media-element");
if (media_node != null) {
view_model.media_type = media_node.get_attribute("type", "urn:xmpp:media-element");
view_model.media_uri = media_node.get_deep_string_content("uri");
}
view_model.title = label; view_model.title = label;
return view_model; return view_model;
} }

View File

@ -1,4 +1,5 @@
using Gee; using Gee;
using Gdk;
using Gtk; using Gtk;
using Dino.Entities; using Dino.Entities;
@ -24,6 +25,11 @@ namespace Dino.Ui.Util {
var entry_view_model = preferences_row as ViewModel.PreferencesRow.Entry; var entry_view_model = preferences_row as ViewModel.PreferencesRow.Entry;
if (entry_view_model != null) { if (entry_view_model != null) {
Adw.EntryRow view = new Adw.EntryRow() { title = entry_view_model.title, show_apply_button=true }; Adw.EntryRow view = new Adw.EntryRow() { title = entry_view_model.title, show_apply_button=true };
if (preferences_row.media_uri != null) {
var bytes = Xmpp.get_data_for_uri(preferences_row.media_uri);
Picture picture = new Picture.for_paintable(Texture.from_bytes(bytes));
view.add_suffix(picture);
}
entry_view_model.bind_property("text", view, "text", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL, (_, from, ref to) => { entry_view_model.bind_property("text", view, "text", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL, (_, from, ref to) => {
var str = (string) from; var str = (string) from;
to = str ?? ""; to = str ?? "";

View File

@ -40,6 +40,8 @@ public class Dino.Ui.ViewModel.ConversationDetails : Object {
public BlockState blocked { get; set; } public BlockState blocked { get; set; }
public GLib.ListStore about_rows = new GLib.ListStore(typeof(PreferencesRow.Any)); public GLib.ListStore about_rows = new GLib.ListStore(typeof(PreferencesRow.Any));
public string? account_jid { get; set; }
public GLib.ListStore settings_rows = new GLib.ListStore(typeof(PreferencesRow.Any)); public GLib.ListStore settings_rows = new GLib.ListStore(typeof(PreferencesRow.Any));
public GLib.ListStore room_configuration_rows { get; set; } public GLib.ListStore room_configuration_rows { get; set; }
public MapListModel members = new MapListModel(null, null); public MapListModel members = new MapListModel(null, null);

View File

@ -7,6 +7,9 @@ using Gtk;
namespace Dino.Ui.ViewModel.PreferencesRow { namespace Dino.Ui.ViewModel.PreferencesRow {
public abstract class Any : Object { public abstract class Any : Object {
public string title { get; set; } public string title { get; set; }
public string? media_type { get; set; }
public string? media_uri { get; set; }
} }
public class Text : Any { public class Text : Any {

View File

@ -198,6 +198,12 @@ namespace Dino.Ui.ConversationDetails {
if (model.room_configuration_rows != null && model.room_configuration_rows.get_n_items() > 0) { if (model.room_configuration_rows != null && model.room_configuration_rows.get_n_items() > 0) {
about_box.append(Util.rows_to_preference_group(model.room_configuration_rows, _("Room Configuration"))); about_box.append(Util.rows_to_preference_group(model.room_configuration_rows, _("Room Configuration")));
} }
if (model.account_jid != null) {
var account_label = new Label(@"via $(model.account_jid)") { halign=Align.START, margin_start=14, margin_top=4 };
account_label.add_css_class("dim-label");
about_box.append(account_label);
}
} }
public void add_encryption_tab_element(Adw.PreferencesGroup preferences_group) { public void add_encryption_tab_element(Adw.PreferencesGroup preferences_group) {

View File

@ -3,7 +3,6 @@ dependencies = [
dep_gee, dep_gee,
dep_glib, dep_glib,
dep_gmodule, dep_gmodule,
dep_gtk4,
dep_libsoup, dep_libsoup,
dep_qlite, dep_qlite,
dep_xmpp_vala, dep_xmpp_vala,

View File

@ -1,5 +1,4 @@
using Gee; using Gee;
using Gtk;
using Dino.Entities; using Dino.Entities;
using Xmpp; using Xmpp;

View File

@ -1,8 +1,8 @@
dependencies = [ dependencies = [
dep_crypto_vala, dep_crypto_vala,
dep_dino, dep_dino,
dep_gdk_pixbuf,
dep_gee, dep_gee,
dep_gio,
dep_glib, dep_glib,
dep_gmodule, dep_gmodule,
dep_gnutls, dep_gnutls,

View File

@ -1,6 +1,5 @@
dependencies = [ dependencies = [
dep_dino, dep_dino,
dep_gdk_pixbuf,
dep_gee, dep_gee,
dep_glib, dep_glib,
dep_gmodule, dep_gmodule,

View File

@ -218,6 +218,7 @@ dino_plugins_rtp_voice_processor_process_stream(void *native_ptr, GstAudioInfo *
frame.samples_per_channel_ = info->rate / 100; frame.samples_per_channel_ = info->rate / 100;
memcpy(frame.data_, map.data, frame.samples_per_channel_ * info->bpf); memcpy(frame.data_, map.data, frame.samples_per_channel_ * info->bpf);
apm->set_stream_delay_ms(native->stream_delay);
err = apm->ProcessStream(&frame); err = apm->ProcessStream(&frame);
if (err >= 0) memcpy(map.data, frame.data_, frame.samples_per_channel_ * info->bpf); if (err >= 0) memcpy(map.data, frame.data_, frame.samples_per_channel_ * info->bpf);

View File

@ -1,5 +1,4 @@
dependencies = [ dependencies = [
dep_gdk_pixbuf,
dep_gee, dep_gee,
dep_gio, dep_gio,
dep_glib, dep_glib,
@ -96,6 +95,7 @@ sources = files(
'src/module/xep/0199_ping.vala', 'src/module/xep/0199_ping.vala',
'src/module/xep/0203_delayed_delivery.vala', 'src/module/xep/0203_delayed_delivery.vala',
'src/module/xep/0215_external_service_discovery.vala', 'src/module/xep/0215_external_service_discovery.vala',
'src/module/xep/0231_bits_of_binary.vala',
'src/module/xep/0234_jingle_file_transfer.vala', 'src/module/xep/0234_jingle_file_transfer.vala',
'src/module/xep/0249_direct_muc_invitations.vala', 'src/module/xep/0249_direct_muc_invitations.vala',
'src/module/xep/0260_jingle_socks5_bytestreams.vala', 'src/module/xep/0260_jingle_socks5_bytestreams.vala',
@ -130,7 +130,6 @@ sources = files(
'src/module/xep/0447_stateless_file_sharing.vala', 'src/module/xep/0447_stateless_file_sharing.vala',
'src/module/xep/0461_replies.vala', 'src/module/xep/0461_replies.vala',
'src/module/xep/0482_call_invites.vala', 'src/module/xep/0482_call_invites.vala',
'src/module/xep/pixbuf_storage.vala',
'src/util.vala', 'src/util.vala',
) )
c_args = [ c_args = [

View File

@ -12,6 +12,18 @@ public string random_uuid() {
return "%08x-%04x-%04x-%04x-%04x%08x".printf(b1, b2, b3, b4, b5_1, b5_2); return "%08x-%04x-%04x-%04x-%04x%08x".printf(b1, b2, b3, b4, b5_1, b5_2);
} }
public Bytes? get_data_for_uri(string uri) {
if (uri.has_suffix("@bob.xmpp.org")) {
string cid = uri.replace("cid:", "");
return Xep.BitsOfBinary.known_bobs[cid];
} else if (uri.has_prefix("data:image/png;base64,")) {
return new Bytes.take(Base64.decode(uri.replace("data:image/png;base64,", "")));
} else {
warning("Couldn't parse data from uri %s", uri);
return null;
}
}
public abstract class StanzaListener<T> : OrderedListener { public abstract class StanzaListener<T> : OrderedListener {
public abstract async bool run(XmppStream stream, T stanza); public abstract async bool run(XmppStream stream, T stanza);

View File

@ -0,0 +1,66 @@
using Gee;
namespace Xmpp.Xep.BitsOfBinary {
public const string NS_URI = "urn:xmpp:bob";
public static HashMap<string, Bytes> known_bobs = null;
public class Module : XmppStreamModule {
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "bits_of_binary");
private ReceivedPipelineListener received_pipeline_listener = new ReceivedPipelineListener();
public override void attach(XmppStream stream) {
known_bobs = new HashMap<string, Bytes>();
var message_module = stream.get_module(MessageModule.IDENTITY);
if (message_module != null) {
message_module.received_pipeline.connect(received_pipeline_listener);
}
stream.received_iq_stanza.connect(on_received_iq_stanza);
}
public override void detach(XmppStream stream) {
var message_module = stream.get_module(MessageModule.IDENTITY);
if (message_module != null) {
message_module.received_pipeline.disconnect(received_pipeline_listener);
}
stream.received_iq_stanza.disconnect(on_received_iq_stanza);
}
private async void on_received_iq_stanza(XmppStream stream, StanzaNode node) {
if (node.sub_nodes == null || node.sub_nodes.size == 0) return;
Gee.List<StanzaNode> data_nodes = node.sub_nodes[0].get_subnodes("data", NS_URI);
foreach (var data_node in data_nodes) {
string cid = data_node.get_attribute("cid", NS_URI);
if (cid == null) continue;
known_bobs[cid] = new Bytes.take(Base64.decode(data_node.get_string_content()));
}
}
public override string get_ns() { return NS_URI; }
public override string get_id() { return IDENTITY.id; }
}
public class ReceivedPipelineListener : StanzaListener<MessageStanza> {
private const string[] after_actions_const = {};
public override string action_group { get { return ""; } }
public override string[] after_actions { get { return after_actions_const; } }
public override async bool run(XmppStream stream, MessageStanza message) {
Gee.List<StanzaNode> data_nodes = message.stanza.get_subnodes("data", NS_URI);
foreach (var data_node in data_nodes) {
string cid = data_node.get_attribute("cid", NS_URI);
if (cid == null) continue;
known_bobs[cid] = new Bytes.take(Base64.decode(data_node.get_string_content()));
}
return false;
}
}
}

View File

@ -13,14 +13,16 @@ namespace Xmpp.Xep.JingleContentThumbnails {
} }
public class Thumbnail { public class Thumbnail {
public string uri; public Bytes data;
public string? media_type; public string? media_type;
public int width; public int width;
public int height; public int height;
public StanzaNode to_stanza_node() { public StanzaNode to_stanza_node() {
string data_uri = "data:image/png;base64," + Base64.encode(data.get_data());
StanzaNode node = new StanzaNode.build("thumbnail", NS_URI).add_self_xmlns() StanzaNode node = new StanzaNode.build("thumbnail", NS_URI).add_self_xmlns()
.put_attribute("uri", this.uri); .put_attribute("uri", data_uri);
if (this.media_type != null) { if (this.media_type != null) {
node.put_attribute("media-type", this.media_type); node.put_attribute("media-type", this.media_type);
} }
@ -34,11 +36,13 @@ namespace Xmpp.Xep.JingleContentThumbnails {
} }
public static Thumbnail? from_stanza_node(StanzaNode node) { public static Thumbnail? from_stanza_node(StanzaNode node) {
string uri = node.get_attribute("uri");
if (uri == null) return null;
Bytes data = Xmpp.get_data_for_uri(uri);
Thumbnail thumbnail = new Thumbnail(); Thumbnail thumbnail = new Thumbnail();
thumbnail.uri = node.get_attribute("uri"); thumbnail.data = data;
if (thumbnail.uri == null) {
return null;
}
thumbnail.media_type = node.get_attribute("media-type"); thumbnail.media_type = node.get_attribute("media-type");
thumbnail.width = node.get_attribute_int("width"); thumbnail.width = node.get_attribute_int("width");
thumbnail.height = node.get_attribute_int("height"); thumbnail.height = node.get_attribute_int("height");

View File

@ -6,23 +6,28 @@ namespace Xmpp.Xep.FallbackIndication {
public class Fallback { public class Fallback {
public string ns_uri { get; set; } public string ns_uri { get; set; }
public FallbackLocation[] locations; public Gee.List<FallbackLocation> locations;
public Fallback(string ns_uri, Gee.List<FallbackLocation> locations) {
public Fallback(string ns_uri, FallbackLocation[] locations) {
this.ns_uri = ns_uri; this.ns_uri = ns_uri;
this.locations = locations; this.locations = locations;
} }
} }
public class FallbackLocation { public class FallbackLocation {
public bool is_whole { get { return from_char == -1 && to_char == -1; } }
public int from_char { get; set; } public int from_char { get; set; }
public int to_char { get; set; } public int to_char { get; set; }
public FallbackLocation(int from_char, int to_char) { public FallbackLocation.partial_body(int from_char, int to_char) {
this.from_char = from_char; this.from_char = from_char;
this.to_char = to_char; this.to_char = to_char;
} }
public FallbackLocation.whole_body() {
this.from_char = -1;
this.to_char = -1;
}
} }
public static void set_fallback(MessageStanza message, Fallback fallback) { public static void set_fallback(MessageStanza message, Fallback fallback) {
@ -30,10 +35,12 @@ namespace Xmpp.Xep.FallbackIndication {
.add_self_xmlns() .add_self_xmlns()
.put_attribute("for", fallback.ns_uri); .put_attribute("for", fallback.ns_uri);
foreach (FallbackLocation location in fallback.locations) { foreach (FallbackLocation location in fallback.locations) {
fallback_node.put_node(new StanzaNode.build("body", NS_URI) var node = new StanzaNode.build("body", NS_URI).add_self_xmlns();
.add_self_xmlns() if (!location.is_whole) {
.put_attribute("start", location.from_char.to_string()) node.put_attribute("start", location.from_char.to_string())
.put_attribute("end", location.to_char.to_string())); .put_attribute("end", location.to_char.to_string());
}
fallback_node.put_node(node);
} }
message.stanza.put_node(fallback_node); message.stanza.put_node(fallback_node);
} }
@ -48,18 +55,24 @@ namespace Xmpp.Xep.FallbackIndication {
string? ns_uri = fallback_node.get_attribute("for"); string? ns_uri = fallback_node.get_attribute("for");
if (ns_uri == null) continue; if (ns_uri == null) continue;
Gee.List<StanzaNode> body_nodes = fallback_node.get_subnodes("body", NS_URI);
if (body_nodes.is_empty) continue;
var locations = new ArrayList<FallbackLocation>(); var locations = new ArrayList<FallbackLocation>();
foreach (StanzaNode body_node in body_nodes) { if (fallback_node.get_all_subnodes().is_empty) {
int start_char = body_node.get_attribute_int("start", -1); locations.add(new FallbackLocation.whole_body());
int end_char = body_node.get_attribute_int("end", -1); } else {
if (start_char == -1 || end_char == -1) continue; Gee.List<StanzaNode> body_nodes = fallback_node.get_subnodes("body", NS_URI);
locations.add(new FallbackLocation(start_char, end_char)); foreach (StanzaNode body_node in body_nodes) {
int start_char = body_node.get_attribute_int("start", -1);
int end_char = body_node.get_attribute_int("end", -1);
if (start_char == -1 && end_char == -1) {
locations.add(new FallbackLocation.whole_body());
continue;
}
if (start_char == -1 || end_char == -1) continue;
locations.add(new FallbackLocation.partial_body(start_char, end_char));
}
} }
if (locations.is_empty) continue; if (locations.is_empty) continue;
ret.add(new Fallback(ns_uri, locations.to_array())); ret.add(new Fallback(ns_uri, locations));
} }
return ret; return ret;

View File

@ -20,7 +20,9 @@ namespace Xmpp.Xep.FileMetadataElement {
node.put_node(new StanzaNode.build("name", NS_URI).put_node(new StanzaNode.text(this.name))); node.put_node(new StanzaNode.build("name", NS_URI).put_node(new StanzaNode.text(this.name)));
} }
if (this.mime_type != null) { if (this.mime_type != null) {
// TODO remove the media_type node, it was implemented by accident and temporary provides backwards-compatibility
node.put_node(new StanzaNode.build("media_type", NS_URI).put_node(new StanzaNode.text(this.mime_type))); node.put_node(new StanzaNode.build("media_type", NS_URI).put_node(new StanzaNode.text(this.mime_type)));
node.put_node(new StanzaNode.build("media-type", NS_URI).put_node(new StanzaNode.text(this.mime_type)));
} }
if (this.size != -1) { if (this.size != -1) {
node.put_node(new StanzaNode.build("size", NS_URI).put_node(new StanzaNode.text(this.size.to_string()))); node.put_node(new StanzaNode.build("size", NS_URI).put_node(new StanzaNode.text(this.size.to_string())));
@ -65,7 +67,12 @@ namespace Xmpp.Xep.FileMetadataElement {
if (desc_node != null && desc_node.get_string_content() != null) { if (desc_node != null && desc_node.get_string_content() != null) {
metadata.desc = desc_node.get_string_content(); metadata.desc = desc_node.get_string_content();
} }
StanzaNode? mime_node = file_node.get_subnode("media_type"); // TODO remove media_type handling, it was implemented by accident and temporary provides backwards-compatibility
StanzaNode? mime_node_bad = file_node.get_subnode("media_type");
if (mime_node_bad != null && mime_node_bad.get_string_content() != null) {
metadata.mime_type = mime_node_bad.get_string_content();
}
StanzaNode? mime_node = file_node.get_subnode("media-type");
if (mime_node != null && mime_node.get_string_content() != null) { if (mime_node != null && mime_node.get_string_content() != null) {
metadata.mime_type = mime_node.get_string_content(); metadata.mime_type = mime_node.get_string_content();
} }

View File

@ -11,7 +11,7 @@ namespace Xmpp.Xep.StatelessFileSharing {
var metadata = Xep.FileMetadataElement.get_file_metadata(file_sharing_node); var metadata = Xep.FileMetadataElement.get_file_metadata(file_sharing_node);
if (metadata == null) continue; if (metadata == null) continue;
var sources_node = message.stanza.get_subnode("sources", NS_URI); var sources_node = file_sharing_node.get_subnode("sources", NS_URI);
ret.add(new FileShare() { ret.add(new FileShare() {
id = file_sharing_node.get_attribute("id", NS_URI), id = file_sharing_node.get_attribute("id", NS_URI),
@ -44,6 +44,16 @@ namespace Xmpp.Xep.StatelessFileSharing {
return ret; return ret;
} }
public static bool is_sfs_fallback_message(MessageStanza message) {
Gee.List<FallbackIndication.Fallback> fallbacks = Xep.FallbackIndication.get_fallbacks(message);
foreach (var fallback in fallbacks) {
if (fallback.ns_uri == StatelessFileSharing.NS_URI && fallback.locations.any_match((it) => it.is_whole)) {
return true;
}
}
return false;
}
// Currently only returns a single http source // Currently only returns a single http source
private static Gee.List<Source>? get_sources(StanzaNode sources_node) { private static Gee.List<Source>? get_sources(StanzaNode sources_node) {
string? url = HttpSchemeForUrlData.get_url(sources_node); string? url = HttpSchemeForUrlData.get_url(sources_node);

View File

@ -1,9 +0,0 @@
using Gdk;
namespace Xmpp.Xep {
public interface PixbufStorage : Object {
public abstract void store(string id, Bytes data);
public abstract bool has_image(string id);
public abstract async Pixbuf? get_image(string id);
}
}