From 42e507ec0a32d188b02f147a0a1927b834602ce9 Mon Sep 17 00:00:00 2001 From: Konstantin Nazarov Date: Sat, 19 Jun 2021 19:47:02 +0000 Subject: [PATCH] Correctly track modified/unmodified notes --- notes.sh | 142 ++++++++++++++++++++++++++++++++++++++++--------------- test.sh | 89 ++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 39 deletions(-) diff --git a/notes.sh b/notes.sh index c9c8922..bcefcac 100755 --- a/notes.sh +++ b/notes.sh @@ -151,6 +151,27 @@ get_header() { grep -m1 "^${HEADER}: " < "$FILE" | sed -n "s/^${HEADER}: \(.*\)$/\1/p" } +find_file_by_id() { + ID="$1" + + { grep -l -r -m1 "^X-Note-Id: $ID$" "$BASEDIR" || true; } | sort| head -1 +} + +assert_find_file_by_id() { + FILE="$(find_file_by_id "$1")" + + if [ ! -f "$FILE" ]; then + die "Note with ID <$ID> not found" + fi + + echo "$FILE" +} + +find_files_by_id() { + ID="$1" + grep -l -r -m 1 "^X-Note-Id: $ID$" "$BASEDIR" || true +} + get_part() { FILE="$1" BOUNDARY="$2" @@ -237,11 +258,7 @@ unpack_mime() { MIME_TYPE=$(get_header "$FILE" Content-Type) NOTE_ID=$(get_header "$FILE" X-Note-Id) - echo "X-Date: $DATE" > "$DIR/note.md" - if [ -n "$NOTE_ID" ]; then - echo "X-Note-Id: $NOTE_ID" >> "$DIR/note.md" - fi - echo "Subject: $SUBJECT" >> "$DIR/note.md" + get_headers "$FILE" | grep -v "^Content-Type\|^Content-Disposition\|^Date\|^MIME-Version" >> "$DIR/note.md" echo "" >> "$DIR/note.md" TMP=$(mktemp --tmpdir="$DIR") @@ -290,7 +307,7 @@ pack_mime() { echo "MIME-Version: 1.0" echo "Date: $MIME_TIMESTAMP" echo "Content-Type: multipart/mixed; boundary=\"$BOUNDARY\"" - get_headers "$DIR/note.md" + get_headers "$DIR/note.md" echo echo "--$BOUNDARY" echo "Content-Type: text/plain; charset=utf-8" @@ -310,20 +327,14 @@ pack_mime() { echo "--$BOUNDARY--" >> "$FILE" } -new_entry() { +input_note() { INP="$1" - DIR=$(mktemp -d) + OUTP="$2" + + DIR="$(mktemp -d)" ENTRY_FILE="$DIR/note.md" - ENTRY_FILE_START="$(mktemp)" MIME_TIMESTAMP=$(LC_ALL="en_US.UTF-8" date "+$DATE_FORMAT") UTC_TIMESTAMP=$(utc_timestamp) - cat > "$ENTRY_FILE" <<- EOF - X-Date: $UTC_TIMESTAMP - X-Note-Id: $(uuid) - Subject: - EOF - - cp "$ENTRY_FILE" "$ENTRY_FILE_START" if [ -n "$INP" ] && [ ! -f "$INP" ] && [ ! -d "$INP" ]; then die "File or directory doesn't exist: $INP" @@ -340,12 +351,12 @@ new_entry() { die "File doesn't exist: $INP/note.md" fi cp -n "$INP"/* "$DIR/" || true - { - get_headers "$INP/note.md" - echo "" - get_body "$INP/note.md" - } >> "$DIR/note.md" elif [ -t 0 ]; then + cat > "$ENTRY_FILE" <<- EOF + X-Date: $UTC_TIMESTAMP + X-Note-Id: $(uuid) + Subject: + EOF OLD_DIR="$(pwd)" cd "$DIR" "$EDITOR" "$ENTRY_FILE" @@ -357,42 +368,95 @@ new_entry() { fi MERGED_ENTRY_FILE="$(mktemp)" - HEADERS=$(get_headers "$ENTRY_FILE" | tac | sort -u -k1,1) - + HEADERS=$(get_headers "$ENTRY_FILE") + BODY="$(get_body "$ENTRY_FILE")" { + echo "$HEADERS" | grep -q "^X-Date:" || echo "X-Date: $UTC_TIMESTAMP" + echo "$HEADERS" | grep -q "^X-Note-Id:" || echo "X-Note-Id: $(uuid)" echo "$HEADERS" + echo "$HEADERS" | grep -q "^Subject:" || echo "Subject: " echo "" get_body "$ENTRY_FILE" } > "$MERGED_ENTRY_FILE" + mv "$MERGED_ENTRY_FILE" "$ENTRY_FILE" - if ! cmp -s "$ENTRY_FILE" "$ENTRY_FILE_START" ; then - UNIX_TIMESTAMP=$(date "+%s") - HOSTNAME=$(hostname -s) + pack_mime "$DIR" "$OUTP" - RESULT=$(mktemp) - pack_mime "$DIR" "$RESULT" - FILENAME="$UNIX_TIMESTAMP.${PID}_1.${HOSTNAME}:2,S" - mv "$RESULT" "$BASEDIR/cur/$FILENAME" - fi + rm -rf "$DIR" } -find_file_by_id() { +remove_notes_by_id() { ID="$1" - FILE="$( { grep -l -r "^X-Note-Id: $ID$" "$BASEDIR" || true; } | head -1)" + find_files_by_id "$ID" | while read -r FN + do + rm "$FN" + done +} - if [ ! -f "$FILE" ]; then - die "Note with ID <$ID> not found" +notes_equal() { + NOTE1="$1" + NOTE2="$2" + + MIME_TYPE1=$(get_header "$NOTE1" Content-Type) + MIME_TYPE2=$(get_header "$NOTE2" Content-Type) + BOUNDARY1=$(echo "$MIME_TYPE1" | sed -n 's/^.*boundary="\(.*\)"$/\1/p') + BOUNDARY2=$(echo "$MIME_TYPE2" | sed -n 's/^.*boundary="\(.*\)"$/\1/p') + + FILTER1="^Date:" + FILTER2="^Date:" + + if [ ! -z "$BOUNDARY1" ]; then + FILTER1="^Date:\|$BOUNDARY1" fi - echo "$FILE" + if [ ! -z "$BOUNDARY2" ]; then + FILTER2="^Date:\|$BOUNDARY2" + fi + + NOTE1_S="$(mktemp)" + + cat "$NOTE1" | grep -v "$FILTER1" > "$NOTE1_S" + + if cat "$NOTE2" | grep -v "$FILTER2" | cmp -s "$NOTE1_S"; then + rm "$NOTE1_S" + return 0 + else + rm "$NOTE1_S" + return 1 + fi } +new_entry() { + OUTP="$(mktemp)" + input_note "$1" "$OUTP" + + if [ ! -s "$OUTP" ]; then + rm "$OUTP" + return + fi + + NOTE_ID="$(get_header "$OUTP" X-Note-Id)" + + OLD_NOTE="$(find_file_by_id "$NOTE_ID")" + if [ ! -z "$OLD_NOTE" ] && notes_equal "$OLD_NOTE" "$OUTP"; then + return + fi + + remove_notes_by_id "$NOTE_ID" + + UNIX_TIMESTAMP=$(date "+%s") + HOSTNAME=$(hostname -s) + FILENAME="$UNIX_TIMESTAMP.${PID}_1.${HOSTNAME}:2,S" + mv "$OUTP" "$BASEDIR/cur/$FILENAME" +} + + edit_entry() { ID="$1" - FILENAME="$(find_file_by_id "$ID")" + FILENAME="$(assert_find_file_by_id "$ID")" DIR="$CACHE_DIR/$ID" @@ -419,7 +483,7 @@ edit_entry() { RESULT=$(mktemp) pack_mime "$DIR" "$RESULT" - if ! cmp -s "$RESULT" "$FILENAME"; then + if ! notes_equal "$RESULT" "$FILENAME"; then DEST_FILENAME="$UNIX_TIMESTAMP.${PID}_1.${HOSTNAME}:2,S" mv "$RESULT" "$BASEDIR/cur/$DEST_FILENAME" rm "$FILENAME" @@ -460,7 +524,7 @@ list_entries() { export_note() { ID="$1" - FILENAME="$(find_file_by_id "$ID")" + FILENAME="$(assert_find_file_by_id "$ID")" DIR="$2" unpack_mime "$FILENAME" "$DIR" diff --git a/test.sh b/test.sh index 55a3974..eac14ac 100755 --- a/test.sh +++ b/test.sh @@ -30,9 +30,13 @@ BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" export BASE_DIR cd "$BASE_DIR" +TESTNAME="$1" RESULT=0 testcase() { + if [ ! -z "$TESTNAME" ] && [[ "$TESTNAME" != "$1" ]]; then + return + fi TMP=$(mktemp -d) cd "$TMP" NOTES_SH_BASEDIR="$(pwd)/notes" @@ -166,6 +170,47 @@ edit_note() { assert 'echo "$OUTPUT" | grep -o line2' "line2" } +edit_note_add_file() { + "$BASE_DIR/notes.sh" -n <<- EOF + Subject: header1 + + line1 + EOF + NOTE_ID="$(cat "$(pwd)/notes/cur"/* | grep X-Note-Id | cut -d ' ' -f 2)" + + cat > "$(pwd)/editor.sh" <<- EOF + #!/bin/bash + FILENAME="\$1" + echo "newfile" > "\$FILENAME.txt" + EOF + chmod a+x "$(pwd)/editor.sh" + export EDITOR="$(pwd)/editor.sh" + + "$BASE_DIR/notes.sh" -e "$NOTE_ID" + + OUTPUT="$(cat "$(pwd)/notes/cur"/*)" + assert 'echo "$OUTPUT" | grep -o line1' "line1" + assert 'echo "$OUTPUT" | grep -o newfile' "newfile" +} + +edit_note_no_modifications() { + "$BASE_DIR/notes.sh" -n <<- EOF + Subject: header1 + + line1 + EOF + NOTE_ID="$(cat "$(pwd)/notes/cur"/* | grep X-Note-Id | cut -d ' ' -f 2)" + NOTE_FILE="$(ls "$(pwd)/notes/cur"/*)" + + cat > "$(pwd)/editor.sh" <<- EOF + #!/bin/bash + EOF + chmod a+x "$(pwd)/editor.sh" + export EDITOR="$(pwd)/editor.sh" + + "$BASE_DIR/notes.sh" -e "$NOTE_ID" + assert 'ls "$(pwd)/notes/cur"/*' "$NOTE_FILE" +} resume_editing() { "$BASE_DIR/notes.sh" -n <<- EOF Subject: header1 @@ -370,12 +415,54 @@ import_export() { assert 'cat "$TMP/outpdir/file.txt"' "$(cat "$TMP/inpdir/file.txt")" } +new_note_overwrite_without_modifications() { + "$BASE_DIR/notes.sh" -n <<- EOF + Subject: header1 + X-Date: 2021-05-30T18:25:38Z + + line1 + EOF + + NOTE_FILE="$(ls "$(pwd)/notes/cur"/*)" + mv "$NOTE_FILE" "$NOTE_FILE.keep" + NOTE_ID="$(cat "$(pwd)/notes/cur"/* | grep X-Note-Id | cut -d ' ' -f 2)" + + mkdir "$TMP/outdir" + "$BASE_DIR/notes.sh" -E "$NOTE_ID" "$TMP/outdir" + + "$BASE_DIR/notes.sh" -n "$TMP/outdir" + + assert 'ls "$(pwd)/notes/cur"/*' "$NOTE_FILE.keep" +} + +new_note_overwrite_with_modifications() { + "$BASE_DIR/notes.sh" -n <<- EOF + Subject: header1 + + line1 + EOF + + NOTE_FILE="$(ls "$(pwd)/notes/cur"/*)" + NOTE_ID="$(cat "$(pwd)/notes/cur"/* | grep X-Note-Id | cut -d ' ' -f 2)" + + mkdir "$TMP/outdir" + "$BASE_DIR/notes.sh" -E "$NOTE_ID" "$TMP/outdir" + + echo "line2" >> "$TMP/outdir/note.md" + + "$BASE_DIR/notes.sh" -n "$TMP/outdir" + + assert 'cat "$(pwd)/notes/cur"/* | grep line2' "line2" +} + testcase new_note_from_stdin testcase new_note_from_file testcase new_note_from_dir testcase list_notes testcase export_note testcase edit_note +testcase edit_note_add_file +testcase edit_note_no_modifications testcase resume_editing testcase pack_multipart testcase pack_multipart_binary @@ -383,6 +470,8 @@ testcase existing_headers testcase no_headers testcase no_headers_dir testcase import_export +testcase new_note_overwrite_without_modifications +testcase new_note_overwrite_with_modifications if [[ "$RESULT" == "0" ]]; then echo "All tests passed."