aboutsummaryrefslogtreecommitdiff
path: root/src/storage
diff options
context:
space:
mode:
Diffstat (limited to 'src/storage')
-rw-r--r--src/storage/image-manager/exif.zig54
-rw-r--r--src/storage/image-manager/image.zig38
-rw-r--r--src/storage/image-manager/root.zig79
-rw-r--r--src/storage/user.zig40
4 files changed, 184 insertions, 27 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(&timestamp.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, &timestamp.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,
};
}
diff --git a/src/storage/user.zig b/src/storage/user.zig
index d5260fe..09f7716 100644
--- a/src/storage/user.zig
+++ b/src/storage/user.zig
@@ -1,6 +1,8 @@
const std = @import("std");
const Storage = @import("root.zig");
+const log = std.log.scoped(.user);
+
const Self = @This();
pub const Info = struct {
@@ -157,8 +159,42 @@ pub fn deinit(self: *Self) void {
pub fn exists(storage: *Storage, name: []const u8) bool {
var user = storage.dir.openDir("user", .{}) catch return false;
- defer user.deinit();
+ defer user.close();
- user.access(name) catch return false;
+ user.access(name, .{}) catch return false;
return true;
}
+
+pub fn image(storage: *Storage, name: []const u8) ?std.fs.File {
+ var user = storage.dir.openDir("user", .{}) catch return null;
+ defer user.close();
+
+ var profile = user.openDir(name, .{}) catch return null;
+ defer profile.close();
+ return profile.openFile("image.jpg", .{ .lock = .shared }) catch null;
+}
+
+pub fn set_image(
+ storage: *Storage,
+ name: []const u8,
+ reader: *std.Io.Reader,
+ size: usize,
+) !void {
+ var user = try storage.dir.openDir("user", .{});
+ defer user.close();
+
+ var profile = try user.openDir(name, .{});
+ defer profile.close();
+
+ var image_file = try profile.createFile("image.jpg", .{ .lock = .exclusive });
+ defer image_file.close();
+
+ var buffer: [1024]u8 = undefined;
+ var file_writer = image_file.writer(&buffer);
+
+ log.info("uploaded to profile {s} [{} bytes]", .{ name, size });
+
+ try reader.streamExact(&file_writer.interface, size);
+
+ try file_writer.interface.flush();
+}