const std = @import("std"); const util = @import("util"); const sql = @import("sql"); const types = @import("./types.zig"); const Uuid = util.Uuid; const DateTime = util.DateTime; const Entry = types.drive.DriveEntry; fn doGetQuery(db: anytype, comptime clause: []const u8, args: anytype, alloc: std.mem.Allocator) !Entry { const q = std.fmt.comptimePrint( \\SELECT id, path, owner_id, name, file_id, kind, parent_directory_id \\FROM drive_entry_path \\WHERE {s} \\LIMIT 1 , .{clause}, ); return db.queryRow(Entry, q, args, alloc) catch |err| switch (err) { error.NoRows => return error.NotFound, else => |e| return e, }; } pub fn stat(db: anytype, owner: Uuid, path: []const u8, alloc: std.mem.Allocator) !Entry { return try doGetQuery( db, "owner_id = $1 AND path = ('/' || $2)", .{ owner, std.mem.trim(u8, path, "/"), }, alloc, ); } pub fn get(db: anytype, id: Uuid, alloc: std.mem.Allocator) !Entry { return try doGetQuery(db, "id = $1", .{id}, alloc); } /// Creates a file or directory pub fn create(db: anytype, owner: Uuid, dir: []const u8, name: []const u8, file_id: ?Uuid, alloc: std.mem.Allocator) !Uuid { if (name.len == 0) return error.EmptyName; const id = Uuid.randV4(util.getThreadPrng()); const tx = try db.begin(); errdefer tx.rollback(); const parent = try stat(tx, owner, dir, alloc); defer util.deepFree(alloc, parent); tx.insert("drive_entry", .{ .id = id, .owner_id = owner, .name = name, .parent_directory_id = parent.id, .file_id = file_id, }, alloc) catch |err| switch (err) { error.UniqueViolation => return error.PathAlreadyExists, else => |e| return e, }; try tx.commit(); return id; } pub fn delete(db: anytype, id: Uuid, alloc: std.mem.Allocator) !void { const tx = try db.beginOrSavepoint(); errdefer tx.rollback(); if ((try tx.queryRow( std.meta.Tuple(&.{usize}), \\SELECT COUNT(1) \\FROM drive_entry \\WHERE parent_directory_id = $1 , .{id}, alloc, ))[0] != 0) { return error.DirectoryNotEmpty; } try tx.exec("DELETE FROM drive_entry WHERE id = $1", .{id}, alloc); try tx.commitOrRelease(); } pub fn move(db: anytype, owner: Uuid, src: []const u8, dest: []const u8, alloc: std.mem.Allocator) !void { const tx = try db.beginOrSavepoint(); errdefer tx.rollback(); const val = try stat(tx, owner, src, alloc); defer util.deepFree(alloc, val); if (val.parent_directory_id == null) return error.RootDirectory; var split = std.mem.splitBackwards(u8, std.mem.trim(u8, dest, "/"), "/"); const name = split.first(); const dir = split.rest(); const parent = try stat(tx, owner, dir, alloc); defer util.deepFree(alloc, parent); try tx.exec( \\UPDATE drive_entry \\SET name = $1, parent_directory_id = $2 \\WHERE id = $3 , .{ name, parent.id, val.id }, alloc, ); try tx.commitOrRelease(); } // TODO: paginate this pub fn list(db: anytype, id: Uuid, alloc: std.mem.Allocator) ![]Entry { return (db.queryRows(Entry, \\SELECT id, path, owner_id, name, file_id, kind, parent_directory_id \\FROM drive_entry_path \\WHERE parent_directory_id = $1 , .{id}, null, alloc) catch |err| switch (err) { error.NoRows => return error.NotFound, else => |e| return e, }); }