Add -s to save and load buffers

master
C. McEnroe 2020-02-10 19:40:13 -05:00
parent 65603d5138
commit b59431bb15
5 changed files with 220 additions and 12 deletions

View File

@ -20,6 +20,7 @@
.Op Fl n Ar nick .Op Fl n Ar nick
.Op Fl p Ar port .Op Fl p Ar port
.Op Fl r Ar real .Op Fl r Ar real
.Op Fl s Ar save
.Op Fl u Ar user .Op Fl u Ar user
.Op Fl w Ar pass .Op Fl w Ar pass
.Op Ar config ... .Op Ar config ...
@ -123,6 +124,18 @@ Set realname to
.Ar real . .Ar real .
The default realname is the same as the nickname. The default realname is the same as the nickname.
. .
.It Fl s Ar name , Cm save = Ar name
Load and save the contents of windows from
.Ar name
in
.Pa $XDG_DATA_DIRS/catgirl ,
or an absolute or relative path if
.Ar name
starts with
.Ql /
or
.Ql \&. .
.
.It Fl u Ar user , Cm user = Ar user .It Fl u Ar user , Cm user = Ar user
Set username to Set username to
.Ar user . .Ar user .
@ -324,7 +337,7 @@ The color numbers are as follows:
.Sh FILES .Sh FILES
.Bl -tag -width Ds .Bl -tag -width Ds
.It Pa $XDG_CONFIG_DIRS/catgirl .It Pa $XDG_CONFIG_DIRS/catgirl
Configuration files are search for first in Configuration files are searched for first in
.Ev $XDG_CONFIG_HOME , .Ev $XDG_CONFIG_HOME ,
usually usually
.Pa ~/.config , .Pa ~/.config ,
@ -334,6 +347,18 @@ usually
.Pa /etc/xdg . .Pa /etc/xdg .
.It Pa ~/.config/catgirl .It Pa ~/.config/catgirl
The most likely location of configuration files. The most likely location of configuration files.
.
.It Pa $XDG_DATA_DIRS/catgirl
Save files are searched for first in
.Ev $XDG_DATA_HOME ,
usually
.Pa ~/.local/share ,
followed by the colon-separated list of paths
.Ev $XDG_DATA_DIRS ,
usually
.Pa /usr/local/share:/usr/share .
.It Pa ~/.local/share/catgirl
The most likely location of save files.
.El .El
. .
.Sh EXAMPLES .Sh EXAMPLES

17
chat.c
View File

@ -47,6 +47,15 @@ size_t idNext = Network + 1;
struct Self self = { .color = Default }; struct Self self = { .color = Default };
static const char *save;
static void exitSave(void) {
int error = uiSave(save);
if (error) {
warn("%s", save);
_exit(EX_IOERR);
}
}
uint32_t hashInit; uint32_t hashInit;
int procPipe[2] = { -1, -1 }; int procPipe[2] = { -1, -1 };
@ -84,7 +93,7 @@ int main(int argc, char *argv[]) {
const char *user = NULL; const char *user = NULL;
const char *real = NULL; const char *real = NULL;
const char *Opts = "!C:H:O:a:c:eh:j:k:n:p:r:u:vw:"; const char *Opts = "!C:H:O:a:c:eh:j:k:n:p:r:s:u:vw:";
const struct option LongOpts[] = { const struct option LongOpts[] = {
{ "insecure", no_argument, NULL, '!' }, { "insecure", no_argument, NULL, '!' },
{ "copy", required_argument, NULL, 'C' }, { "copy", required_argument, NULL, 'C' },
@ -99,6 +108,7 @@ int main(int argc, char *argv[]) {
{ "nick", required_argument, NULL, 'n' }, { "nick", required_argument, NULL, 'n' },
{ "port", required_argument, NULL, 'p' }, { "port", required_argument, NULL, 'p' },
{ "real", required_argument, NULL, 'r' }, { "real", required_argument, NULL, 'r' },
{ "save", required_argument, NULL, 's' },
{ "user", required_argument, NULL, 'u' }, { "user", required_argument, NULL, 'u' },
{ "debug", no_argument, NULL, 'v' }, { "debug", no_argument, NULL, 'v' },
{ "pass", required_argument, NULL, 'w' }, { "pass", required_argument, NULL, 'w' },
@ -121,6 +131,7 @@ int main(int argc, char *argv[]) {
break; case 'n': nick = optarg; break; case 'n': nick = optarg;
break; case 'p': port = optarg; break; case 'p': port = optarg;
break; case 'r': real = optarg; break; case 'r': real = optarg;
break; case 's': save = optarg;
break; case 'u': user = optarg; break; case 'u': user = optarg;
break; case 'v': self.debug = true; break; case 'v': self.debug = true;
break; case 'w': pass = optarg; break; case 'w': pass = optarg;
@ -154,6 +165,10 @@ int main(int argc, char *argv[]) {
if (privFile) fclose(privFile); if (privFile) fclose(privFile);
uiInit(); uiInit();
if (save) {
uiLoad(save);
atexit(exitSave);
}
uiShowID(Network); uiShowID(Network);
uiFormat(Network, Cold, NULL, "Traveling..."); uiFormat(Network, Cold, NULL, "Traveling...");
uiDraw(); uiDraw();

4
chat.h
View File

@ -26,6 +26,8 @@
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
#define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit #define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit
#define XDG_SUBDIR "catgirl"
typedef unsigned char byte; typedef unsigned char byte;
int procPipe[2]; int procPipe[2];
@ -144,6 +146,8 @@ void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str);
void uiFormat( void uiFormat(
size_t id, enum Heat heat, const time_t *time, const char *format, ... size_t id, enum Heat heat, const time_t *time, const char *format, ...
) __attribute__((format(printf, 4, 5))); ) __attribute__((format(printf, 4, 5)));
void uiLoad(const char *name);
int uiSave(const char *name);
enum Edit { enum Edit {
EditHead, EditHead,

View File

@ -24,8 +24,6 @@
#include "chat.h" #include "chat.h"
#define CONFIG_DIR "catgirl"
FILE *configOpen(const char *path, const char *mode) { FILE *configOpen(const char *path, const char *mode) {
if (path[0] == '/' || path[0] == '.') goto local; if (path[0] == '/' || path[0] == '.') goto local;
@ -35,10 +33,10 @@ FILE *configOpen(const char *path, const char *mode) {
char buf[PATH_MAX]; char buf[PATH_MAX];
if (configHome) { if (configHome) {
snprintf(buf, sizeof(buf), "%s/" CONFIG_DIR "/%s", configHome, path); snprintf(buf, sizeof(buf), "%s/" XDG_SUBDIR "/%s", configHome, path);
} else { } else {
if (!home) goto local; if (!home) goto local;
snprintf(buf, sizeof(buf), "%s/.config/" CONFIG_DIR "/%s", home, path); snprintf(buf, sizeof(buf), "%s/.config/" XDG_SUBDIR "/%s", home, path);
} }
FILE *file = fopen(buf, mode); FILE *file = fopen(buf, mode);
if (file) return file; if (file) return file;
@ -48,7 +46,7 @@ FILE *configOpen(const char *path, const char *mode) {
while (*configDirs) { while (*configDirs) {
size_t len = strcspn(configDirs, ":"); size_t len = strcspn(configDirs, ":");
snprintf( snprintf(
buf, sizeof(buf), "%.*s/" CONFIG_DIR "/%s", buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s",
(int)len, configDirs, path (int)len, configDirs, path
); );
file = fopen(buf, mode); file = fopen(buf, mode);

176
ui.c
View File

@ -20,11 +20,14 @@
#include <ctype.h> #include <ctype.h>
#include <curses.h> #include <curses.h>
#include <err.h> #include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/stat.h>
#include <sysexits.h> #include <sysexits.h>
#include <term.h> #include <term.h>
#include <termios.h> #include <termios.h>
@ -66,6 +69,13 @@ static void bufferPush(struct Buffer *buffer, time_t time, const char *line) {
if (!buffer->lines[i]) err(EX_OSERR, "strdup"); if (!buffer->lines[i]) err(EX_OSERR, "strdup");
} }
static time_t bufferTime(const struct Buffer *buffer, size_t i) {
return buffer->times[(buffer->len + i) % BufferCap];
}
static const char *bufferLine(const struct Buffer *buffer, size_t i) {
return buffer->lines[(buffer->len + i) % BufferCap];
}
enum { WindowLines = BufferCap }; enum { WindowLines = BufferCap };
struct Window { struct Window {
size_t id; size_t id;
@ -532,9 +542,8 @@ static void reflow(struct Window *window) {
werase(window->pad); werase(window->pad);
wmove(window->pad, WindowLines - 1, 0); wmove(window->pad, WindowLines - 1, 0);
window->unreadLines = 0; window->unreadLines = 0;
struct Buffer *buffer = &window->buffer;
for (size_t i = 0; i < BufferCap; ++i) { for (size_t i = 0; i < BufferCap; ++i) {
char *line = buffer->lines[(buffer->len + i) % BufferCap]; const char *line = bufferLine(&window->buffer, i);
if (!line) continue; if (!line) continue;
waddch(window->pad, '\n'); waddch(window->pad, '\n');
if (i >= (size_t)(BufferCap - window->unreadCount)) { if (i >= (size_t)(BufferCap - window->unreadCount)) {
@ -557,12 +566,12 @@ static void resize(void) {
statusUpdate(); statusUpdate();
} }
static void bufferList(struct Buffer *buffer) { static void bufferList(const struct Buffer *buffer) {
uiHide(); uiHide();
waiting = true; waiting = true;
for (size_t i = 0; i < BufferCap; ++i) { for (size_t i = 0; i < BufferCap; ++i) {
time_t time = buffer->times[(buffer->len + i) % BufferCap]; time_t time = bufferTime(buffer, i);
const char *line = buffer->lines[(buffer->len + i) % BufferCap]; const char *line = bufferLine(buffer, i);
if (!line) continue; if (!line) continue;
struct tm *tm = localtime(&time); struct tm *tm = localtime(&time);
@ -848,3 +857,160 @@ void uiRead(void) {
} }
inputUpdate(); inputUpdate();
} }
static FILE *dataOpen(const char *path, const char *mode) {
if (path[0] == '/' || path[0] == '.') goto local;
const char *home = getenv("HOME");
const char *dataHome = getenv("XDG_DATA_HOME");
const char *dataDirs = getenv("XDG_DATA_DIRS");
char homePath[PATH_MAX];
if (dataHome) {
snprintf(
homePath, sizeof(homePath),
"%s/" XDG_SUBDIR "/%s", dataHome, path
);
} else {
if (!home) goto local;
snprintf(
homePath, sizeof(homePath),
"%s/.local/share/" XDG_SUBDIR "/%s", home, path
);
}
FILE *file = fopen(homePath, mode);
if (file) return file;
if (errno != ENOENT) {
warn("%s", homePath);
return NULL;
}
char buf[PATH_MAX];
if (!dataDirs) dataDirs = "/usr/local/share:/usr/share";
while (*dataDirs) {
size_t len = strcspn(dataDirs, ":");
snprintf(
buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s",
(int)len, dataDirs, path
);
file = fopen(buf, mode);
if (file) return file;
if (errno != ENOENT) {
warn("%s", buf);
return NULL;
}
dataDirs += len;
if (*dataDirs) dataDirs++;
}
if (mode[0] != 'r') {
char *base = strrchr(homePath, '/');
*base = '\0';
int error = mkdir(homePath, S_IRWXU);
if (error && errno != EEXIST) {
warn("%s", homePath);
return NULL;
}
*base = '/';
file = fopen(homePath, mode);
if (!file) warn("%s", homePath);
return file;
}
local:
file = fopen(path, mode);
if (!file) warn("%s", path);
return file;
}
static const size_t Signatures[] = {
0x6C72696774616301,
};
static size_t signatureVersion(size_t signature) {
for (size_t i = 0; i < ARRAY_LEN(Signatures); ++i) {
if (signature == Signatures[i]) return i;
}
err(EX_DATAERR, "unknown file signature %zX", signature);
}
static int writeSize(FILE *file, size_t value) {
return (fwrite(&value, sizeof(value), 1, file) ? 0 : -1);
}
static int writeTime(FILE *file, time_t time) {
return (fwrite(&time, sizeof(time), 1, file) ? 0 : -1);
}
static int writeString(FILE *file, const char *str) {
return (fwrite(str, strlen(str) + 1, 1, file) ? 0 : -1);
}
int uiSave(const char *name) {
FILE *file = dataOpen(name, "w");
if (!file) return -1;
if (writeSize(file, Signatures[0])) return -1;
const struct Window *window;
for (window = windows.head; window; window = window->next) {
if (writeString(file, idNames[window->id])) return -1;
for (size_t i = 0; i < BufferCap; ++i) {
time_t time = bufferTime(&window->buffer, i);
const char *line = bufferLine(&window->buffer, i);
if (!line) continue;
if (writeTime(file, time)) return -1;
if (writeString(file, line)) return -1;
}
if (writeTime(file, 0)) return -1;
}
return fclose(file);
}
static size_t readSize(FILE *file) {
size_t value;
fread(&value, sizeof(value), 1, file);
if (ferror(file)) err(EX_IOERR, "fread");
if (feof(file)) errx(EX_DATAERR, "unexpected eof");
return value;
}
static time_t readTime(FILE *file) {
time_t time;
fread(&time, sizeof(time), 1, file);
if (ferror(file)) err(EX_IOERR, "fread");
if (feof(file)) errx(EX_DATAERR, "unexpected eof");
return time;
}
static ssize_t readString(FILE *file, char **buf, size_t *cap) {
ssize_t len = getdelim(buf, cap, '\0', file);
if (len < 0 && !feof(file)) err(EX_IOERR, "getdelim");
return len;
}
void uiLoad(const char *name) {
FILE *file = dataOpen(name, "r");
if (!file) {
if (errno != ENOENT) exit(EX_NOINPUT);
file = dataOpen(name, "w");
if (!file) exit(EX_CANTCREAT);
fclose(file);
return;
}
size_t signature = readSize(file);
signatureVersion(signature);
char *buf = NULL;
size_t cap = 0;
while (0 < readString(file, &buf, &cap)) {
struct Window *window = windowFor(idFor(buf));
for (;;) {
time_t time = readTime(file);
if (!time) break;
readString(file, &buf, &cap);
bufferPush(&window->buffer, time, buf);
}
reflow(window);
// TODO: Place some marker of end of save.
}
free(buf);
fclose(file);
}