diff options
Diffstat (limited to 'src/storage/image-manager')
| -rw-r--r-- | src/storage/image-manager/exif.zig | 54 | ||||
| -rw-r--r-- | src/storage/image-manager/image.zig | 38 | ||||
| -rw-r--r-- | src/storage/image-manager/root.zig | 79 |
3 files changed, 146 insertions, 25 deletions
diff --git a/src/storage/image-manager/exif.zig b/src/storage/image-manager/exif.zig new file mode 100644 index 0000000..02199cf --- /dev/null +++ b/src/storage/image-manager/exif.zig @@ -0,0 +1,54 @@ +const std = @import("std"); +const exif = @cImport(@cInclude("libexif/exif-data.h")); +const cstd = @cImport(@cInclude("time.h")); + +const tags = [_]c_uint { + exif.EXIF_TAG_DATE_TIME, + exif.EXIF_TAG_DATE_TIME_ORIGINAL, + exif.EXIF_TAG_DATE_TIME_DIGITIZED, +}; + +pub fn get_date_time(path: [*:0]const u8) ?i64 { + const exif_data = exif.exif_data_new_from_file(path); + defer exif.exif_data_unref(exif_data); + + if (exif_data == null) { + return null; + } + + var entry: ?[*c]exif.struct__ExifEntry = null; + for (0..exif.EXIF_IFD_COUNT) |index| { + for (tags) |tag| { + entry = exif.exif_content_get_entry((exif_data.*).ifd[index], tag); + + if (entry) |_| { break; } + } + + if (entry) |_| { break; } + } + + if (entry == null) { + return null; + } + + const c_data = (entry.?.*).data; + + return parse_date(c_data); +} + +extern fn strptime( + s: [*c]const u8, + format: [*c]const u8, + tm: *cstd.tm, +) ?*const u8; + +extern fn timegm(tm: *cstd.tm) i64; + +fn parse_date(date: [*:0]const u8) ?i64 { + var tm: cstd.tm = std.mem.zeroes(cstd.tm); + if (strptime(date, "%Y:%m:%d %H:%M:%S", &tm)) |_| { + return timegm(&tm); + } + + return null; +} diff --git a/src/storage/image-manager/image.zig b/src/storage/image-manager/image.zig index 91a094e..b11b7b1 100644 --- a/src/storage/image-manager/image.zig +++ b/src/storage/image-manager/image.zig @@ -1,6 +1,8 @@ const std = @import("std"); const Storage = @import("../root.zig"); +const exif = @import("exif.zig"); + const id_size = 32; const log = std.log.scoped(.image); @@ -19,6 +21,7 @@ fn new_id() [id_size]u8 { const Self = @This(); id: []const u8, +timestamp: ?i64, pub fn new( storage: *Storage, @@ -28,8 +31,11 @@ pub fn new( var dir = try storage.dir.openDir("image", .{}); defer dir.close(); - var file_name: [id_size+4]u8 = undefined; - const self: Self = .{ .id = &new_id() }; + var self: Self = undefined; + + self.id = try storage.allocator.dupe(u8, &new_id()); + + var file_name: [id_size + 4]u8 = undefined; @memcpy(file_name[0..id_size], self.id); @memcpy(file_name[id_size..], ".jpg"); @@ -43,14 +49,17 @@ pub fn new( try file_writer.interface.flush(); - log.info("uploaded {s} [{} bytes]", .{self.id, size}); + log.info("uploaded {s} [{} bytes]", .{ self.id, size }); + + self.timestamp = try load_timestamp(storage, self.id); return self; } -pub fn init(allocator: std.mem.Allocator, id: []const u8) !Self { +pub fn init(storage: *Storage, id: []const u8) !Self { return .{ - .id = try allocator.dupe(u8, id), + .id = try storage.allocator.dupe(u8, id), + .timestamp = try load_timestamp(storage, id), }; } @@ -61,7 +70,7 @@ pub fn file(self: *Self, storage: *Storage) !std.fs.File { }; defer dir.close(); - var file_name: [id_size+4]u8 = undefined; + var file_name: [id_size + 4]u8 = undefined; @memcpy(file_name[0..id_size], self.id); @memcpy(file_name[id_size..], ".jpg"); @@ -71,3 +80,20 @@ pub fn file(self: *Self, storage: *Storage) !std.fs.File { pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { allocator.free(self.id); } + +fn load_timestamp(storage: *Storage, id: []const u8) !?i64 { + var dir = storage.dir.openDir("image", .{}) catch blk: { + try storage.dir.makeDir("image"); + break :blk try storage.dir.openDir("image", .{}); + }; + defer dir.close(); + + var file_name: [id_size + 4]u8 = undefined; + @memcpy(file_name[0..id_size], id); + @memcpy(file_name[id_size..], ".jpg"); + + var path_buffer: [std.fs.max_path_bytes:0]u8 = std.mem.zeroes([std.fs.max_path_bytes:0]u8); + const path = try dir.realpath(&file_name, &path_buffer); + + return exif.get_date_time(@ptrCast(path)); +} diff --git a/src/storage/image-manager/root.zig b/src/storage/image-manager/root.zig index 7742df2..7fa1607 100644 --- a/src/storage/image-manager/root.zig +++ b/src/storage/image-manager/root.zig @@ -1,24 +1,30 @@ const std = @import("std"); -const Storage = @import("../root.zig"); +const memora = @import("memora"); +const Storage = memora.Storage; pub const Image = @import("image.zig"); const Self = @This(); pub const empty: Self = .{}; -items: std.ArrayList(Image) = .empty, -rw_lock: std.Thread.RwLock = .{}, +const Timestamp = struct { + index: usize, + node: std.DoublyLinkedList.Node, -pub const LockedImages = struct { - images: []Image, - rw_lock: *std.Thread.RwLock, + pub fn next(self: *Timestamp) ?*Timestamp { + return @fieldParentPtr("node", self.node.next orelse return null); + } - pub fn deinit(self: *const @This()) void { - self.rw_lock.unlockShared(); + pub fn prev(self: *Timestamp) ?*Timestamp { + return @fieldParentPtr("node", self.node.prev orelse return null); } }; +items: std.ArrayList(Image) = .empty, +rw_lock: std.Thread.RwLock = .{}, +timestamp_order: std.DoublyLinkedList = .{}, + pub fn init( self: *Self, storage: *Storage, @@ -35,32 +41,67 @@ pub fn init( while (try iterator.next()) |entry| { if (entry.kind == .file and std.mem.endsWith(u8, entry.name, ".jpg")) { - try self.items.append( + try self.add( storage.allocator, - try .init(storage.allocator, entry.name[0..entry.name.len - 4]) + try .init(storage, entry.name[0..entry.name.len - 4]) ); } } } -pub fn add( +pub fn add(self: *Self, allocator: std.mem.Allocator, image: Image) !void { + self.rw_lock.lock(); + defer self.rw_lock.unlock(); + + try self.items.append( + allocator, + image, + ); + + var current = self.timestamp_order.first; + + if (current == null) { + const timestamp = try allocator.create(Timestamp); + timestamp.index = self.items.items.len - 1; + self.timestamp_order.append(×tamp.node); + return; + } + + while (current) |c| { + const index = @as(*Timestamp, @fieldParentPtr("node", c)).index; + if (image.timestamp orelse 0 > self.items.items[index].timestamp orelse 0) { + const timestamp = try allocator.create(Timestamp); + timestamp.index = self.items.items.len - 1; + self.timestamp_order.insertBefore(c, ×tamp.node); + break; + } + + current = c.next; + } +} + +pub fn save( self: *Self, storage: *Storage, reader: *std.Io.Reader, size: usize, ) !void { - self.rw_lock.lock(); - defer self.rw_lock.unlock(); + return self.add(storage.allocator, try Image.new(storage, reader, size)); +} - try self.items.append( - storage.allocator, - try Image.new(storage, reader, size), - ); +pub fn list(self: *Self) memora.locked.Shared([]Image) { + self.rw_lock.lockShared(); + return .{ + .value = self.items.items, + .rw_lock = &self.rw_lock, + }; } -pub fn list(self: *Self) LockedImages { +pub fn first_by_timestamp(self: *Self) memora.locked.Shared(?*Timestamp) { + self.rw_lock.lockShared(); return .{ - .images = self.items.items, + .value = if (self.timestamp_order.first) |first| @fieldParentPtr("node", first) + else null, .rw_lock = &self.rw_lock, }; } |