# Motivation In our computer science curriculum, we frequently use version control tools like Git during daily software development. Git tracks, records, and allows rollback of file versions, enabling us to systematically manage code history, compare differences between versions, track modifications, and restore to a previous state when necessary. Given Git's deep connection with file handling, we contemplated integrating Git's usage with a file processing project. Therefore, the concept behind this assignment is to "simulate a simplified version control system" with the goal of deeply understanding code writing and file operation processes through Git's fundamental operational concepts and data flow. For instance, we require students to implement features such as version addition, history viewing, extracting specific version content, and clearing file records. By personally coding these commands and completing the functionalities, students will gain a more profound understanding of file handling functions like fopen, fgets, fclose, and fseek. Moreover, through these functions, students will be encouraged to think about managing version differences, performing incremental file updates via a set of commands, tracking versions based on files, and recovering to previous states when needed. Through this exercise, we aim to establish a more solid foundation for understanding Git and other version control systems, while simultaneously reinforcing file handling and string manipulation skills in the C language. --- # Problem Description ## Homework ### Description In homework, we would like to implement a simple file editing system that allows users to manipulate local files through various commands. The system supports the following functions: 1. Open and manipulate a specified file. 2. Add data to a buffer and write the buffer content to the file. 3. Clear or display the current buffer content. 4. Read and display the file's content. 5. Close a file or clear its contents. 6. Switch between files. The program should do the corresponding operation of given commands. The user can enter several commands before it exit the program. ### Input Format 1. Upon starting, the program prompts the user to input a file name. If the file does not exist, the system automatically creates a new one. If user enters the 'exit' command, the program will be terminated directly. 2. Each line contains one command, and each commmand ends with a newline charater. 3. The program continues to run until the input command is `exit`. 4. If the `add <n>` command is issued, the subsequent **n** lines will be added to the buffer. ### Output Format - If an invalid command is entered, the program outputs: `Invalid command.`. - For other commands, the program outputs relevant messages or content based on the operation. ### Commands 1. **`exit`** - Close the current file and terminates the program. 2. **`close`** - Closes the current file and clears the buffer. - The user is then prompted to provide a new file name. - If `exit` is entered, the program terminates. Otherwise, a new file is opened. 3. **`add <n>`** - Adds **n** lines of text to the buffer. - Once text input is completed, the buffer can be further manipulated through other commands. 4. **`clear`** - Clears the content of the buffer. 5. **`status`** - Displays the current content of the buffer. 6. **`push`** - Writes the buffer content to the file. - After writing, the buffer is cleared. 7. **`pull`** - Reads and displays the content from the file. 8. **`truncate`** - Clears the content of the file. - Output: `File truncated.` ### Constraints - The **file name** must be no longer than **16** characters and may only contain English letters (A–Z, a–z), digits (0–9), and end with '.txt'. - Each **line** of text may contain a maximum of **128** characters and may only include English letters, digits and space. - The total length of the **buffer** must not exceed **1024** characters. - Using alternative data structures to store file content may exceed memory limits, so please do not attempt to bypass this. - Memory limit: 4 MB - Time limit: 1 s ### Sample Input ``` file00.txt add 2 hello world status push pull exit ``` ### Sample Output ``` hello world hello world ``` ### Sample Output Text File file00.txt ``` hello world ``` ### Explaination ``` file00.txt ``` Open file `file00.txt`. --- ``` add 2 hello world ``` Add two lines to buffer. ---- ``` status ``` Show the content in current buffer Output: ``` hello world ``` ---- ``` push ``` Write current buffer to file and clear buffer. ---- ``` pull ``` Print the current content of `file00.txt`. Output: ``` hello world ``` ---- ``` exit ``` Exit the program. ### Tips - All `push` operaion add new contents to the current file, the existing content in the file won't be edited unless `truncate` is called. ## Quiz ### Description Based on the file system implementation in the homework, the quiz enrichs the file editing system with version control functionality. Users can perform various file operations through a set of commands, including managing file content, maintaining a version history, and restoring specific versions. The system provides the following features: - Open and edit a specified file. - Add data to a buffer and write it to the file. - Clear or display the buffer content. - Read the latest version of the file. - View the history of all versions or retrieve the content of a specific version. - Switch between files during operation. Every execution of the `push` command automatically saves a new version, allowing future retrieval or restoration. ### Input format 1. Upon starting, the program prompts the user to input a file name. If user enters the `exit` command, the program will be terminated directly. 2. Each line contains one command, and each commmand ends with a newline charater. 3. The program continues to run until the input command is `exit`. 4. If the `add <n>` command is issued, the subsequent **n** lines will be added to the buffer. 5. If the `version` command is entered, it must be followed by `<n>`, indicating the you are viewing the **nth** version. ### Output Format - If an invalid command is entered, the program outputs: `Invalid command.`. - For other commands, the program outputs relevant messages or content based on the operation. ### Commands 1. **`exit`** - Terminates the program and closes the current file. 2. **`close`** - Closes the current file and clears the buffer. - The user is then prompted to provide a new file name. - If `exit` is entered, the program terminates. Otherwise, a new file is opened. - If the specified file does not exist, it is automatically created. 3. **`add <n>`** - Adds **n** lines of text to the buffer. - Once text input is completed, the buffer can be further manipulated through other commands. 4. **`clear`** - Clears the content of the buffer. 5. **`status`** - Displays the current content of the buffer. 6. **`push`** - Append the contents of the buffer to the end of the file and automatically saves a version in the specified format. The content of the new version contains only the content that was added at the time. - If there is no new content, write an empty version as well. - Example format for pushing a new version i: ``` Version <i> <new added content> --- ``` - Clears the buffer after writing. - Outputs: `<file_name> Version <n> saved.`, where `<file_name>` is the current file name and `<n>` is the version number. - Version numbers are 1-based. 7. **`pull`** - Displays everything that has been added to the file so far - Does not display version numbers. 8. **`log`** - Lists the history of all versions, including version numbers and their content. Versions are separated by ---. - If no version of the file is currently stored, the output is empty. 9. **`version <n>`** - Displays the content of the **nth** version so far - If the version does not exist, output `Invalid version.` 10. **`truncate`** - Clears all content from the file, including previously saved versions. - Outputs: `File truncated.`. #### Hints: Please carefully review the file read/write requirements for `push`, `pull`, `log`, and `version`. Note the following: - `push`: When writing to a file, only the newly added content will be recorded in the version. - `pull`: Outputs all the content added up to the current point, including the content from all previous versions. - `log`: Outputs the content for all versions. Each version should include both the newly added content for that version and the content from all preceding versions. - `version` Outputs all the added content up to the specified version. - Example: When the input is ``` add 1 This is version 1 push add 1 This is version 2 push pull log version 2 ``` The content in the file should be: ``` Version 1 This is version 1 --- Version 2 This is version 2 --- ``` The output result is: // The comment section does not need to be output ``` <file_name> Version 1 saved. <file_name> Version 2 saved. // output of pull This is version 1 This is version 2 // output of log Version 1 This is version 1 --- Version 2 This is version 1 This is version 2 --- // output of version 2 This is version 1 This is version 2 ``` ### Constraints - The **file name** must be no longer than **16** characters and may only contain English letters (A–Z, a–z), digits (0–9), and end with '.txt'. - Each **line** of text may contain a maximum of **128** characters and may only include English letters, digits, and space. - The total length of the **buffer** must not exceed **1024** characters. - Using alternative data structures to store file content may exceed memory limits, so please do not attempt to bypass this. - We ensure that the content write to the file does not contain data in `Version <n>` format. - Memory limit: 4MB - Time limit: 1 ~ 3s ### Sample Input 1 ``` file00.txt add 2 This is version 1 hello world push add 2 This is version 2 abcdefg push log pull exit ``` ### Sample Output 1 ``` file00.txt Version 1 saved. file00.txt Version 2 saved. Version 1 This is version 1 hello world --- Version 2 This is version 1 hello world This is version 2 abcdefg --- This is version 1 hello world This is version 2 abcdefg ``` ### Sample Output Text File file00.txt ``` Version 1 This is version 1 hello world --- Version 2 This is version 2 abcdefg --- ``` ### Sample Input 2 ``` file10.txt add 2 This is version 1 12345678 push add 2 This is version 2 hello world push add 2 This is version 3 abcdefg push version 2 truncate add 1 helloworld push log close exit ``` ### Sample Output 2 ``` file10.txt Version 1 saved. file10.txt Version 2 saved. file10.txt Version 3 saved. This is version 1 12345678 This is version 2 hello world File truncated. file10.txt Version 1 saved. Version 1 helloworld --- ``` ### Sample Output Text File file10.txt ``` Version 1 helloworld --- ``` --- # Solution ## Overview of problem-solving ideas ![Problem-solving ideas](https://hackmd.io/_uploads/ry6oHaW4yg.jpg) 1. When the program starts, the user enters the filename to open. If the input is `exit`, the program terminates; otherwise, it opens the specified file. 2. The user then inputs a command. If the command is `exit`, the program terminates. If the command is `close`, the file is closed, the buffer is cleared, and the user is prompted to enter a new filename to open. Otherwise, the corresponding command is executed. ## Homework Detailed implementation: - **open file** Open the file with `a+` mode. - **close file** Use `fclose` to close the file, and `memset` to empty the buffer. - **add** Use `sscanf` to parses the number of lines to add from the command. Use `fgets` to read input and `strncat` to appends it to the buffer. - **push** Use `fseek` to move the file pointer to the end of the file, `fprintf` to write the buffer to the file and `memset` to clear the buffer. - **pull** Use `fseek` to move the file pointer to the beginning of the file, then then read and print the file line by line. - **truncate** Close the file, then re-open file with `w+` mode. - **Different ways of writing to files** - If the complete content is stored in the buffer every time instead of writing it every time it is pushed, you may encounter MLE when the overall content is large. - Our method is to write the file and clear the buffer every time we push. ## Quiz Almost the same as homework Detailed implementation: - **push** Use `fseek` to move the file pointer to the end of the file, `fprintf` to write the version infomation, the buffer, and `---` to the file. Use`memset` to clear the buffer. - **version** Pass in the parameter `idx` to specify which version to view. Use `fseek` to move the file pointer to the beginning of the file, then read the file line by line. Output the version content until encountering `version [idx+1]` or the end of the file. - **log** Call version `i`, where `i` ranges from `1` to `version_num` (the total number of existing versions) - Analyze different ways to solve problems: - **Different ways to calculate the total number of versions** - Our approach:When opening a file, first read the current version number. -> higher efficiency - Reread every time needed. - **Different ways of log processing** - Create a cumulative_buffer to accumulate the entire content of the file. When encountering `---``, output the current content of the buffer. This approach will use more memory than our approach (the approach mentioned above). --- # Sample AC code ## Homework ```c #include <stdio.h> #include <string.h> #define MAX_BUFFER_SIZE 1024 #define MAX_COMMAND_LENGTH 16 #define MAX_MESSAGE_LENGTH 128 #define MAX_FILE_NAME_LENGTH 16 int main() { char buffer[MAX_BUFFER_SIZE] = {0}; char full_command[MAX_COMMAND_LENGTH] = {0}; char file_name[MAX_FILE_NAME_LENGTH] = {0}; scanf("%s", file_name); if(strcmp(file_name, "exit") == 0){ return 0; } FILE *stream; stream = fopen(file_name, "a+"); if(stream == NULL){ perror("Error opening file"); return 1; } char command[MAX_COMMAND_LENGTH] = {0}; while (1) { memset(full_command, 0, sizeof(full_command)); fgets(full_command, MAX_COMMAND_LENGTH, stdin); if (full_command[0] == '\n') { continue; } sscanf(full_command, "%s", command); if (strcmp(command, "exit") == 0) { fclose(stream); break; }else if(strcmp(command, "close") == 0){ memset(buffer, 0, sizeof(buffer)); fclose(stream); scanf("%s", file_name); while (getchar() != '\n'); if(strcmp(file_name, "exit") == 0){ break; } stream = fopen(file_name, "a+"); if(stream == NULL){ perror("Error opening file"); return 1; } }else if (strcmp(command, "add") == 0){ int num; sscanf(full_command, "%s %d", command, &num); char message[MAX_MESSAGE_LENGTH] = {0}; for(int i = 0; i < num; i++){ fgets(message, MAX_MESSAGE_LENGTH, stdin); strcat(buffer, message); } }else if(strcmp(command, "clear") == 0){ memset(buffer, 0, sizeof(buffer)); }else if(strcmp(command, "status") == 0){ printf("%s", buffer); }else if (strcmp(command, "push") == 0){ fprintf(stream, "%s", buffer); fflush(stream); memset(buffer, 0, sizeof(buffer)); }else if(strcmp(command, "pull") == 0){ char pull_buffer[MAX_BUFFER_SIZE] = {0}; fseek(stream, 0, SEEK_SET); while(fgets(pull_buffer, MAX_BUFFER_SIZE, stream) != NULL){ printf("%s", pull_buffer); } }else if(strcmp(command, "truncate") == 0){ fclose(stream); stream = fopen(file_name, "w+"); if(stream == NULL){ perror("Error truncate file"); return 1; } printf("File truncated.\n"); }else{ printf("Invalid command.\n"); } } return 0; } ``` ## Quiz ```c #include <stdio.h> #include <string.h> #define MAX_BUFFER_SIZE 1024 #define MAX_COMMAND_LENGTH 16 #define MAX_MESSAGE_LENGTH 128 #define MAX_FILE_NAME_LENGTH 16 void write_version(FILE *file, int version, const char *buffer) { fseek(file, 0, SEEK_END); // write version fprintf(file, "Version %d\n", version); fprintf(file, "%s", buffer); fprintf(file, "---\n"); fflush(file); } void read_version(FILE *file, int version_id) { // for pull and version command char line[MAX_BUFFER_SIZE] = {0}; int current_version = 0; fseek(file, 0, SEEK_SET); // move to the beginning of the file while (fgets(line, sizeof(line), file) != NULL) { if (strncmp(line, "Version ", 8) == 0) { sscanf(line, "Version %d", &current_version); } if (current_version == version_id) { while (fgets(line, sizeof(line), file) != NULL) { if (strncmp(line, "---", 3) == 0) break; printf("%s", line); // output the version content } return; } } } void log_func(FILE *file, int version_id) { // for log command char line[MAX_BUFFER_SIZE] = {0}; printf("Version %d\n", version_id); int current_version = 0; fseek(file, 0, SEEK_SET); // move to the beginning of the file while(fgets(line, sizeof(line), file) != NULL){ if (strncmp(line, "Version ", 8) == 0) { sscanf(line, "Version %d", &current_version); } if (current_version == version_id) { while (fgets(line, sizeof(line), file) != NULL) { if (strncmp(line, "---", 3) == 0){ printf("%s", line); break; } printf("%s", line); // output the version content } return; }else{ while(fgets(line, sizeof(line), file) != NULL){ if(strncmp(line, "---", 3) == 0){ break; } printf("%s", line); } } } } int main() { char buffer[MAX_BUFFER_SIZE] = {0}; char full_command[MAX_COMMAND_LENGTH] = {0}; char file_name[MAX_FILE_NAME_LENGTH] = {0}; scanf("%s", file_name); if(strcmp(file_name, "exit") == 0){ return 0; } FILE *stream; stream = fopen(file_name, "a+"); if(stream == NULL){ perror("Error opening file"); return 1; } // check the number of versions int version_count = 0; char line[MAX_BUFFER_SIZE]; fseek(stream, 0, SEEK_SET); while (fgets(line, sizeof(line), stream) != NULL) { if (strncmp(line, "Version ", 8) == 0) { version_count++; } } char command[MAX_COMMAND_LENGTH] = {0}; while (1) { memset(full_command, 0, sizeof(full_command)); fgets(full_command, MAX_COMMAND_LENGTH, stdin); if (full_command[0] == '\n') { continue; } sscanf(full_command, "%s", command); if (strcmp(command, "exit") == 0) { fclose(stream); break; }else if(strcmp(command, "close") == 0){ memset(buffer, 0, sizeof(buffer)); fclose(stream); scanf("%s", file_name); while (getchar() != '\n'); if(strcmp(file_name, "exit") == 0){ break; } stream = fopen(file_name, "a+"); if(stream == NULL){ perror("Error opening file"); return 1; } fseek(stream, 0, SEEK_SET); version_count = 0; while (fgets(line, sizeof(line), stream) != NULL) { if (strncmp(line, "Version ", 8) == 0) { version_count++; } } }else if (strcmp(command, "add") == 0){ int num; sscanf(full_command, "%s %d", command, &num); char message[MAX_MESSAGE_LENGTH]; for(int i = 0; i < num; i++){ fgets(message, MAX_MESSAGE_LENGTH, stdin); strcat(buffer, message); } }else if(strcmp(command, "clear") == 0){ memset(buffer, 0, sizeof(buffer)); }else if(strcmp(command, "status") == 0){ printf("%s", buffer); }else if (strcmp(command, "push") == 0){ version_count++; write_version(stream, version_count, buffer); printf("%s Version %d saved.\n", file_name,version_count); memset(buffer, 0, sizeof(buffer)); }else if(strcmp(command, "pull") == 0){ for(int i = 1; i <= version_count; i++){ read_version(stream, i); } }else if(strcmp(command, "log") == 0){ char line[MAX_BUFFER_SIZE]; for(int i = 1; i <= version_count; i++){ log_func(stream, i); } }else if (strncmp(command, "version", 7) == 0) { int version_id; sscanf(full_command, "%s %d", command, &version_id); if(version_id > version_count || version_id <= 0){ printf("Invalid version.\n"); }else{ for(int i = 1; i <= version_id; i++){ read_version(stream, i); } } }else if(strcmp(command, "truncate") == 0){ fclose(stream); stream = fopen(file_name, "w+"); if (stream == NULL){ perror("Error truncating file"); return 1; } version_count = 0; printf("File truncated.\n"); }else{ printf("Invalid command.\n"); } } return 0; } ``` # About Testcases ## Homework Our test suite includes a total of 12 test cases, each designed to evaluate different aspects of the program's functionality and robustness. - **Test cases 0 and 1**: These focus on single-file operations, thoroughly testing various commands, including `add`, `clear`, `status` for buffer manipulation; `push`, `pull`, and `truncate` for file operations; and `exit` and `close` for file switching and termination. These cases ensure the program handles a wide range of commands correctly within a single file. - **Test cases 2 and 3**: These are designed to verify whether commands can operate seamlessly across multiple files. Test case 3 further examines whether the program can correctly handle reading and writing when the input contains only a single newline character. - **Test cases 4 and 6**: Similar to test cases 2 and 3, these test multi-file operations but with larger input sizes. These serve as edge case tests for buffer size, where the content written to the buffer reaches the maximum size specified in the problem constraints. - **Test case 5**: This tests whether the buffer content is cleared at the specific points required by the problem. It ensures the program adheres to the correct buffer management rules. - **Test case 7**: This verifies whether the program correctly handles invalid commands by providing appropriate responses. - **Test cases 8 and 9**: These are edge cases targeting buffer size limits. They involve writing content to the file at the maximum size allowed by the problem constraints, testing the program's ability to handle such scenarios accurately. - **Test case 10**: A stress test with a 4MB input size, designed to ensure that the program adheres to the memory constraints. This prevents the use of unauthorized data structures for storing information, as doing so would exceed the memory limit. - **Test case 11**: A simple test with only an `exit` command. This serves as a reminder test to ensure that entering `exit` at the very start does not inadvertently create a file named "exit" but instead terminates the program immediately. --- These test cases collectively cover a comprehensive range of scenarios, including single and multi-file operations, buffer management, edge cases for buffer size and content, invalid command handling, as well as stress tests for memory constraints. They are carefully designed to validate the correctness, reliability, and robustness of the program under varying conditions. ## Quiz Our test suite for quiz also includes a total of 12 test cases, each designed to evaluate different aspects of the program's functionality and robustness. - **Test cases 0 and 1**: These focus on single-file operations, thoroughly testing various commands, including `add`, `clear`, `status` for buffer manipulation; `push`, `version`, `pull`, `log`, and `truncate` for file operations; and `close` and `exit` for file switching and termination. These cases ensure the program handles a wide range of commands correctly within a single file. - **Test cases 2 and 3**: These are designed to verify whether commands can operate seamlessly across multiple files. They test switching between files while ensuring that all operations behave as expected in different contexts. - **Test case 4**: This test operations involving multiple files but specifically examines the program's ability to distinguish between user-written content starting with "Version" and the versioning framework used internally by the program. This ensures correct parsing and prevents misinterpretation of content. - **Test case 5**: This case checks the behavior when the buffer is empty. It repeatedly performs `push` operations to verify whether the program correctly writes to the file and retrieves content without introducing errors. - **Test case 6**: An edge case designed to test the program's handling of maximum buffer size, as specified in the problem statement. This ensures that the program can handle large data inputs without crashing or malfunctioning. - **Test case 7**: This focuses on multi-file operations and verifies whether the program clears the buffer at the correct times in accordance with the problem requirements. It ensures that buffer management remains consistent across file switches. - **Test cases 8 and 9**: These are edge cases targeting buffer size limits. They involve writing content to the file at the maximum size allowed by the problem constraints, testing the program's ability to handle such scenarios accurately. - **Test case 10**: A stress test with a 4MB input size, designed to ensure that the program adheres to the memory constraints. This prevents the use of unauthorized data structures for storing information, as doing so would exceed the memory limit. - **Test case 11**: A simple test with only an `exit` command. This serves as a reminder test to ensure that entering `exit` at the very start does not inadvertently create a file named "exit" but instead terminates the program immediately. --- These test cases collectively cover a comprehensive range of scenarios, including single and multi-file operations, edge cases for buffer size and content, as well as stress tests for memory constraints. They are carefully designed to validate the correctness, reliability, and robustness of the program under varying conditions. --- # Teamwork - B11902088 謝典樺: Problem design, Testcases design - B12502021 楊硯堯: Slides preparation, Presentation, and solution analysis - R13944059 陳沁蕓: Testcases test - B11303042 黃沛綺: Slides preparation, Presentation - B09902083 郭俐欣 Problem Story design, English translation # Reference 1. [IBM], "Standard C Library Function Table, By Name", https://www.ibm.com/docs/en/i/7.5?topic=extensions-standard-c-library-functions-table-by-name 2. ChatGPT (OpenAI), Discussion and suggestions on problem and test cases design 3. Course materials # Helpfulness Ranking of Feedback Sheets | Rank | Group | |------|--------| | 1 | 7 | | 2 | 2 | | 3 | 11 | | 4 | 12 | | 5 | 21 | | 6 | 6 | | 7 | 14 | | 8 | 5 | | 9 | 3 | | 10 | 17 | | 11 | 19 | | 12 | 13 | | 13 | 18 | | 14 | 9 | | 15 | 20 | | 16 | 15 |