jcs
/subtext
/amendments
/178
folder: Start on file storage
Has no actual transmission mechanism (XMODEM/ZMODEM) but it's a start.
jcs made amendment 178 over 2 years ago
--- folder.c Thu Jun 30 15:22:59 2022
+++ folder.c Thu Jun 30 15:38:24 2022
@@ -0,0 +1,820 @@
+/*
+ * Copyright (c) 2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "subtext.h"
+#include "ansi.h"
+#include "folder.h"
+#include "user.h"
+
+#define FILES_PER_PAGE 10
+
+#define FILE_VIEW_RETURN_DONE -1
+#define FILE_VIEW_RETURN_LIST -2
+#define FILE_VIEW_RETURN_FIND -3
+
+struct struct_field folder_fields[] = {
+ { "Folder ID", CONFIG_TYPE_LONG,
+ offsetof(struct folder, id),
+ 1, ULONG_MAX },
+ { "Name", CONFIG_TYPE_STRING,
+ offsetof(struct folder, name),
+ 1, member_size(struct board, name) },
+ { "Description", CONFIG_TYPE_STRING,
+ offsetof(struct folder, description),
+ 0, member_size(struct board, description) },
+ { "Restricted Posting", CONFIG_TYPE_BOOLEAN,
+ offsetof(struct folder, restricted_posting),
+ 0, 0 },
+ { "Restricted Viewing", CONFIG_TYPE_BOOLEAN,
+ offsetof(struct folder, restricted_viewing),
+ 0, 0 },
+};
+size_t nfolder_fields = nitems(folder_fields);
+
+struct bile_object_field folder_object_fields[] = {
+ { offsetof(struct folder, id),
+ member_size(struct folder, id), -1 },
+ { offsetof(struct folder, name),
+ member_size(struct folder, name), -1 },
+ { offsetof(struct folder, description),
+ member_size(struct folder, description), -1 },
+ { offsetof(struct folder, restricted_posting),
+ member_size(struct folder, restricted_posting), -1 },
+ { offsetof(struct folder, restricted_viewing),
+ member_size(struct folder, restricted_viewing), -1 },
+ { offsetof(struct folder, last_upload_at),
+ member_size(struct folder, last_upload_at), -1 },
+ { offsetof(struct folder, file_count),
+ member_size(struct folder, file_count), -1 },
+};
+size_t nfolder_object_fields = nitems(folder_object_fields);
+
+struct bile_object_field folder_file_object_fields[] = {
+ { offsetof(struct folder_file, id),
+ member_size(struct folder_file, id), -1 },
+ { offsetof(struct folder_file, time),
+ member_size(struct folder_file, time), -1 },
+ { offsetof(struct folder_file, uploader_user_id),
+ member_size(struct folder_file, uploader_user_id), -1 },
+ { offsetof(struct folder_file, filename),
+ member_size(struct folder_file, filename), -1 },
+ { offsetof(struct folder_file, description),
+ member_size(struct folder_file, description), -1 },
+ { offsetof(struct folder_file, size),
+ member_size(struct folder_file, size), -1 },
+ { offsetof(struct folder_file, sha1_checksum),
+ member_size(struct folder_file, sha1_checksum), -1 },
+ { offsetof(struct folder_file, notes),
+ -1, offsetof(struct folder_file, notes_size) },
+};
+size_t nfolder_file_object_fields = nitems(folder_file_object_fields);
+
+enum {
+ PROTO_XMODEM = 1,
+ PROTO_ZMODEM
+};
+
+unsigned long folder_upload(struct session *s, struct folder *folder,
+ char *initial_filename, char *initial_description);
+void folder_list_files(struct session *s, struct folder *folder,
+ size_t nfile_Ids, unsigned long *file_ids, size_t page, size_t pages);
+void folder_show(struct session *s, struct folder *folder);
+short folder_file_view(struct session *s, struct folder *folder,
+ unsigned long id, short idx);
+void folder_delete_file(struct session *s, struct folder *folder,
+ struct folder_file *file);
+size_t folder_find_file_ids(struct folder *folder, size_t *nfile_ids,
+ unsigned long **file_ids);
+short folder_file_save(struct folder *folder, struct folder_file *file);
+
+void
+folder_list(struct session *s)
+{
+ static struct session_menu_option opts[] = {
+ { '#', "#0123456789", "View file folder [#]" },
+ { 'l', "Ll", "List folders" },
+ { 'q', "QqXx", "Return to main menu" },
+ { '?', "?", "List menu options" },
+ };
+ short n;
+ char c;
+ bool done, show_list, show_help;
+
+ show_list = true;
+ show_help = false;
+ done = false;
+
+ while (!done && !s->ending) {
+ if (show_list) {
+ session_printf(s, "{{B}}File Folders{{/B}}\r\n");
+ session_printf(s, "%s# Name Description%s\r\n",
+ ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END));
+ session_flush(s);
+
+ for (n = 0; n < db->nfolders; n++) {
+ session_printf(s, "%d %- 13s %s\r\n",
+ n,
+ db->folders[n].name,
+ db->folders[n].description);
+ }
+ session_flush(s);
+
+ show_list = false;
+ }
+
+ c = session_menu(s, "File Folders", "Files", opts, nitems(opts),
+ show_help);
+ show_help = false;
+
+handle_opt:
+ switch (c) {
+ case 'l':
+ show_list = true;
+ break;
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ if (c >= db->nfolders) {
+ session_printf(s, "Invalid folder\r\n");
+ session_flush(s);
+ break;
+ }
+ folder_show(s, &db->folders[c]);
+ break;
+ case '?':
+ show_help = true;
+ break;
+ default:
+ done = true;
+ break;
+ }
+ }
+}
+
+void
+folder_show(struct session *s, struct folder *folder)
+{
+ static struct session_menu_option opts[] = {
+ { '#', "#0123456789", "View file [#]" },
+ { '<', "<", "Previous page of files" },
+ { 'l', "Ll", "List files" },
+ { '>', ">", "Next page of files" },
+ { 'u', "Uu", "Upload new file" },
+ { 'q', "QqXx", "Return to main menu" },
+ { '?', "?", "List menu options" },
+ };
+ size_t n, page, pages, nfile_ids;
+ unsigned long *file_ids = NULL;
+ short ret;
+ char c;
+ bool done, show_list, show_help;
+
+ page = 0;
+ show_list = true;
+ show_help = false;
+ done = false;
+
+ folder_find_file_ids(folder, &nfile_ids, &file_ids);
+ /* ceil(nfile_ids / FILES_PER_PAGE) */
+ pages = (nfile_ids + FILES_PER_PAGE - 1) / FILES_PER_PAGE;
+
+ if (page >= pages)
+ page = pages - 1;
+
+ while (!done && !s->ending) {
+ if (show_list) {
+ folder_list_files(s, folder, nfile_ids, file_ids, page + 1,
+ pages);
+ show_list = false;
+ }
+
+ c = session_menu(s, folder->description, folder->name, opts,
+ nitems(opts), show_help);
+ show_help = false;
+
+handle_opt:
+ switch (c) {
+ case 'l':
+ show_list = true;
+ break;
+ case 'u':
+ folder_upload(s, folder, NULL, NULL);
+ break;
+ case '>':
+ case '<':
+ if (c == '>' && page == pages - 1) {
+ session_printf(s, "You are at the last page of files\r\n");
+ session_flush(s);
+ break;
+ }
+ if (c == '<' && page == 0) {
+ session_printf(s, "You are already at the first page\r\n");
+ session_flush(s);
+ break;
+ }
+ if (c == '>')
+ page++;
+ else
+ page--;
+ show_list = true;
+ break;
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ if (c >= nfile_ids) {
+ session_printf(s, "Invalid file\r\n");
+ session_flush(s);
+ break;
+ }
+ c = folder_file_view(s, folder, file_ids[c], c);
+ goto handle_opt;
+ case '?':
+ show_help = true;
+ break;
+ default:
+ done = true;
+ break;
+ }
+ }
+
+ free(file_ids);
+}
+
+void
+folder_list_files(struct session *s, struct folder *folder,
+ size_t nfile_ids, unsigned long *file_ids, unsigned long page,
+ unsigned long pages)
+{
+ char time[24];
+ size_t n, off, size, idx;
+ struct username_cache *user;
+ struct bile_object *obj;
+ struct folder_file file;
+ short j, k;
+ char *data;
+
+ session_printf(s, "{{B}}%s: %s (Page %ld of %ld){{/B}}\r\n",
+ folder->name, folder->description, page, pages);
+ session_printf(s, "%s# Date From File Description%s\r\n",
+ ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END));
+ session_flush(s);
+
+ if (nfile_ids == 0) {
+ session_printf(s, "No files here yet.\r\n");
+ session_flush(s);
+ return;
+ }
+
+ off = FILES_PER_PAGE * (page - 1);
+
+ for (n = 0; n < FILES_PER_PAGE; n++) {
+ if (off + n >= nfile_ids)
+ break;
+
+ size = bile_read_alloc(folder->bile, FOLDER_FILE_RTYPE,
+ file_ids[off + n], &data);
+ bile_unmarshall_object(folder->bile, folder_file_object_fields,
+ nfolder_file_object_fields, data, size, &file, false);
+ free(data);
+
+ user = user_find_username(file.uploader_user_id);
+ strftime(time, sizeof(time), "%b %d", localtime(&file.time));
+
+ session_printf(s, "%ld %s {{#}}%- 10s %- 40s\r\n",
+ n,
+ user ? user->username : "(unknown)",
+ file.filename,
+ "");
+ }
+ session_flush(s);
+}
+
+unsigned long
+folder_upload(struct session *s, struct folder *folder,
+ char *initial_filename, char *initial_description)
+{
+ struct folder_file file = { 0 };
+ char *data = NULL, *tmp = NULL;
+ size_t size;
+ short c, ret, proto = -1, error;
+
+ if (!s->user) {
+ session_printf(s, "Uploading is not available to guests.\r\n"
+ "Please create an account first.\r\n");
+ session_flush(s);
+ return 0;
+ }
+
+ if (initial_filename)
+ strlcpy(file.filename, initial_filename, sizeof(file.filename));
+
+ file.uploader_user_id = s->user->id;
+
+ session_printf(s, "{{B}}Upload New File{{/B}}\r\n");
+ session_printf(s,
+ "Your file will be received and temporarily stored. You'll\r\n");
+ session_printf(s,
+ "then be given the option to change the filename and enter a\r\n");
+ session_printf(s,
+ "description for the file.\r\n\r\n");
+ session_printf(s,
+ "Upload file via (X)MODEM or (Z)MODEM? [x/Z] ");
+ session_flush(s);
+
+ while (proto < 0) {
+ c = session_input_char(s);
+ if (c == 0 || s->ending)
+ return 0;
+
+ switch (c) {
+ case 'x':
+ case 'X':
+ session_printf(s, "%c\r\n", c);
+ session_flush(s);
+ proto = PROTO_XMODEM;
+ break;
+ case '\r':
+ case '\n':
+ c = 'z';
+ case 'z':
+ case 'Z':
+ session_printf(s, "%c\r\n", c);
+ session_flush(s);
+ proto = PROTO_ZMODEM;
+ break;
+ case CONTROL_C:
+ return 0;
+ }
+ }
+
+ session_printf(s,
+ "\r\n"
+ "To begin your %s upload, press Enter and choose a file through\r\n",
+ (proto == PROTO_XMODEM ? "XMODEM" : "ZMODEM"));
+ session_printf(s,
+ "your terminal program (only one file can be uploaded at a time).\r\n");
+ session_printf(s,
+ "\r\n"
+ "To cancel your upload, press ^C a few times.\r\n\r\n");
+ session_flush(s);
+ session_pause_return(s, false, "when you are ready...");
+
+ /* TODO: do xmodem or zmodem */
+
+ if (file.tmp_filename[0] == '\0') {
+ session_printf(s, "No file received, canceling.\r\n");
+ session_flush(s);
+ return 0;
+ }
+
+ session_printf(s, "{{B}}From:{{/B}} %s\r\n", s->user->username);
+ session_printf(s, "{{B}}To:{{/B}} %s\r\n", folder->name);
+
+file_upload_annotate:
+ for (;;) {
+ session_printf(s, "{{B}}Name:{{/B}} ");
+ session_flush(s);
+
+ tmp = session_field_input(s, FOLDER_FILE_FILENAME_LENGTH,
+ FOLDER_FILE_FILENAME_LENGTH - 1, file.filename, true, 0);
+ if (tmp == NULL)
+ goto file_upload_cancel;
+ strlcpy(file.filename, tmp, sizeof(file.filename));
+ free(tmp);
+ session_output(s, "\r\n", 2);
+ session_flush(s);
+ if (file.filename[0] == '\0') {
+ session_output_template(s, "{{B}}Error:{{/B}} Filename "
+ "cannot be blank (^C to cancel)\r\n");
+ session_flush(s);
+ continue;
+ }
+ /* TODO: verify filename */
+ break;
+ }
+
+ for (;;) {
+ session_printf(s, "{{B}}Notes:{{/B}} (Optional)\r\n");
+ session_flush(s);
+
+ tmp = session_field_input(s, FOLDER_FILE_DESCR_LENGTH,
+ FOLDER_FILE_DESCR_LENGTH - 1, file.notes, true, 0);
+ if (file.notes != NULL)
+ free(file.notes);
+ file.notes = tmp;
+ session_output(s, "\r\n", 2);
+ session_flush(s);
+ if (file.notes == NULL) {
+ file.notes_size = 0;
+ break;
+ }
+ file.notes_size = strlen(file.notes) + 1;
+ break;
+ }
+
+ for (;;) {
+ session_output_template(s, "\r\n{{B}}(S){{/B}}ave file, "
+ "{{B}}(E){{/B}}dit again, or {{B}}(C){{/B}}ancel? ");
+ session_flush(s);
+
+ c = session_input_char(s);
+ if (c == 0 || s->ending)
+ goto file_upload_done;
+
+ switch (c) {
+ case 's':
+ case 'S':
+ case 'y':
+ session_printf(s, "%c\r\n", c);
+ session_flush(s);
+ /* FALLTHROUGH */
+ case '\n':
+ case '\r':
+ /* send */
+ session_printf(s, "Saving file... ");
+ session_flush(s);
+
+ if (folder_file_save(folder, &file) == 0) {
+ session_log(s, "Saved file %s to %s", file.filename,
+ folder->name);
+ session_printf(s, "done\r\n");
+ } else
+ session_printf(s, "failed!\r\n");
+
+ session_flush(s);
+
+ goto file_upload_done;
+ case 'e':
+ case 'E':
+ session_printf(s, "%c\r\n", c);
+ session_flush(s);
+ goto file_upload_annotate;
+ case 'c':
+ case 'C':
+ session_printf(s, "%c\r\n", c);
+ session_flush(s);
+ /* FALLTHROUGH */
+ case CONTROL_C:
+ goto file_upload_done;
+ }
+ }
+
+file_upload_error:
+ session_printf(s, "Failed saving file!\r\n");
+ session_flush(s);
+
+file_upload_cancel:
+ if (file.tmp_filename[0] != '\0') {
+ CtoPstr(&file.tmp_filename);
+ error = FSDelete(&file.tmp_filename, folder->folder_path_vrefnum);
+ if (error) {
+ PtoCstr(&file.tmp_filename);
+ session_log(s, "Failed deleting temporary uploaded file %s: %d",
+ file.tmp_filename);
+ }
+ }
+
+file_upload_done:
+ if (file.notes)
+ free(file.notes);
+
+ return file.id;
+}
+
+short
+folder_file_view(struct session *s, struct folder *folder,
+ unsigned long id, short idx)
+{
+ static struct session_menu_option opts[] = {
+ { '#', "#0123456789", "View file [#]" },
+ { 'd', "Dd", "Download this file" },
+ { 'r', "Rr", "Remove this file (if permitted)" },
+ { 'q', "QqXx", "Return to threads" },
+ { '?', "?", "List these options" },
+ };
+ char time[32];
+ struct folder_file file;
+ struct username_cache *uploader;
+ unsigned long new_id;
+ char prompt[BOARD_NAME_LENGTH + 8];
+ size_t n, size;
+ char c;
+ char *data;
+ short cc, ret;
+ bool done = false, show_help = false;
+
+ size = bile_read_alloc(folder->bile, FOLDER_FILE_RTYPE, id, &data);
+ if (size == 0)
+ panic("failed fetching message %ld: %d", id,
+ bile_error(folder->bile));
+ bile_unmarshall_object(folder->bile, folder_file_object_fields,
+ nfolder_file_object_fields, data, size, &file, true);
+ free(data);
+
+ uploader = user_find_username(file.uploader_user_id);
+
+ strftime(time, sizeof(time), "%Y-%m-%d %H:%M:%S",
+ localtime(&file.time));
+
+ session_printf(s, "{{B}}File Name:{{/B}} %s\r\n", file.filename);
+ session_printf(s, "{{B}}Description:{{/B}} %s\r\n", file.description);
+ session_printf(s, "{{B}}Folder:{{/B}} %s\r\n", folder->name);
+ session_printf(s, "{{B}}Date:{{/B}} %s %s\r\n", time,
+ db->config.timezone);
+ session_printf(s, "{{B}}File Size:{{/B}} %lu\r\n", file.size);
+ session_printf(s, "{{B}}File SHA1:{{/B}} %s\r\n", file.sha1_checksum);
+ session_printf(s, "{{B}}Uploaded By:{{/B}} %s\r\n",
+ uploader ? uploader->username : "(unknown)");
+ session_flush(s);
+
+ if (file.notes_size > 0) {
+ session_printf(s, "\r\n");
+ session_output(s, file.notes, file.notes_size);
+ session_printf(s, "\r\n");
+ }
+
+ snprintf(prompt, sizeof(prompt), "%s:%ld", folder->name, file.id);
+
+ while (!done && !s->ending) {
+ c = session_menu(s, file.filename, prompt, opts, nitems(opts),
+ show_help);
+ show_help = false;
+
+ switch (c) {
+ case 'r':
+ if (!(s->user && (s->user->is_sysop ||
+ s->user->id == file.uploader_user_id))) {
+ session_printf(s, "Invalid option\r\n");
+ session_flush(s);
+ break;
+ }
+
+ session_printf(s, "Are you sure you want to permanently "
+ "delete this file? [y/N] ");
+ session_flush(s);
+
+ cc = session_input_char(s);
+ if (cc == 'y' || c == 'Y') {
+ session_printf(s, "%c\r\n", cc);
+ session_flush(s);
+
+ folder_delete_file(s, folder, &file);
+
+ session_log(s, "Deleted file %ld (%s)", file.id,
+ file.filename);
+
+ session_printf(s, "\r\n{{B}}File deleted!{{/B}}\r\n");
+ session_flush(s);
+ ret = FILE_VIEW_RETURN_FIND;
+ done = true;
+ } else {
+ session_printf(s, "\r\Post not deleted.\r\n");
+ session_flush(s);
+ }
+ break;
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ ret = c;
+ done = true;
+ break;
+ case 'q':
+ done = true;
+ break;
+ case '?':
+ show_help = true;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+size_t
+folder_find_file_ids(struct folder *folder, size_t *nfile_ids,
+ unsigned long **file_ids)
+{
+ struct bile_object *o;
+ struct folder_file file;
+ struct file_name_map {
+ unsigned long id;
+ char filename[10]; /* only sorted to 10 character places */
+ };
+ struct file_name_map *name_map = NULL, tmp_map;
+ size_t n, size;
+ short i, j;
+ char *data;
+
+ *nfile_ids = bile_count_by_type(folder->bile, FOLDER_FILE_RTYPE);
+ if (*nfile_ids == 0)
+ return 0;
+
+ name_map = xcalloc(*nfile_ids, sizeof(struct file_name_map));
+
+ for (n = 0; o = bile_get_nth_of_type(folder->bile, n,
+ FOLDER_FILE_RTYPE); n++) {
+ if (n >= *nfile_ids)
+ break;
+ bile_read_alloc(folder->bile, FOLDER_FILE_RTYPE, o->id, &data);
+ bile_unmarshall_object(folder->bile, folder_file_object_fields,
+ nfolder_file_object_fields, data, o->size, &file, false);
+ free(data);
+ free(o);
+
+ name_map[n].id = file.id;
+ strlcpy(name_map[n].filename, file.filename,
+ sizeof(name_map[n].filename));
+ }
+
+ /* sort by filename */
+ for (i = 0; i < *nfile_ids; i++) {
+ for (j = 0; j < *nfile_ids - i - 1; j++) {
+ if (strcasecmp(name_map[j].filename,
+ name_map[j + 1].filename) > 0) {
+ tmp_map = name_map[j];
+ name_map[j] = name_map[j + 1];
+ name_map[j + 1] = tmp_map;
+ }
+ }
+ }
+
+ *file_ids = xcalloc(sizeof(long), *nfile_ids);
+ for (i = 0; i < *nfile_ids; i++)
+ (*file_ids)[i] = name_map[i].id;
+
+done:
+ return *nfile_ids;
+}
+
+short
+folder_file_save(struct folder *folder, struct folder_file *file)
+{
+ short ret;
+ char *data;
+ size_t size;
+ ssize_t n, j;
+ size_t insert;
+
+ file->id = bile_next_id(folder->bile, FOLDER_FILE_RTYPE);
+ file->time = Time;
+
+ ret = bile_marshall_object(folder->bile, folder_file_object_fields,
+ nfolder_file_object_fields, file, &data, &size);
+ if (ret != 0 || size == 0) {
+ warn("failed to marshall new file object");
+ file->id = 0;
+ goto done;
+ }
+ if (bile_write(folder->bile, FOLDER_FILE_RTYPE, file->id, data,
+ size) != size) {
+ warn("bile_write of new file failed! %d", bile_error(folder->bile));
+ file->id = 0;
+ free(data);
+ goto done;
+ }
+ free(data);
+
+ bile_flush(folder->bile, true);
+
+done:
+ return file->id;
+}
+
+void
+folder_delete_file(struct session *s, struct folder *folder,
+ struct folder_file *file)
+{
+#if 0
+ size_t size, n, nn;
+ char *data;
+ char del[50];
+ short ret;
+ bool childs = false;
+ unsigned long *new_post_ids;
+ unsigned long *new_parent_post_ids;
+
+ if (thread->nposts == 1) {
+ bile_delete(board->bile, BOARD_THREAD_RTYPE, thread->thread_id);
+ bile_delete(board->bile, BOARD_POST_RTYPE, post->id);
+ return;
+ }
+
+ for (n = 0; n < thread->nposts; n++) {
+ if (thread->parent_post_ids[n] == post->id) {
+ childs = true;
+ break;
+ }
+ }
+
+ if (!childs) {
+ /* just zap this off the end of the tree */
+ new_post_ids = xcalloc(thread->nposts - 1, sizeof(unsigned long));
+ new_parent_post_ids = xcalloc(thread->nposts - 1,
+ sizeof(unsigned long));
+
+ for (n = 0, nn = 0; n < thread->nposts; n++) {
+ if (thread->post_ids[n] == post->id)
+ continue;
+
+ new_post_ids[nn] = thread->post_ids[n];
+ nn++;
+ }
+
+ for (n = 0, nn = 0; n < thread->nposts; n++) {
+ if (thread->post_ids[n] == post->id)
+ continue;
+
+ new_parent_post_ids[nn] = thread->parent_post_ids[n];
+ nn++;
+ }
+
+ thread->nposts--;
+
+ free(thread->post_ids);
+ thread->post_ids = new_post_ids;
+ free(thread->parent_post_ids);
+ thread->parent_post_ids = new_parent_post_ids;
+
+ ret = bile_marshall_object(board->bile, board_thread_object_fields,
+ nboard_thread_object_fields, thread, &data, &size);
+ if (ret != 0 || size == 0) {
+ warn("failed to marshall thread object during post delete");
+ return;
+ }
+ if (bile_write(board->bile, BOARD_THREAD_RTYPE, thread->thread_id,
+ data, size) != size) {
+ warn("bile_write of updated thread after post delete failed! "
+ "%d", bile_error(board->bile));
+ free(data);
+ return;
+ }
+ free(data);
+
+ bile_delete(board->bile, BOARD_POST_RTYPE, post->id);
+
+ bile_flush(board->bile, true);
+ return;
+ }
+
+ /* all we can do is change the post to say deleted */
+ if (post->body != NULL)
+ free(post->body);
+ snprintf(del, sizeof(del), "[ Post deleted by %s ]",
+ s->user ? s->user->username : "unknown");
+ post->body = xstrdup(del);
+ post->body_size = strlen(post->body) + 1;
+
+ ret = bile_marshall_object(board->bile, board_post_object_fields,
+ nboard_post_object_fields, post, &data, &size);
+ if (ret != 0 || size == 0) {
+ warn("failed to marshall updated post object");
+ return;
+ }
+ if (bile_write(board->bile, BOARD_POST_RTYPE, post->id, data,
+ size) != size) {
+ warn("bile_write of updated post failed! %d",
+ bile_error(board->bile));
+ free(data);
+ return;
+ }
+#endif
+}
--- folder.h Thu Jun 30 13:38:47 2022
+++ folder.h Thu Jun 30 13:38:47 2022
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __FOLDER_H__
+#define __FOLDER_H__
+
+#include <stddef.h>
+#include "db.h"
+#include "sha1.h"
+
+struct folder {
+ unsigned long id;
+#define FOLDER_NAME_LENGTH 32
+ char name[FOLDER_NAME_LENGTH];
+#define FOLDER_DESCR_LENGTH 100
+ char description[FOLDER_DESCR_LENGTH];
+ bool restricted_posting;
+ bool restricted_viewing;
+ unsigned long last_upload_at;
+ unsigned long file_count;
+
+ short folder_path_vrefnum;
+ struct bile *bile;
+};
+
+extern struct struct_field folder_fields[];
+extern size_t nfolder_fields;
+extern struct bile_object_field folder_object_fields[];
+extern size_t nfolder_object_fields;
+
+struct folder_file {
+ unsigned long id;
+ time_t time;
+ unsigned long uploader_user_id;
+#define FOLDER_FILE_FILENAME_LENGTH 31
+ char filename[FOLDER_FILE_FILENAME_LENGTH];
+#define FOLDER_FILE_DESCR_LENGTH 50
+ char description[FOLDER_FILE_DESCR_LENGTH];
+ size_t size;
+ char sha1_checksum[SHA1_DIGEST_STRING_LENGTH + 1]; /* 42 */
+ size_t notes_size;
+ char *notes;
+
+ char tmp_filename[FOLDER_FILE_FILENAME_LENGTH];
+};
+extern struct bile_object_field folder_file_object_fields[];
+extern size_t nfolder_file_object_fields;
+
+void folder_list(struct session *s);
+
+#endif