Separate drive and file apis

This commit is contained in:
jaina heartles 2022-12-05 02:12:40 -08:00
parent 208007c0f7
commit af7c77babf

View file

@ -1,203 +1,131 @@
const std = @import("std"); const std = @import("std");
const sql = @import("sql");
const util = @import("util"); const util = @import("util");
const Uuid = util.Uuid; const Uuid = util.Uuid;
const DateTime = util.DateTime; const DateTime = util.DateTime;
pub const FileOwner = union(enum) { pub const FileStatus = enum {
user_id: Uuid, uploading,
community_id: Uuid, uploaded,
external,
deleted,
}; };
pub const DriveFile = struct { pub const FileUpload = struct {
id: Uuid, id: Uuid,
path: []const u8, created_by: Uuid,
filename: []const u8,
owner: FileOwner,
size: usize, size: usize,
description: []const u8, filename: []const u8,
content_type: []const u8, description: ?[]const u8,
content_type: ?[]const u8,
sensitive: bool, sensitive: bool,
status: FileStatus,
created_at: DateTime, created_at: DateTime,
updated_at: DateTime, updated_at: DateTime,
}; };
const EntryType = enum { pub const FileMeta = struct {
dir,
file,
};
pub const CreateFileArgs = struct {
dir: []const u8,
filename: []const u8, filename: []const u8,
owner: FileOwner,
created_by: Uuid,
description: ?[]const u8, description: ?[]const u8,
content_type: ?[]const u8, content_type: ?[]const u8,
sensitive: bool, sensitive: bool,
}; };
fn lookupDirectory(db: anytype, owner: FileOwner, path: []const u8, alloc: std.mem.Allocator) !Uuid { pub fn Partial(comptime T: type) type {
return (try db.queryRow( const t_fields = std.meta.fields(T);
std.meta.Tuple( var fields: [t_fields]std.builtin.Type.StructField = undefined;
&.{util.Uuid}, for (std.meta.fields(T)) |f, i| fields[i] = .{
), .name = f.name,
\\SELECT id .field_type = ?f.field_type,
\\FROM drive_entry_path .default_value = &@as(?f.field_type, null),
\\WHERE .is_comptime = false,
\\ path = (CASE WHEN LENGTH($1) = 0 THEN '/' ELSE '/' || $1 || '/' END) .alignment = @alignOf(?f.field_type),
\\ AND account_owner_id IS NOT DISTINCT FROM $2 };
\\ AND community_owner_id IS NOT DISTINCT FROM $3 return @Type(.{ .Struct = .{
\\ AND kind = 'dir' .layout = .Auto,
\\LIMIT 1 .fields = fields,
, .decls = &.{},
.{ .is_tuple = false,
std.mem.trim(u8, path, "/"), } });
if (owner == .user_id) owner.user_id else null,
if (owner == .community_id) owner.community_id else null,
},
alloc,
))[0];
} }
fn lookup(db: anytype, owner: FileOwner, path: []const u8, alloc: std.mem.Allocator) !Uuid { pub fn update(db: anytype, id: Uuid, meta: Partial(FileMeta), alloc: std.mem.Allocator) !void {
return (try db.queryRow( var builder = sql.QueryBuilder.init(alloc);
std.meta.Tuple( defer builder.deinit();
&.{util.Uuid},
),
\\SELECT id
\\FROM drive_entry_path
\\WHERE
\\ path = (CASE WHEN LENGTH($1) = 0 THEN '/' ELSE '/' || $1 || '/' END)
\\ AND account_owner_id IS NOT DISTINCT FROM $2
\\ AND community_owner_id IS NOT DISTINCT FROM $3
\\LIMIT 1
,
.{
std.mem.trim(u8, path, "/"),
if (owner == .user_id) owner.user_id else null,
if (owner == .community_id) owner.community_id else null,
},
alloc,
))[0];
}
pub fn mkdir(db: anytype, owner: FileOwner, path: []const u8, alloc: std.mem.Allocator) !void { try builder.appendSlice("UPDATE file_upload");
var split = std.mem.splitBackwards(u8, std.mem.trim(u8, path, "/"), "/");
const name = split.first();
const dir = split.rest();
std.log.debug("'{s}' / '{s}'", .{ name, dir });
if (name.len == 0) return error.EmptyName; if (meta.filename) |_| try builder.set("filename", "$2");
if (meta.description) |_| try builder.set("description", "$3");
if (meta.content_type) |_| try builder.set("content_type", "$4");
if (meta.sensitive) |_| try builder.set("sensitive", "$5");
const id = Uuid.randV4(util.getThreadPrng()); if (meta.set_statements_appended == 0) return error.NoChange;
const tx = try db.begin(); try builder.andWhere("id = $1");
errdefer tx.rollback();
const parent = try lookupDirectory(tx, owner, dir, alloc); try builder.appendSlice("\nLIMIT 1");
try tx.insert("drive_entry", .{ try db.exec(try builder.terminate(), .{
.id = id, id,
meta.filename orelse null,
.account_owner_id = if (owner == .user_id) owner.user_id else null, meta.description orelse null,
.community_owner_id = if (owner == .community_id) owner.community_id else null, meta.content_type orelse null,
meta.sensitive orelse null,
.name = name,
.parent_directory_id = parent,
}, alloc);
try tx.commit();
}
pub fn rmdir(db: anytype, owner: FileOwner, path: []const u8, alloc: std.mem.Allocator) !void {
const tx = try db.begin();
errdefer tx.rollback();
const id = try lookupDirectory(tx, owner, path, alloc);
try tx.exec("DELETE FROM drive_directory WHERE id = $1", .{id}, alloc);
try tx.commit();
}
fn insertFileRow(tx: anytype, id: Uuid, filename: []const u8, owner: FileOwner, dir: Uuid, alloc: std.mem.Allocator) !void {
try tx.insert("drive_entry", .{
.id = id,
.account_owner_id = if (owner == .user_id) owner.user_id else null,
.community_owner_id = if (owner == .community_id) owner.community_id else null,
.parent_directory_id = dir,
.name = filename,
.file_id = id,
}, alloc); }, alloc);
} }
pub fn createFile(db: anytype, args: CreateFileArgs, data: []const u8, alloc: std.mem.Allocator) !void { pub fn create(db: anytype, created_by: Uuid, meta: FileMeta, data: []const u8, alloc: std.mem.Allocator) !void {
const id = Uuid.randV4(util.getThreadPrng()); const id = Uuid.randV4(util.getThreadPrng());
const now = DateTime.now(); const now = DateTime.now();
try db.insert("file_upload", .{
{
var tx = try db.begin();
errdefer tx.rollback();
const dir_id = try lookupDirectory(tx, args.owner, args.dir, alloc);
try tx.insert("file_upload", .{
.id = id, .id = id,
.filename = args.filename, .created_by = created_by,
.created_by = args.created_by,
.size = data.len, .size = data.len,
.description = args.description, .filename = meta.filename,
.content_type = args.content_type, .description = meta.description,
.sensitive = args.sensitive, .content_type = meta.content_type,
.sensitive = meta.sensitive,
.is_deleted = false, .status = .uploading,
.created_at = now, .created_at = now,
.updated_at = now, .updated_at = now,
}, alloc); }, alloc);
var sub_tx = try tx.savepoint(); saveFile(id, data) catch |err| {
if (insertFileRow(sub_tx, id, args.filename, args.owner, dir_id, alloc)) |_| { db.exec("DELETE FROM file_upload WHERE ID = $1", .{id}, alloc) catch |e| {
try sub_tx.release(); std.log.err("Unable to remove file record in DB: {}", .{e});
} else |err| {
std.log.debug("{}", .{err});
switch (err) {
error.UniqueViolation => {
try sub_tx.rollbackSavepoint();
// Rename the file before trying again
var split = std.mem.split(u8, args.filename, ".");
const name = split.first();
const ext = split.rest();
var buf: [256]u8 = undefined;
const drive_filename = try std.fmt.bufPrint(&buf, "{s}.{}.{s}", .{ name, id, ext });
try insertFileRow(tx, id, drive_filename, args.owner, dir_id, alloc);
},
else => return error.DatabaseFailure,
}
}
try tx.commit();
}
errdefer {
db.exec("DELETE FROM file_upload WHERE ID = $1", .{id}, alloc) catch |err| {
std.log.err("Unable to remove file record in DB: {}", .{err});
}; };
db.exec("DELETE FROM drive_entry WHERE ID = $1", .{id}, alloc) catch |err| { return err;
std.log.err("Unable to remove file record in DB: {}", .{err});
}; };
}
try saveFile(id, data); try db.exec(
\\UPDATE file_upload
\\SET status = 'uploaded'
\\WHERE id = $1
\\LIMIT 1
, .{id}, alloc);
}
pub fn delete(db: anytype, id: Uuid, alloc: std.mem.Allocator) !void {
var dir = try std.fs.cwd().openDir(data_root, .{});
defer dir.close();
try dir.deleteFile(id.toCharArray());
try db.exec(
\\DELETE FROM file_upload
\\WHERE id = $1
\\LIMIT 1
, .{id}, alloc);
} }
const data_root = "./files"; const data_root = "./files";
@ -218,17 +146,3 @@ pub fn deref(alloc: std.mem.Allocator, id: Uuid) ![]const u8 {
return dir.readFileAlloc(alloc, &id.toCharArray(), 1 << 32); return dir.readFileAlloc(alloc, &id.toCharArray(), 1 << 32);
} }
pub fn deleteFile(db: anytype, alloc: std.mem.Allocator, id: Uuid) !void {
var dir = try std.fs.cwd().openDir(data_root, .{});
defer dir.close();
try dir.deleteFile(id.toCharArray());
const tx = try db.beginOrSavepoint();
errdefer tx.rollback();
tx.exec("DELETE FROM drive_entry WHERE ID = $1", .{id}, alloc) catch return error.DatabaseFailure;
tx.exec("DELETE FROM file_upload WHERE ID = $1", .{id}, alloc) catch return error.DatabaseFailure;
try tx.commitOrRelease();
}