diff --git a/Makefile b/Makefile index 13620cc1..e4b644fc 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,13 @@ -all: kilo +CC = gcc -g -Wall -W -ansi -pedantic -std=c99 -pthread -o +C+ = g++ -g -Wall -pthread -std=c++11 -o + +all: kilo server kilo: kilo.c - $(CC) -o kilo kilo.c -Wall -W -pedantic -std=c99 + $(CC) kilo kilo.c + +server: server.cpp + $(C+) server server.cpp clean: - rm kilo + rm -f kilo server transfer diff --git a/README.md b/README.md index 47d612fe..a5713011 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,11 @@ A screencast is available here: https://asciinema.org/a/90r2i9bq8po03nazhqtsifks Usage: kilo `` -Keys: +New Usage: kilo `` `` + +'get' to copy file from server + +Editor Keys: CTRL-S: Save CTRL-Q: Quit @@ -24,3 +28,29 @@ style CLI. Kilo was written by Salvatore Sanfilippo aka antirez and is released under the BSD 2 clause license. + +# cpd-term-project +Term Project for COP5570 +Concurrent Text File Editing (Google Docs redev) + +Contributers: +Skylar Scorca, +Tony Drouillard, +Jack Dewey + +Project Objectives (intended features) +- Allow users to update a single text file simultaneously +- Allow users to view the updates of other users in real-time +- Handle the movement of a user’s cursor after an update is made +- Handle transactions quickly and efficiently + +Optional Objectives: (if time allows) +- Allow users to create and delete text files in a greater system of files +- Allow users to modify the tree structure of the greater system of files +- Allow users to view the updates to the file system in real-time + +Due date countdown: +https://www.timeanddate.com/countdown/generic?iso=20230428T2359&p0=856&msg=cpd+term+project+due+date&font=cursive&csz=1 + +Google Drive Folder: +https://drive.google.com/drive/folders/1sZ5ulUizpBA6Rq9KOYdHxhaF4QC_5UAi?usp=share_link diff --git a/TODO b/TODO deleted file mode 100644 index 95ae28b9..00000000 --- a/TODO +++ /dev/null @@ -1,10 +0,0 @@ -IMPORTANT -=== - -* Testing and stability to reach "usable" level. - -MAYBE -=== - -* Send alternate screen sequences if TERM=xterm: "\033[?1049h" and "\033[?1049l" -* Improve internals to be more understandable. diff --git a/kilo.c b/kilo.c index 406eb7be..bfe2b34a 100644 --- a/kilo.c +++ b/kilo.c @@ -49,10 +49,21 @@ #include #include #include +#include #include #include #include #include +#include +#include + +/* Server Communication */ +#define _POSIX_SOURCE +#define MSGSIZE 1024 +static int serverFd; + +/* Temporary File */ +char filename[20] = "transfer"; /* Syntax highlight types */ #define HL_NORMAL 0 @@ -109,6 +120,8 @@ struct editorConfig { struct editorSyntax *syntax; /* Current syntax highlight, or NULL. */ }; +//Note: we may want to add a few fields to the erow and editorConfig structs + static struct editorConfig E; enum KEY_ACTION{ @@ -198,6 +211,7 @@ struct editorSyntax HLDB[] = { #define HLDB_ENTRIES (sizeof(HLDB)/sizeof(HLDB[0])) /* ======================= Low level terminal handling ====================== */ +//Note: probably don't need to edit these static struct termios orig_termios; /* In order to restore at exit.*/ @@ -214,7 +228,7 @@ void editorAtExit(void) { disableRawMode(STDIN_FILENO); } -/* Raw mode: 1960 magic shit. */ +/* Raw mode: 1960 magic*/ int enableRawMode(int fd) { struct termios raw; @@ -362,6 +376,7 @@ int getWindowSize(int ifd, int ofd, int *rows, int *cols) { } /* ====================== Syntax highlight color scheme ==================== */ +//Note: probably don't need to edit these int is_separator(int c) { return c == '\0' || isspace(c) || strchr(",.()+-/*=~%[];",c) != NULL; @@ -551,6 +566,10 @@ void editorSelectSyntaxHighlight(char *filename) { } /* ======================= Editor rows implementation ======================= */ +//Note: probably want to edit these. perhaps at the end of an update function, we call another +// function to send an update message to the server. +//Note: we can copy the logic from these functions to allow for editing after receuving an +// update message from the server. /* Update the rendered version and the syntax highlight of a row. */ void editorUpdateRow(erow *row) { @@ -607,6 +626,11 @@ void editorInsertRow(int at, char *s, size_t len) { editorUpdateRow(E.row+at); E.numrows++; E.dirty++; + +/* //setup server message + char msg[MSGSIZE]; + sprintf(msg, "ir:%d:%s", at, s); + send(serverFd, msg, MSGSIZE, 0); */ } /* Free row's heap allocated stuff. */ @@ -616,7 +640,7 @@ void editorFreeRow(erow *row) { free(row->hl); } -/* Remove the row at the specified position, shifting the remainign on the +/* Remove the row at the specified position, shifting the remaining on the * top. */ void editorDelRow(int at) { erow *row; @@ -628,6 +652,11 @@ void editorDelRow(int at) { for (int j = at; j < E.numrows-1; j++) E.row[j].idx++; E.numrows--; E.dirty++; + + //setup server message + char msg[MSGSIZE]; + sprintf(msg, "dr:%d", at); + send(serverFd, msg, MSGSIZE, 0); } /* Turn the editor rows into a single heap-allocated string. @@ -678,6 +707,11 @@ void editorRowInsertChar(erow *row, int at, int c) { row->chars[at] = c; editorUpdateRow(row); E.dirty++; + + //setup server message + char msg[MSGSIZE]; + sprintf(msg, "ic:%d:%d:%c", row->idx, at, c); + send(serverFd, msg, MSGSIZE, 0); } /* Append the string 's' at the end of a row */ @@ -688,6 +722,11 @@ void editorRowAppendString(erow *row, char *s, size_t len) { row->chars[row->size] = '\0'; editorUpdateRow(row); E.dirty++; + + //setup server message + char msg[MSGSIZE]; + sprintf(msg, "as:%d:%s", row->idx, s); + send(serverFd, msg, MSGSIZE, 0); } /* Delete the character at offset 'at' from the specified row. */ @@ -697,9 +736,15 @@ void editorRowDelChar(erow *row, int at) { editorUpdateRow(row); row->size--; E.dirty++; + + //setup server message + char msg[MSGSIZE]; + sprintf(msg, "dc:%d:%d", row->idx, at); + send(serverFd, msg, MSGSIZE, 0); } /* Insert the specified char at the current prompt position. */ +//don't need to make a server message here since we call editorInsertRow and editorRowInsertChar void editorInsertChar(int c) { int filerow = E.rowoff+E.cy; int filecol = E.coloff+E.cx; @@ -722,14 +767,21 @@ void editorInsertChar(int c) { /* Inserting a newline is slightly complex as we have to handle inserting a * newline in the middle of a line, splitting the line as needed. */ +//don't need to make a server message here since we call editorInsertRow void editorInsertNewline(void) { int filerow = E.rowoff+E.cy; int filecol = E.coloff+E.cx; erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; + char msg[MSGSIZE]; if (!row) { if (filerow == E.numrows) { editorInsertRow(filerow,"",0); + + //server message + sprintf(msg, "ir:%d:%s", filerow, ""); + send(serverFd, msg, MSGSIZE, 0); + goto fixcursor; } return; @@ -739,14 +791,24 @@ void editorInsertNewline(void) { if (filecol >= row->size) filecol = row->size; if (filecol == 0) { editorInsertRow(filerow,"",0); + + //server message + sprintf(msg, "ir:%d:%s", filerow, ""); + send(serverFd, msg, MSGSIZE, 0); } else { /* We are in the middle of a line. Split it between two rows. */ editorInsertRow(filerow+1,row->chars+filecol,row->size-filecol); + + //server message + sprintf(msg, "ir:%d:%s", filerow+1, row->chars+filecol); + send(serverFd, msg, MSGSIZE, 0); + row = &E.row[filerow]; row->chars[filecol] = '\0'; row->size = filecol; editorUpdateRow(row); } + fixcursor: if (E.cy == E.screenrows-1) { E.rowoff++; @@ -758,6 +820,7 @@ void editorInsertNewline(void) { } /* Delete the char at the current prompt position. */ +//don't need to make a server message here since we call editorDelRow and editorRowDelChar void editorDelChar() { int filerow = E.rowoff+E.cy; int filecol = E.coloff+E.cx; @@ -852,6 +915,7 @@ int editorSave(void) { } /* ============================= Terminal update ============================ */ +//Note: probably don't need to edit these /* We define a very simple "append buffer" structure, that is an heap * allocated string where we can append to. This is useful in order to @@ -1008,6 +1072,7 @@ void editorSetStatusMessage(const char *fmt, ...) { } /* =============================== Find mode ================================ */ +//Note: probably don't need to edit these #define KILO_QUERY_LEN 256 @@ -1107,6 +1172,7 @@ void editorFind(int fd) { } /* ========================= Editor events handling ======================== */ +//Note: we probably don't need to edit these /* Handle cursor position change because arrow keys were pressed. */ void editorMoveCursor(int key) { @@ -1184,12 +1250,7 @@ void editorMoveCursor(int key) { /* Process events arriving from the standard input, which is, the user * is typing stuff on the terminal. */ -#define KILO_QUIT_TIMES 3 void editorProcessKeypress(int fd) { - /* When the file is modified, requires Ctrl-q to be pressed N times - * before actually quitting. */ - static int quit_times = KILO_QUIT_TIMES; - int c = editorReadKey(fd); switch(c) { case ENTER: /* Enter */ @@ -1200,17 +1261,17 @@ void editorProcessKeypress(int fd) { * to the edited file. */ break; case CTRL_Q: /* Ctrl-q */ - /* Quit if the file was already saved. */ - if (E.dirty && quit_times) { - editorSetStatusMessage("WARNING!!! File has unsaved changes. " - "Press Ctrl-Q %d more times to quit.", quit_times); - quit_times--; - return; - } + if (!fork()){ + char *args[] = {"clear", NULL}; + execvp("clear", args); // Kills child + } + wait(NULL); // Wait on child + close(serverFd); + remove(filename); exit(0); break; case CTRL_S: /* Ctrl-s */ - editorSave(); + //editorSave(); break; case CTRL_F: editorFind(fd); @@ -1250,8 +1311,6 @@ void editorProcessKeypress(int fd) { editorInsertChar(c); break; } - - quit_times = KILO_QUIT_TIMES; /* Reset it to the original value. */ } int editorFileWasModified(void) { @@ -1288,21 +1347,112 @@ void initEditor(void) { signal(SIGWINCH, handleSigWinCh); } +/* ========================= Communication with Server ======================== */ + +void receiveFile(){ + ssize_t n; + FILE *file = fopen("transfer", "w"); + char buffer[MSGSIZE]; + + n = read(serverFd, buffer, MSGSIZE); + buffer[n] = '\0'; + + while (1){ + if ((n = read(serverFd, buffer, MSGSIZE)) > 0){ + buffer[n] = '\0'; + if (!strcmp(buffer, "End Transfer")){ + printf("Closing\n"); + fclose(file); + return; + } + fprintf(file, "%s\n", buffer); + send(serverFd, "ACK", MSGSIZE, 0); + } + } +} + +void handle_server_message(char *msg){ + char cmd[MSGSIZE]; + int i; + + //get command + for(i = 0; i < MSGSIZE; ++i){ + if(msg[i] == ':'){break;} //stop reading when we encounter colon + cmd[i] = msg[i]; + } +} + +void *read_server_messages(){ + char buffer[MSGSIZE]; + int n; + + pthread_detach(pthread_self()); + + if ((n = read(serverFd, buffer, MSGSIZE)) == 0) { + printf("server crashed\n"); + exit(0); + } + buffer[n] = '\0'; + + handle_server_message(buffer); + + return NULL; +} + +/* ============================= Main Program ================================== */ + +//main program of text-editor client int main(int argc, char **argv) { - if (argc != 2) { - fprintf(stderr,"Usage: kilo \n"); + pthread_t read_thread; + + //check command-line args + if (argc != 3) { + fprintf(stderr,"Usage: kilo \n"); exit(1); } - initEditor(); - editorSelectSyntaxHighlight(argv[1]); - editorOpen(argv[1]); + //setup + struct addrinfo hints, *res, *traverser; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + int r = getaddrinfo(argv[1], argv[2], &hints, &res); + if (r != 0){ + fprintf(stderr,"Error: Can't find server.\n"); + return 1; + } + + // Try addresses until one is successful + for (traverser = res; traverser; traverser = traverser->ai_next){ + if ((serverFd = socket(traverser->ai_family, traverser->ai_socktype, traverser->ai_protocol)) != -1){ + if ((connect(serverFd, traverser->ai_addr, traverser->ai_addrlen)) == 0){ + break; + } + } + } + printf("Connected\n"); + + send(serverFd, "get", 1024, 0); + receiveFile(); + + //create thread for reading server messages + int i; + if ((i = pthread_create(&read_thread, NULL, read_server_messages, (void*)NULL)) != 0) { + printf("thread creation failed\n"); + } + + // char buffer[1024] = {'g', 'e', 't', '\0'}; + //start editor + initEditor(); + editorSelectSyntaxHighlight(filename); + editorOpen(filename); enableRawMode(STDIN_FILENO); editorSetStatusMessage( - "HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find"); + "HELP: Ctrl-Q = quit | Ctrl-F = find"); while(1) { editorRefreshScreen(); editorProcessKeypress(STDIN_FILENO); } + return 0; } diff --git a/server.cpp b/server.cpp new file mode 100755 index 00000000..d4b54248 --- /dev/null +++ b/server.cpp @@ -0,0 +1,183 @@ +// C Headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// C++ Headers +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +vector users; + +//readFile - reads lines in file into the data structure lines +vector readFile(){ + string line; + vector lines; + ifstream file("test"); + while (getline(file, line)){ + lines.push_back(line); + } + return lines; +} + +//sendFile - send file line-by-line to a client at fd +void sendFile(int fd, vector lines){ + vector::iterator i; + + send(fd, (void*)"Start Transfer", 1024, 0); + + //this is the same as the loop that is commented out + for(string msg : lines){ + char buffer[1024]; + + send(fd, msg.c_str(), msg.length(), 0); + read(fd, buffer, 1024); + } + + send(fd, (void*)"End Transfer", 1024, 0); +} + +//threadFunc - thread function to read any messages from a client +void *threadFunc(void *args){ + ssize_t n; + int clientFd = *(int*)args; + char buffer[1024]; + bool copied = false; + + pthread_detach(pthread_self()); + + // Read until disconnection + while ((n = read(clientFd, buffer, 1024)) > 0){ + string line(buffer); + + if (line == "exit"){ + close(clientFd); + } + else if (line == "get"){ + cout << "Get Received" << endl; + sendFile(clientFd, readFile()); + copied = true; + } + else if(copied){ + stringstream ss(line); + + //get update type + bool validCMD = true; + string cmd; + getline(ss, cmd, ':'); + + if(cmd == "ir"){ + cout << "ir received\n" << line << endl; + } + else if(cmd == "dr"){ + cout << "dr received\n" << line << endl; + } + else if(cmd == "ic"){ + cout << "ic received\n" << line << endl; + } + else if(cmd == "as"){ + cout << "as received\n" << line << endl; + } + else if(cmd == "dc"){ + cout << "dc received\n" << line << endl; + } + else{ + cout << "Error: " << cmd << " is not a valid update type\n"; + validCMD = false; + } + + if(validCMD){ + // Send update messages + for(auto user : users){ + if(user == clientFd) continue; + write(user, buffer, 1024); + } + } + } + } + return NULL; +} + +//handleSigInt - action performed when user types ctrl-C +void handleSigInt(int unused __attribute__((unused))){ + exit(0); +} + +//main server program +int main(int argc, char *argv[]){ + if (argc != 2){ + cerr << "Usage: \n"; + return 1; + } + stringstream stream(argv[1]); + int port; + stream >> port; + //setup SIGINT signal handler + signal(SIGINT, handleSigInt); + + // Create server socket + int serverFd; + if ((serverFd = socket(AF_INET, SOCK_STREAM, 0)) < 0){ + std::cerr << "Error: Can't create socket." << std::endl; + return 1; + } + + // Set options for socket + int val; + if (setsockopt(serverFd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(int))){ + std::cerr << "Error: Can't reuse socket." << std::endl; + return 2; + } + + // Configure addr and bind + struct sockaddr_in serverAddr; + serverAddr.sin_family = AF_INET; + serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); + serverAddr.sin_port = htons(port); + if (bind(serverFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1){ + cerr << "Error: Can't bind socket to port." << endl; + return 3; + } + + // Listen for client connections + if (listen(serverFd, 20) < 0){ + cerr << "Error: Can't listen for clients." << endl; + return 4; + } + + // Get name and port assigned to server + char *name = new char[1024]; + struct sockaddr_in infoAddr; + socklen_t len = sizeof(infoAddr); + gethostname(name, 1024); + getsockname(serverFd, (struct sockaddr *)&infoAddr, &len); + + // Report name and port + cout << name << ":" << ntohs(serverAddr.sin_port)<< endl; + delete name; + + // Connect a client + struct sockaddr_in cliAddr; + len = sizeof(cliAddr); + while (true){ + users.push_back(accept(serverFd, (struct sockaddr *)&serverAddr, &len)); + + // Create thread to deal with client + pthread_t thread; + pthread_create(&thread, NULL, threadFunc, (void *)&users[users.size()-1]); + } + return 0; +} diff --git a/test b/test new file mode 100755 index 00000000..1c7d86c3 --- /dev/null +++ b/test @@ -0,0 +1,5 @@ +test file +here is a test +this test is working +very nice +goodbye