This repository implements a collaborative Markdown editor comprised of three main layers:
- Markdown Library - immutable editing core
- Client - terminal-based user application
- Server - multi-threaded coordinator, command queue, versioned broadcast
| Name | Purpose |
|---|---|
chunk | Smallest text unit - stores text, length, type |
document | Doubly-linked list of chunks + version fields |
del_range[] | Tracks ranges deleted within current version |
document ├─ committed_head ─> [chunk] - committed content (read-only) ├─ head ─> [chunk] - WIP edits └─ del_ranges[] deferred deletions - Committed state - immutable snapshot of version N
- WIP state - edits for next version, lazily initialised
All public API functions follow the same pattern:
markdown_ensure_wip()- copy committed list on first edit- Adjust cursor using
del_rangeshelpers - Perform edit (
insert,delete,heading…) - Consolidate text chunks to keep list compact
Return codes are SUCCESS / INVALID_CURSOR_POS / … defined in common.h.
markdown_increment_version()commits the WIP listcommitted_versionis incremented, WIP pointers resetmarkdown_flatten()always serialises the committed list → guarantees that only committed content is exposed to file/network.
markdown_horizontal_rule() inserts --- and lazily adds a trailing \n only when required, respecting block-element newline rules.
- Signal handshake
clientsendsSIGRTMIN→ server
waits forSIGRTMIN+1 - FIFO pair
FIFO_C2S_<pid>- write commands
FIFO_S2C_<pid>- read broadcasts / replies - Authentication
sends<username>\n, receivesread/writeorReject UNAUTHORISED. - Document bootstrap
reads role, version, length, then full content → loads intolocal_docwithout incrementing version.
struct client { uint64_t current_version; // server version we believe in document *local_doc; // local committed copy ... };- Main thread - CLI loop, sends commands, prints DOC?/LOG?/PERM?
- Broadcast thread - consumes
FIFO_S2C, parsesread/write→ signals PERM? responseVERSION N…ENDbroadcast →process_broadcast_message()
- Append every line to local log (linked list)
- For each
EDIT … SUCCESSline- call matching
markdown_…using the document's current committed version - set
edits_applied = truewhen edit succeeds
- call matching
- After
END- if
edits_applied- callmarkdown_increment_version()soDOC?sees the update - set
local_doc->committed_version = target_version - set
current_version = target_version
- if
The client never invents a new version number - it adopts the server's.
| Structure | Role |
|---|---|
global_doc | Authoritative committed document |
command_queue_t | Thread-safe FIFO of pending client commands |
active_client_list | Singly linked list of live client_session nodes |
server_log_t | Linked list storing every broadcast for LOG? requests |
All shared structures are protected by mutexes.
struct version_log_entry { uint64_t version_number; char *broadcast_message; // full multi-line VERSION … END block ... };add_log_entry()- append under mutexprint_server_log()- dump to stdout onLOG?(admin)- Used to replay history for new clients (
LOG?from client).
command_queue_push()- O(1) enqueue from client threadscommand_queue_drain()- main thread atomically grabs whole list each interval- Commands carry
client_doc_versionto enforce version rules.
- Creates FIFOs & sends role + initial document (version, length, content)
- Reads commands line-by-line:
- Metadata:
PERM?,DOC?,LOG?,DISCONNECT - Edit commands pushed into
command_queue
- Metadata:
- Read-only users are pre-labelled
UNAUTHORISEDbefore push.
while (!shutdown) { select(stdin, timeout = interval) → handle admin commands (QUIT / DOC? / LOG?) drain command_queue sort by timestamp for each cmd { version check & execute via Markdown API } if edits made → markdown_increment_version(global_doc) build broadcast "VERSION N\nEDIT …\nEND\n" add_log_entry() broadcast_to_all_clients() } - Interval Timing - sleeps via
selecttimeout specified by CLI argument. - Broadcast - written to every
client_session->fd_s2c; clients update in their broadcast thread.
# Build default (COMP2017) version make clean && make all #build (COMP9017) version make comp9017 # Start server ./server <TIME-INTERVAL> # Start client ./client <SERVER_PID> <USER_NAME>