Changes
5 changed files (+0/-701)
-
-
@@ -238,19 +238,6 @@ yield;return (owned) result; } public async Image.GetResult? get_image(string image_key, Image.GetOptions options) { GLib.SourceFunc callback = get_image.callback; Image.GetResult? result = null; new GLib.Thread<void>("get_image", () => { result = conn.get_image(image_key, options); GLib.Idle.add((owned) callback); }); yield; return (owned) result; } public string? get_image_url(string image_key, Image.GetOptions options) { return conn.get_image_url(image_key, options); }
-
-
-
@@ -413,8 +413,6 @@ const ControlListener = Listener(transport.ControlResultCode, 3_000);const SeekListener = Listener(transport.SeekResultCode, 2_000); const ChangeVolumeListener = Listener(transport.VolumeControlResultCode, 2_000); const ImageDownloader = image.Downloader(.{}); const EventsQueue = std.DoublyLinkedList(*Event); internal: *Internal,
-
@@ -432,7 +430,6 @@ arc: Arc = .{},saved_token: ?[]const u8 = null, browse_listeners: BrowseListener.Store, load_listeners: LoadListener.Store, image_downloads: ImageDownloader, control_events: ControlListener.Store, seek_events: SeekListener.Store, change_volume_events: ChangeVolumeListener.Store,
-
@@ -472,7 +469,6 @@ .host = host,.saved_token = saved_token, .browse_listeners = undefined, .load_listeners = undefined, .image_downloads = undefined, .control_events = undefined, .seek_events = undefined, .change_volume_events = undefined,
-
@@ -482,7 +478,6 @@fn initListeners(self: *Internal) void { self.browse_listeners = BrowseListener.Store.init(allocator); self.load_listeners = LoadListener.Store.init(allocator); self.image_downloads = ImageDownloader.init(self.tsa.allocator()); self.control_events = ControlListener.Store.init(allocator); self.seek_events = SeekListener.Store.init(allocator); self.change_volume_events = ChangeVolumeListener.Store.init(allocator);
-
@@ -492,7 +487,6 @@ fn deinitListeners(self: *Internal) void {self.browse_listeners.deinit(); self.load_listeners.deinit(); self.image_downloads.deinit(); self.control_events.deinit(); self.seek_events.deinit(); self.change_volume_events.deinit();
-
@@ -1513,48 +1507,6 @@ },} } pub fn getImage( ptr: ?*Connection, image_key_ptr: [*:0]const u8, opts_ptr: ?*image.GetOptions, ) callconv(.C) ?*image.GetResult { var self = ptr orelse @panic( std.fmt.comptimePrint("Received null pointer on {s}_{s}", .{ cname, @src().fn_name }), ); const image_key = std.mem.span(image_key_ptr); const req = req: { const opts = image.GetOptions.retain(opts_ptr); defer opts.release(); var r = ImageService.Get.Request{ .image_key = image_key, .format = if (opts.internal.data.content_type) |t| switch (t) { .jpeg => .jpeg, .png => .png, } else null, }; if (opts.internal.data.size) |size| { r.scale = switch (size.scaling_method) { .fit => .fit, .fill => .fill, .stretch => .stretch, }; r.width = size.width; r.height = size.height; } break :req r; }; return self.internal.image_downloads.download(self.internal.tsa.allocator(), &req, self.internal.server) catch { std.log.err("Out of memory during image download", .{}); return image.GetResult.makeRetainedError(.out_of_memory) catch null; }; } pub fn getImageUrl( ptr: ?*Connection, image_key_ptr: [*:0]const u8,
-
@@ -1612,7 +1564,6 @@ @export(&increaseVolume, .{ .name = std.fmt.comptimePrint("{s}_increase_volume", .{cname}) });@export(&decreaseVolume, .{ .name = std.fmt.comptimePrint("{s}_decrease_volume", .{cname}) }); @export(&changeVolume, .{ .name = std.fmt.comptimePrint("{s}_change_volume", .{cname}) }); @export(&requestBrowse, .{ .name = std.fmt.comptimePrint("{s}_browse", .{cname}) }); @export(&getImage, .{ .name = std.fmt.comptimePrint("{s}_get_image", .{cname}) }); @export(&getImageUrl, .{ .name = std.fmt.comptimePrint("{s}_get_image_url", .{cname}) }); @export(&disconnect, .{ .name = std.fmt.comptimePrint("{s}_disconnect", .{cname}) }); }
-
-
-
@@ -32,16 +32,6 @@ jpeg = 0,png = 1, }; pub const GetResultCode = enum(c_int) { ok = 0, unknown_error = 1, out_of_memory = 2, unexpected_response = 3, socket_closed = 4, failed_to_send = 5, timeout = 6, }; pub const GetOptions = extern struct { const cname = "plac_image_get_options"; const allocator = std.heap.c_allocator;
-
@@ -137,558 +127,6 @@ @export(&setContentType, .{ .name = std.fmt.comptimePrint("{s}_set_content_type", .{cname}) });} }; pub const Image = extern struct { const cname = "plac_image_image"; const allocator = std.heap.c_allocator; internal: *Internal, content_type: ContentType, data_ptr: [*]const u8, data_len: usize, const Internal = struct { arc: Arc = .{}, }; pub fn make(src: *const ImageService.Get.Response) std.mem.Allocator.Error!*@This() { const internal = try allocator.create(Internal); errdefer allocator.destroy(internal); internal.* = .{}; const data = try allocator.dupe(u8, src.data); errdefer allocator.free(data); const self = try allocator.create(@This()); errdefer allocator.destroy(self); self.* = .{ .internal = internal, .content_type = switch (src.content_type) { .jpeg => ContentType.jpeg, .png => ContentType.png, }, .data_ptr = data.ptr, .data_len = data.len, }; return self; } pub fn retain(ptr: ?*@This()) callconv(.C) *@This() { var self = ptr orelse @panic( std.fmt.comptimePrint("Received null pointer on {s}_{s}", .{ cname, @src().fn_name }), ); self.internal.arc.ref(); return self; } pub fn release(ptr: ?*@This()) callconv(.C) void { var self = ptr orelse @panic( std.fmt.comptimePrint("Received null pointer on {s}_{s}", .{ cname, @src().fn_name }), ); if (self.internal.arc.unref()) { freelog(self); allocator.free(self.data_ptr[0..self.data_len]); allocator.destroy(self.internal); allocator.destroy(self); } } pub fn export_capi() void { @export(&retain, .{ .name = std.fmt.comptimePrint("{s}_retain", .{cname}) }); @export(&release, .{ .name = std.fmt.comptimePrint("{s}_release", .{cname}) }); } }; pub const GetResult = extern struct { const cname = "plac_image_get_result"; const allocator = std.heap.c_allocator; internal: *Internal, code: GetResultCode = .ok, image: ?*Image = null, const Internal = struct { arc: Arc = .{}, }; pub fn make(src: *const ImageService.Get.Response) std.mem.Allocator.Error!*@This() { const internal = try allocator.create(Internal); errdefer allocator.destroy(internal); internal.* = .{}; const self = try allocator.create(@This()); errdefer allocator.destroy(self); const image = try Image.make(src); _ = image.retain(); self.* = .{ .internal = internal, .image = image, }; return self; } pub inline fn makeRetained(src: *const ImageService.Get.Response) std.mem.Allocator.Error!*@This() { const result = try make(src); return result.retain(); } pub fn makeError(code: GetResultCode) std.mem.Allocator.Error!*@This() { const internal = try allocator.create(Internal); errdefer allocator.destroy(internal); internal.* = .{}; const self = try allocator.create(@This()); errdefer allocator.destroy(self); self.* = .{ .internal = internal, .code = code, }; return self; } pub inline fn makeRetainedError(code: GetResultCode) std.mem.Allocator.Error!*@This() { const result = try makeError(code); return result.retain(); } pub fn makeCached(image: *Image) std.mem.Allocator.Error!*@This() { const internal = try allocator.create(Internal); errdefer allocator.destroy(internal); internal.* = .{}; const self = try allocator.create(@This()); errdefer allocator.destroy(self); self.* = .{ .internal = internal, .image = image.retain(), }; return self; } pub inline fn makeCachedRetained(image: *Image) std.mem.Allocator.Error!*@This() { const result = try makeCached(image); return result.retain(); } pub fn retain(ptr: ?*@This()) callconv(.C) *@This() { var self = ptr orelse @panic( std.fmt.comptimePrint("Received null pointer on {s}_{s}", .{ cname, @src().fn_name }), ); self.internal.arc.ref(); return self; } pub fn release(ptr: ?*@This()) callconv(.C) void { var self = ptr orelse @panic( std.fmt.comptimePrint("Received null pointer on {s}_{s}", .{ cname, @src().fn_name }), ); if (self.internal.arc.unref()) { freelog(self); if (self.image) |image| { image.release(); } allocator.destroy(self.internal); allocator.destroy(self); } } pub fn export_capi() void { @export(&retain, .{ .name = std.fmt.comptimePrint("{s}_retain", .{cname}) }); @export(&release, .{ .name = std.fmt.comptimePrint("{s}_release", .{cname}) }); } }; pub fn export_capi() void { Image.export_capi(); GetOptions.export_capi(); GetResult.export_capi(); } fn DoubleFifoCache(comptime T: type, short_size: usize, main_size: usize) type { return struct { pub const Data = T; pub const Entry = struct { hash: u64, data: *Data, accessed: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), }; allocator: std.mem.Allocator, short: std.DoublyLinkedList(Entry) = .{}, main: std.DoublyLinkedList(Entry) = .{}, pub fn init(allocator: std.mem.Allocator) @This() { return .{ .allocator = allocator, }; } pub fn deinit(self: *@This()) void { while (self.short.pop()) |popped| { self.release(popped); } while (self.main.pop()) |popped| { self.release(popped); } } pub fn get(self: *@This(), hash: u64) ?*Data { var entry = self.short.first; while (entry) |e| : (entry = e.next) { if (e.data.hash == hash) { e.data.accessed.store(true, .release); return e.data.data; } } entry = self.main.first; while (entry) |e| : (entry = e.next) { if (e.data.hash == hash) { return e.data.data; } } return null; } fn release(self: *const @This(), node: *std.DoublyLinkedList(Entry).Node) void { if (@hasDecl(Data, "release")) { node.data.data.release(); } self.allocator.destroy(node); } fn evictMain(self: *@This()) void { if (self.main.len <= main_size) { return; } const popped = self.main.pop() orelse return; self.release(popped); } fn evictShort(self: *@This()) void { if (self.short.len <= short_size) { return; } const popped = self.short.pop() orelse return; if (!popped.data.accessed.load(.unordered)) { self.release(popped); return; } self.main.prepend(popped); } pub fn put(self: *@This(), hash: u64, data: *Data) std.mem.Allocator.Error!void { if (@hasDecl(Data, "retain")) { _ = data.retain(); } const node = try self.allocator.create(std.DoublyLinkedList(Entry).Node); node.* = .{ .data = .{ .data = data, .hash = hash, }, }; self.short.prepend(node); self.evictShort(); } }; } test DoubleFifoCache { const Cache = DoubleFifoCache(struct { n: u64, rc: usize = 0, pub fn retain(self: *@This()) void { self.rc += 1; } pub fn release(self: *@This()) void { self.rc -= 1; } }, 3, 10); var cache = Cache.init(std.testing.allocator); var n1 = Cache.Data{ .n = 1 }; try cache.put(1, &n1); var n2 = Cache.Data{ .n = 2 }; try cache.put(2, &n2); var n3 = Cache.Data{ .n = 3 }; try cache.put(3, &n3); try std.testing.expectEqual(2, cache.get(2).?.n); try std.testing.expectEqual(3, cache.get(3).?.n); try std.testing.expect(cache.get(4) == null); try std.testing.expectEqual(3, cache.short.len); try std.testing.expectEqual(0, cache.main.len); var n4 = Cache.Data{ .n = 4 }; try cache.put(4, &n4); try std.testing.expectEqual(4, cache.get(4).?.n); try std.testing.expectEqual(3, cache.short.len); try std.testing.expectEqual(0, cache.main.len); var n5 = Cache.Data{ .n = 5 }; try cache.put(5, &n5); try std.testing.expect(cache.get(1) == null); try std.testing.expectEqual(2, cache.get(2).?.n); try std.testing.expectEqual(3, cache.get(3).?.n); try std.testing.expectEqual(4, cache.get(4).?.n); try std.testing.expectEqual(5, cache.get(5).?.n); try std.testing.expectEqual(3, cache.short.len); try std.testing.expectEqual(1, cache.main.len); try std.testing.expectEqual(0, n1.rc); try std.testing.expectEqual(1, n2.rc); try std.testing.expectEqual(1, n3.rc); try std.testing.expectEqual(1, n4.rc); try std.testing.expectEqual(1, n5.rc); cache.deinit(); try std.testing.expectEqual(0, n1.rc); try std.testing.expectEqual(0, n2.rc); try std.testing.expectEqual(0, n3.rc); try std.testing.expectEqual(0, n4.rc); try std.testing.expectEqual(0, n5.rc); } pub const DownloaderOptions = struct { concurrent_download_limit: u5 = 10, short_cache_size: usize = 10, main_cache_size: usize = 50, }; pub fn Downloader(opts: DownloaderOptions) type { return struct { const Job = struct { ready: std.Thread.Condition = .{}, result: *GetResult = undefined, req_hash: u64, arc: Arc = .{}, }; const Cache = DoubleFifoCache(Image, opts.short_cache_size, opts.main_cache_size); // a bit is set (1) = available, otherwise occupied. // ex) truncating size to u8, concurrent download limit = 5 // 0b00011111 = Fully available // 0b00011110 = First item is occupied, second item is available // 0b00000000 = Fully occupied downloads: u32 = ~(@as(u32, std.math.maxInt(u32)) << opts.concurrent_download_limit), mutex: std.Thread.Mutex = .{}, cond: std.Thread.Condition = .{}, downloads_queue: std.DoublyLinkedList(Job) = .{}, queue_mutex: std.Thread.Mutex = .{}, http_client: std.http.Client, is_closed: bool = false, cache: Cache, pub fn init(allocator: std.mem.Allocator) @This() { return .{ .http_client = std.http.Client{ .allocator = allocator, }, .cache = Cache.init(allocator), }; } pub fn deinit(self: *@This()) void { self.is_closed = true; self.mutex.lock(); self.downloads = std.math.maxInt(u32); self.cond.signal(); self.mutex.unlock(); self.http_client.deinit(); self.cache.deinit(); } fn hash(url: []const u8) u64 { var hasher = std.hash.Wyhash.init(0); std.hash.autoHashStrat(&hasher, url, .Deep); return hasher.final(); } pub fn download( self: *@This(), allocator: std.mem.Allocator, req: *const ImageService.Get.Request, server: *Server, ) std.mem.Allocator.Error!*GetResult { _ = server.retain(); defer server.release(); const format = req.format orelse { std.log.err("Downloading image without specifying format is not currently supported", .{}); return try GetResult.makeRetainedError(.failed_to_send); }; const url = req.url([]const u8, allocator, server.internal.address) catch |err| { std.log.err("Unable to construct image download URL: {s}", .{@errorName(err)}); return try GetResult.makeRetainedError(.unknown_error); }; defer allocator.free(url); const req_hash = hash(url); if (self.cache.get(req_hash)) |cached| { std.log.debug("Using cached image for {s}", .{url}); return try GetResult.makeCachedRetained(cached); } { self.queue_mutex.lock(); defer self.queue_mutex.unlock(); var node = self.downloads_queue.first; while (node) |d| : (node = d.next) { if (d.data.req_hash == req_hash) { d.data.arc.ref(); defer if (d.data.arc.unref()) { self.downloads_queue.remove(d); allocator.destroy(d); }; d.data.ready.wait(&self.queue_mutex); return d.data.result.retain(); } } } const node = try allocator.create(std.DoublyLinkedList(Job).Node); node.* = .{ .data = .{ .req_hash = req_hash, }, }; node.data.arc.ref(); { self.queue_mutex.lock(); defer self.queue_mutex.unlock(); self.downloads_queue.prepend(node); } defer if (node.data.arc.unref()) { self.queue_mutex.lock(); defer self.queue_mutex.unlock(); self.downloads_queue.remove(node); allocator.destroy(node); }; const job_index: u5 = job_index: { self.mutex.lock(); defer self.mutex.unlock(); while (self.downloads == 0) { self.cond.wait(&self.mutex); } if (self.is_closed) { return try GetResult.makeRetainedError(.socket_closed); } const i = @min(@ctz(self.downloads), std.math.maxInt(u5)); self.downloads &= ~(@as(u32, 1) << i); break :job_index i; }; std.log.debug("Downloading image({d}) {s}", .{ job_index, url }); defer { self.mutex.lock(); defer self.mutex.unlock(); if (!self.is_closed) { self.downloads |= @as(u32, 1) << job_index; self.cond.signal(); } } var response = std.ArrayList(u8).init(allocator); defer response.deinit(); const result = self.http_client.fetch(.{ .location = .{ .url = url }, .method = .GET, .redirect_behavior = .not_allowed, .headers = .{ .content_type = .{ .override = if (format == .jpeg) "image/jpeg" else "image/png", }, }, .response_storage = .{ .dynamic = &response }, }) catch |err| { if (err == error.ConnectionTimedOut) { std.log.err("Image download timed out", .{}); return try GetResult.makeRetainedError(.timeout); } std.log.err("Failed to download image: {s}", .{@errorName(err)}); return try GetResult.makeRetainedError(.unknown_error); }; if (result.status != .ok) { std.log.err("Unexpected image download response: HTTP({d}) {s}", .{ @intFromEnum(result.status), @tagName(result.status), }); return try GetResult.makeRetainedError(.unexpected_response); } const moo_resp = ImageService.Get.Response{ // TODO: Set from parsed HTTP response header .content_type = format, .data = response.items, }; const get_result = try GetResult.make(&moo_resp); if (get_result.image) |image| { self.cache.put(req_hash, image) catch { std.log.warn("Unable to cache downloaded image due to out of memory error", .{}); }; } node.data.result = get_result; node.data.ready.broadcast(); return get_result.retain(); } }; }
-
-
-
@@ -345,17 +345,6 @@ PLAC_IMAGE_CONTENT_TYPE_JPEG = 0,PLAC_IMAGE_CONTENT_TYPE_PNG = 1, } plac_image_content_type; // image.GetResultCode typedef enum { PLAC_IMAGE_GET_RESULT_OK = 0, PLAC_IMAGE_GET_RESULT_UNKNOWN_ERROR = 1, PLAC_IMAGE_GET_RESULT_OUT_OF_MEMORY = 2, PLAC_IMAGE_GET_RESULT_UNEXPECTED_RESPONSE = 3, PLAC_IMAGE_GET_RESULT_SOCKET_CLOSED = 4, PLAC_IMAGE_GET_RESULT_FAILED_TO_SEND = 5, PLAC_IMAGE_GET_RESULT_TIMEOUT = 6, } plac_image_get_result_code; // image.GetOptions typedef struct { void *__pri; } plac_image_get_options; plac_image_get_options *plac_image_get_options_make();
-
@@ -363,25 +352,6 @@ plac_image_get_options *plac_image_get_options_retain(plac_image_get_options*);void *plac_image_get_options_release(plac_image_get_options*); void *plac_image_get_options_set_size(plac_image_get_options*, plac_image_scaling_method, size_t width, size_t height); void *plac_image_get_options_set_content_type(plac_image_get_options*, plac_image_content_type); // image.Image typedef struct { void *__pri; plac_image_content_type content_type; const uint8_t *data_ptr; size_t data_len; } plac_image_image; plac_image_image *plac_image_image_retain(plac_image_image*); void plac_image_image_release(plac_image_image*); // image.GetResult typedef struct { void *__pri; plac_image_get_result_code code; plac_image_image *image; } plac_image_get_result; plac_image_get_result *plac_image_get_result_retain(plac_image_get_result*); void plac_image_get_result_release(plac_image_get_result*); // connection.ConnectedEvent typedef struct {
-
@@ -441,7 +411,6 @@ plac_transport_volume_control_result_code plac_connection_change_volume(plac_connection*, plac_transport_output*, double abs_value);plac_transport_volume_control_result_code plac_connection_increase_volume(plac_connection*, plac_transport_output*); plac_transport_volume_control_result_code plac_connection_decrease_volume(plac_connection*, plac_transport_output*); plac_browse_result *plac_connection_browse(plac_connection*, plac_browse_hierarchy, plac_transport_zone*, plac_browse_item*, bool pop); plac_image_get_result *plac_connection_get_image(plac_connection*, const char *image_key, plac_image_get_options*); const char *plac_connection_get_image_url(plac_connection*, const char *image_key, plac_image_get_options*); #endif
-
-
-
@@ -496,49 +496,6 @@public void set_size(ScalingMethod scaling, size_t width, size_t height); public void set_content_type(ContentType content_type); } [CCode ( cname = "plac_image_get_result_code", cprefix = "PLAC_IMAGE_GET_RESULT_", has_type_id = false )] public enum GetResultCode { OK = 0, UNKNOWN_ERROR = 1, OUT_OF_MEMORY = 2, UNEXPECTED_RESPONSE = 3, SOCKET_CLOSED = 4, FAILED_TO_SEND = 5, TIMEOUT = 6, } [CCode ( cname = "plac_image_image", ref_function = "plac_image_image_retain", unref_function = "plac_image_image_release" )] [Compact] public class Image { public ContentType content_type; [CCode ( cname = "data_ptr", array_length_cname = "data_len", array_length_type = "size_t" )] public uint8[] data; } [CCode ( cname = "plac_image_get_result", ref_function = "plac_image_get_result_retain", unref_function = "plac_image_get_result_release" )] [Compact] public class GetResult { public GetResultCode code; public Image? image; } } [CCode (
-
@@ -665,9 +622,6 @@ public Transport.SeekResultCode seek(Transport.Zone zone, int64 seconds);[CCode (cname = "plac_connection_browse")] public Browse.Result? browse(Browse.Hierarchy hierarchy, Transport.Zone? zone, Browse.Item? item, bool pop); [CCode (cname = "plac_connection_get_image")] public Image.GetResult? get_image(string image_key, Image.GetOptions options); [CCode (cname = "plac_connection_get_image_url")] public string? get_image_url(string image_key, Image.GetOptions options);
-