AmendHub

Download:

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