jcs
/subtext
/amendments
/194
folder: Finally integrate with ZMODEM for downloads and uploads
jcs made amendment 194 over 2 years ago
--- folder.c Thu Jun 30 15:38:24 2022
+++ folder.c Tue Jul 12 11:32:19 2022
@@ -22,7 +22,9 @@
#include "subtext.h"
#include "ansi.h"
#include "folder.h"
+#include "sha1.h"
#include "user.h"
+#include "zmodem.h"
#define FILES_PER_PAGE 10
@@ -36,10 +38,13 @@ struct struct_field folder_fields[] = {
1, ULONG_MAX },
{ "Name", CONFIG_TYPE_STRING,
offsetof(struct folder, name),
- 1, member_size(struct board, name) },
+ 1, member_size(struct folder, name) },
+ { "Disk Path", CONFIG_TYPE_STRING,
+ offsetof(struct folder, path),
+ 1, member_size(struct folder, path) },
{ "Description", CONFIG_TYPE_STRING,
offsetof(struct folder, description),
- 0, member_size(struct board, description) },
+ 0, member_size(struct folder, description) },
{ "Restricted Posting", CONFIG_TYPE_BOOLEAN,
offsetof(struct folder, restricted_posting),
0, 0 },
@@ -64,6 +69,8 @@ struct bile_object_field folder_object_fields[] = {
member_size(struct folder, last_upload_at), -1 },
{ offsetof(struct folder, file_count),
member_size(struct folder, file_count), -1 },
+ { offsetof(struct folder, path),
+ member_size(struct folder, path), -1 },
};
size_t nfolder_object_fields = nitems(folder_object_fields);
@@ -82,20 +89,17 @@ struct bile_object_field folder_file_object_fields[] =
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_size),
+ member_size(struct folder_file, notes_size), -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);
+ 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);
@@ -103,12 +107,17 @@ void folder_delete_file(struct session *s, struct fold
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);
+bool folder_file_save(struct folder *folder, struct folder_file *file,
+ char *temp_path);
+bool folder_file_valid_filename(struct session *session,
+ struct folder *folder, struct folder_file *file, char **error);
+void folder_edit_file(struct session *s, struct folder *folder,
+ struct folder_file *file);
void
folder_list(struct session *s)
{
- static struct session_menu_option opts[] = {
+ static const struct session_menu_option opts[] = {
{ '#', "#0123456789", "View file folder [#]" },
{ 'l', "Ll", "List folders" },
{ 'q', "QqXx", "Return to main menu" },
@@ -179,7 +188,7 @@ handle_opt:
void
folder_show(struct session *s, struct folder *folder)
{
- static struct session_menu_option opts[] = {
+ static const struct session_menu_option opts[] = {
{ '#', "#0123456789", "View file [#]" },
{ '<', "<", "Previous page of files" },
{ 'l', "Ll", "List files" },
@@ -188,32 +197,40 @@ folder_show(struct session *s, struct folder *folder)
{ 'q', "QqXx", "Return to main menu" },
{ '?', "?", "List menu options" },
};
+ char prompt[6 + BOARD_NAME_LENGTH + 8];
size_t n, page, pages, nfile_ids;
unsigned long *file_ids = NULL;
short ret;
char c;
- bool done, show_list, show_help;
+ bool done, show_list, show_help, find_files;
page = 0;
show_list = true;
+ find_files = 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;
+ snprintf(prompt, sizeof(prompt), "Files:%s", folder->name);
- if (page >= pages)
- page = pages - 1;
-
while (!done && !s->ending) {
+ if (find_files) {
+ 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;
+
+ find_files = false;
+ }
+
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,
+ c = session_menu(s, folder->description, prompt, opts,
nitems(opts), show_help);
show_help = false;
@@ -223,7 +240,8 @@ handle_opt:
show_list = true;
break;
case 'u':
- folder_upload(s, folder, NULL, NULL);
+ if (folder_upload(s, folder, NULL, NULL))
+ find_files = true;
break;
case '>':
case '<':
@@ -259,7 +277,9 @@ handle_opt:
break;
}
c = folder_file_view(s, folder, file_ids[c], c);
- goto handle_opt;
+ if (c == FILE_VIEW_RETURN_FIND)
+ find_files = true;
+ break;
case '?':
show_help = true;
break;
@@ -279,7 +299,6 @@ folder_list_files(struct session *s, struct folder *fo
{
char time[24];
size_t n, off, size, idx;
- struct username_cache *user;
struct bile_object *obj;
struct folder_file file;
short j, k;
@@ -287,7 +306,8 @@ folder_list_files(struct session *s, struct folder *fo
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",
+ session_printf(s,
+ "%s# Date File Description%s\r\n",
ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END));
session_flush(s);
@@ -309,14 +329,13 @@ folder_list_files(struct session *s, struct folder *fo
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));
+ strftime(time, sizeof(time), "%Y-%m-%d", localtime(&file.time));
- session_printf(s, "%ld %s {{#}}%- 10s %- 40s\r\n",
+ session_printf(s, "%ld %s {{#}}%- 17s %- 40s\r\n",
n,
- user ? user->username : "(unknown)",
+ time,
file.filename,
- "");
+ file.description);
}
session_flush(s);
}
@@ -325,10 +344,17 @@ unsigned long
folder_upload(struct session *s, struct folder *folder,
char *initial_filename, char *initial_description)
{
+ char upload_path[256] = { 0 };
+ char time[32];
struct folder_file file = { 0 };
- char *data = NULL, *tmp = NULL;
+ FILE *fp;
+ SHA1_CTX sha1;
+ struct stat sb;
+ unsigned long now;
+ struct zmodem_session *zs;
+ char *data = NULL, *tmp = NULL, *errorstr = NULL;
size_t size;
- short c, ret, proto = -1, error;
+ short c, ret, error;
if (!s->user) {
session_printf(s, "Uploading is not available to guests.\r\n"
@@ -344,71 +370,133 @@ folder_upload(struct session *s, struct folder *folder
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");
+ "To begin your ZMODEM upload, press Enter and choose a file through\r\n");
session_printf(s,
- "then be given the option to change the filename and enter a\r\n");
+ "your terminal program (only one file can be uploaded at a time).\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] ");
+ "\r\n"
+ "You'll then be given the option to change the filename and enter a\r\n"
+ "description for the file.\r\n"
+ "\r\n"
+ "To cancel your upload, press ^C a few times.\r\n\r\n");
session_flush(s);
+ session_pause_return(s, 0, "when you are ready...");
+
+ now = Time;
+ strftime(time, sizeof(time), "%Y%m%d%H%M%S", localtime(&now));
+ snprintf(upload_path, sizeof(upload_path),
+ "%s:upload-%s-%ld", folder->path, time, s->log.id);
- 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;
+ zs = ZCreateReceiver(s, upload_path);
+ zs->DoIACEscape = s->is_telnet;
+
+ ZInit(zs);
+ s->transferring_file = true;
+ session_log(s, "[%s] Uploading file to %s", folder->name, upload_path);
+ for (;;) {
+ if (ZHaveTimedOut(zs)) {
+ ZTimeOutProc(zs);
+ session_log(s, "[%s] Transfer timed out, canceling",
+ folder->name);
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;
}
+
+ if (!ZParse(zs))
+ break;
+
+ if (s->obuflen)
+ session_flush(s);
}
+
+ /* flush the pipes */
+ s->obuflen = 0;
+ uthread_msleep(1000);
+ session_clear_input(s);
+ s->transferring_file = false;
+
+ if (zs->file) {
+ fclose(zs->file);
+ zs->file = NULL;
+ }
- 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...");
+ session_printf(s, "\r\n\r\n");
- /* TODO: do xmodem or zmodem */
-
- if (file.tmp_filename[0] == '\0') {
- session_printf(s, "No file received, canceling.\r\n");
- session_flush(s);
+ if (stat(upload_path, &sb) != 0) {
+ session_printf(s, "Failed receiving file, aborting.\r\n");
+ session_pause_return(s, '\r', "to continue...");
+ ZDestroy(zs);
+ zs = NULL;
+ session_log(s, "[%s] Failed receiving upload, no temp file",
+ folder->name);
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);
+ if (sb.st_size != zs->file_size) {
+ session_log(s, "[%s] Received uploaded file of size %ld but "
+ "supposed to be %ld, canceling", folder->name, sb.st_size,
+ zs->file_size);
+
+ session_printf(s,
+ "Uploaded file expected to be {{B}}%ld{{/B}} byte%s, but "
+ "received\r\n"
+ "{{B}}%ld{{/B}} byte%s. Deleting file and canceling upload.\r\n",
+ zs->file_size, zs->file_size == 1 ? "" : "s",
+ sb.st_size, sb.st_size == 1 ? "" : "s");
+ session_pause_return(s, '\r', "to continue...");
+
+ ZDestroy(zs);
+ zs = NULL;
+ goto file_upload_cancel;
+ }
+ session_printf(s, "Successfully received file {{B}}%s{{/B}} of size "
+ "{{B}}%ld{{/B}} byte%s.\r\n",
+ zs->file_name, zs->file_size, zs->file_size == 1 ? "" : "s");
+ session_pause_return(s, '\r', "to continue...");
+
+ strlcpy(file.filename, zs->file_name, sizeof(file.filename));
+ file.size = sb.st_size;
+
+ session_log(s, "[%s] Received uploaded file %s of size %ld",
+ folder->name, file.filename, file.size);
+
+ /* this will fclose and free zs */
+ ZDestroy(zs);
+ zs = NULL;
+
+ session_printf(s, "Calculating SHA1 checksum of uploaded file...");
+ session_flush(s);
+ SHA1Init(&sha1);
+ data = xmalloc(1024);
+ fp = fopen(upload_path, "rb");
+ while (fp && !feof(fp)) {
+ size = fread(data, 1024, 1, fp);
+ SHA1Update(&sha1, (const u_int8_t *)data, size);
+ uthread_yield();
+ }
+ if (fp)
+ fclose(fp);
+ free(data);
+
+ SHA1End(&sha1, (char *)&file.sha1_checksum);
+ session_printf(s, "done.\r\n\r\n");
+ session_flush(s);
+ session_printf(s, "{{B}}Uploaded By:{{/B}} %s\r\n",
+ s->user->username);
+ session_printf(s, "{{B}}Folder:{{/B}} %s\r\n",
+ folder->name);
+ session_printf(s, "{{B}}SHA1 Checksum:{{/B}} %s\r\n",
+ file.sha1_checksum);
+ session_printf(s, "{{B}}File Size:{{/B}} %ld\r\n", file.size);
+ session_flush(s);
+
file_upload_annotate:
for (;;) {
- session_printf(s, "{{B}}Name:{{/B}} ");
+ session_printf(s, "{{B}}File Name:{{/B}} ");
session_flush(s);
tmp = session_field_input(s, FOLDER_FILE_FILENAME_LENGTH,
- FOLDER_FILE_FILENAME_LENGTH - 1, file.filename, true, 0);
+ FOLDER_FILE_FILENAME_LENGTH - 1, file.filename, false, 0);
if (tmp == NULL)
goto file_upload_cancel;
strlcpy(file.filename, tmp, sizeof(file.filename));
@@ -421,12 +509,40 @@ file_upload_annotate:
session_flush(s);
continue;
}
- /* TODO: verify filename */
+
+ if (!folder_file_valid_filename(s, folder, &file, &errorstr)) {
+ session_printf(s, "{{B}}Error:{{/B}} %s\r\n", errorstr);
+ free(errorstr);
+ continue;
+ }
+
break;
}
+
+ for (;;) {
+ session_printf(s, "{{B}}Short Description:{{/B}} ");
+ session_flush(s);
+ tmp = session_field_input(s, FOLDER_FILE_DESCR_LENGTH,
+ FOLDER_FILE_DESCR_LENGTH - 1, file.description, false, 0);
+ if (tmp == NULL)
+ goto file_upload_cancel;
+ strlcpy(file.description, tmp, sizeof(file.description));
+ free(tmp);
+ session_output(s, "\r\n", 2);
+ session_flush(s);
+ if (file.description[0] == '\0') {
+ session_output_template(s, "{{B}}Error:{{/B}} File "
+ "description cannot be blank (^C to cancel)\r\n");
+ session_flush(s);
+ continue;
+ }
+
+ break;
+ }
+
for (;;) {
- session_printf(s, "{{B}}Notes:{{/B}} (Optional)\r\n");
+ session_printf(s, "{{B}}Notes:{{/B}} (Optional, ^D when finished.)\r\n");
session_flush(s);
tmp = session_field_input(s, FOLDER_FILE_DESCR_LENGTH,
@@ -440,6 +556,12 @@ file_upload_annotate:
file.notes_size = 0;
break;
}
+ if (file.notes[0] == '\0') {
+ free(file.notes);
+ file.notes = NULL;
+ file.notes_size = 0;
+ break;
+ }
file.notes_size = strlen(file.notes) + 1;
break;
}
@@ -462,20 +584,22 @@ file_upload_annotate:
/* FALLTHROUGH */
case '\n':
case '\r':
- /* send */
+ /* save */
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);
+ if (folder_file_save(folder, &file, upload_path)) {
+ session_log(s, "[%s] Saved file %s", folder->name,
+ file.filename);
session_printf(s, "done\r\n");
- } else
+ session_flush(s);
+ goto file_upload_done;
+ } else {
session_printf(s, "failed!\r\n");
-
- session_flush(s);
-
- goto file_upload_done;
+ session_flush(s);
+ goto file_upload_cancel;
+ }
+ break;
case 'e':
case 'E':
session_printf(s, "%c\r\n", c);
@@ -487,7 +611,7 @@ file_upload_annotate:
session_flush(s);
/* FALLTHROUGH */
case CONTROL_C:
- goto file_upload_done;
+ goto file_upload_cancel;
}
}
@@ -496,16 +620,22 @@ file_upload_error:
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);
+ session_printf(s, "\r\n");
+ session_flush(s);
+
+ if (upload_path[0] != '\0') {
+ CtoPstr(upload_path);
+ error = FSDelete(upload_path, 0);
+ PtoCstr(upload_path);
if (error) {
- PtoCstr(&file.tmp_filename);
- session_log(s, "Failed deleting temporary uploaded file %s: %d",
- file.tmp_filename);
+ session_log(s, "[%s] Failed deleting temporary uploaded "
+ "file %s: %d", folder->name, upload_path, error);
+ } else {
+ session_log(s, "[%s] Canceled upload, deleted temp file %s",
+ folder->name, upload_path);
}
}
-
+
file_upload_done:
if (file.notes)
free(file.notes);
@@ -517,18 +647,22 @@ short
folder_file_view(struct session *s, struct folder *folder,
unsigned long id, short idx)
{
- static struct session_menu_option opts[] = {
+ static const 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" },
+ { 'r', "Rr", "Remove this file" },
+ { 'e', "Ee", "Edit this file" },
+ { 'q', "QqXx", "Return to folder" },
{ '?', "?", "List these options" },
};
+ char path[256];
char time[32];
struct folder_file file;
struct username_cache *uploader;
- unsigned long new_id;
- char prompt[BOARD_NAME_LENGTH + 8];
+ struct zmodem_session *zs;
+ struct session_menu_option *dopts = NULL;
+ FILE *fp;
+ char prompt[6 + BOARD_NAME_LENGTH + 8];
size_t n, size;
char c;
char *data;
@@ -543,36 +677,112 @@ folder_file_view(struct session *s, struct folder *fol
nfolder_file_object_fields, data, size, &file, true);
free(data);
+ dopts = xmalloc(sizeof(opts));
+ memcpy(dopts, opts, sizeof(opts));
+ if (!(s->user && (s->user->is_sysop ||
+ s->user->id == file.uploader_user_id))) {
+ /* disable deleting and editing */
+ dopts[2].key[0] = '\0';
+ dopts[3].key[0] = '\0';
+ }
+
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}}Folder:{{/B}} %s\r\n", folder->name);
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:{{/B}} %s %s\r\n", time,
+ db->config.timezone);
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_printf(s, "{{B}}Notes:{{/B}}\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);
+ session_printf(s,
+ "\r\n[ Not viewing file contents, press 'd' to download. ]\r\n");
+ session_flush(s);
+ snprintf(prompt, sizeof(prompt), "Files:%s:%ld", folder->name,
+ file.id);
+
while (!done && !s->ending) {
- c = session_menu(s, file.filename, prompt, opts, nitems(opts),
+ c = session_menu(s, file.filename, prompt, dopts, nitems(opts),
show_help);
show_help = false;
switch (c) {
+ case 'd':
+ snprintf(path, sizeof(path), "%s:%s", folder->path,
+ file.filename);
+ fp = fopen(path, "rb");
+ if (!fp) {
+ session_log(s, "[%s] Failed opening file %s",
+ folder->name, path);
+ session_printf(s,
+ "{{B}}Error:{{/B}} Failed opening file\r\n");
+ break;
+ }
+
+ zs = ZCreateSender(s, fp, file.filename);
+ zs->DoIACEscape = s->is_telnet;
+
+ ZInit(zs);
+ s->transferring_file = true;
+ session_log(s, "[%s] Downloading file %s", folder->name,
+ file.filename);
+ for (;;) {
+ if (ZHaveTimedOut(zs)) {
+ ZTimeOutProc(zs);
+ session_log(s, "[%s] Transfer timed out, canceling",
+ folder->name);
+ break;
+ }
+
+ if (!ZParse(zs))
+ break;
+
+ if (s->obuflen)
+ session_flush(s);
+ }
+
+ /* flush the pipes */
+ s->obuflen = 0;
+ uthread_msleep(1000);
+ session_clear_input(s);
+ s->transferring_file = false;
+
+ if (zs->file) {
+ fclose(zs->file);
+ zs->file = NULL;
+ }
+ ZDestroy(zs);
+ zs = NULL;
+
+ session_printf(s, "\r\n");
+ session_flush(s);
+ session_pause_return(s, CONTROL_C, "to continue...");
+ break;
+ case 'e':
+ 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;
+ }
+ folder_edit_file(s, folder, &file);
+ ret = FILE_VIEW_RETURN_FIND;
+ done = true;
+ break;
case 'r':
if (!(s->user && (s->user->is_sysop ||
s->user->id == file.uploader_user_id))) {
@@ -592,9 +802,6 @@ folder_file_view(struct session *s, struct folder *fol
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;
@@ -649,7 +856,7 @@ folder_find_file_ids(struct folder *folder, size_t *nf
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)
@@ -685,9 +892,11 @@ done:
return *nfile_ids;
}
-short
-folder_file_save(struct folder *folder, struct folder_file *file)
+bool
+folder_file_save(struct folder *folder, struct folder_file *file,
+ char *temp_path)
{
+ char new_name[256];
short ret;
char *data;
size_t size;
@@ -702,119 +911,133 @@ folder_file_save(struct folder *folder, struct folder_
if (ret != 0 || size == 0) {
warn("failed to marshall new file object");
file->id = 0;
- goto done;
+ return false;
}
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;
+ return false;
}
free(data);
bile_flush(folder->bile, true);
-
-done:
- return file->id;
+
+ snprintf(new_name, sizeof(new_name), "%s:%s", folder->path,
+ file->filename);
+ CtoPstr(temp_path);
+ CtoPstr(new_name);
+ ret = Rename(temp_path, 0, new_name);
+ PtoCstr(temp_path);
+ PtoCstr(new_name);
+ if (ret != 0) {
+ warn("FSRename(%s, 0, %s) failed: %d", temp_path, new_name, ret);
+ bile_delete(folder->bile, FOLDER_FILE_RTYPE, file->id);
+ file->id = 0;
+ return false;
+ }
+
+ return true;
}
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];
+ char path[256];
short ret;
- bool childs = false;
- unsigned long *new_post_ids;
- unsigned long *new_parent_post_ids;
+
+ bile_delete(folder->bile, FOLDER_FILE_RTYPE, file->id);
+ bile_flush(folder->bile, true);
+
+ snprintf(path, sizeof(path), "%s:%s", folder->path, file->filename);
+ CtoPstr(path);
+ ret = FSDelete(path, 0);
+ PtoCstr(path);
+
+ if (ret != 0)
+ session_log(s, "[%s] Failed deleting %s: %d", folder->name,
+ path, ret);
+
+ if (file->notes != NULL)
+ free(file->notes);
- if (thread->nposts == 1) {
- bile_delete(board->bile, BOARD_THREAD_RTYPE, thread->thread_id);
- bile_delete(board->bile, BOARD_POST_RTYPE, post->id);
- return;
+ session_log(s, "[%s] Deleted file %s (%ld)", folder->name,
+ file->filename, file->id);
+}
+
+bool
+folder_file_valid_filename(struct session *session,
+ struct folder *folder, struct folder_file *file, char **error)
+{
+ struct bile_object *o;
+ struct folder_file tfile;
+ size_t len, n;
+ char *data;
+ char c;
+
+ if (file->filename[0] == '\0') {
+ *error = xstrdup("filename cannot be empty");
+ return false;
}
- for (n = 0; n < thread->nposts; n++) {
- if (thread->parent_post_ids[n] == post->id) {
- childs = true;
- break;
- }
+ len = strlen(file->filename);
+ if (len > FOLDER_FILE_FILENAME_LENGTH) {
+ *error = xmalloc(61);
+ snprintf(*error, 60, "filename cannot be more than %d characters",
+ FOLDER_FILE_FILENAME_LENGTH);
+ return false;
}
- 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; n < len; n++) {
+ c = file->filename[n];
- 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++;
+ if (n == 0 && c == '.') {
+ *error = xstrdup("filename cannot start with a dot");
+ return false;
}
- 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 (n == len - 1 && c == ' ') {
+ *error = xstrdup("filename cannot end with a space");
+ return false;
}
- 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;
+ if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z') || c == '_' || c == '-' || c == '+' ||
+ c == '*' || c == ' ' || c == '.' || c == ',')) {
+ *error = xmalloc(61);
+ snprintf(*error, 60, "filename cannot contain '%c' character",
+ c);
+ return false;
+ }
}
- /* 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;
+ for (n = 0; o = bile_get_nth_of_type(folder->bile, n,
+ FOLDER_FILE_RTYPE); n++) {
+ if (o->id == file->id) {
+ free(o);
+ continue;
+ }
+
+ 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, &tfile, false);
+ free(data);
+ free(o);
- 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 (strcasecmp(file->filename, tfile.filename) == 0) {
+ *error = xstrdup("filename is already taken");
+ return false;
+ }
}
- 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
-}
+
+ return true;
+}
+
+void
+folder_edit_file(struct session *s, struct folder *folder,
+ struct folder_file *file)
+{
+ /* TODO */
+}
--- folder.h Thu Jun 30 13:38:47 2022
+++ folder.h Mon Jul 11 15:09:01 2022
@@ -31,8 +31,8 @@ struct folder {
bool restricted_viewing;
unsigned long last_upload_at;
unsigned long file_count;
+ char path[256];
- short folder_path_vrefnum;
struct bile *bile;
};
@@ -53,8 +53,6 @@ struct folder_file {
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;