diff --git a/src/cli.cc b/src/cli.cc index 0e2ffa1..0c91263 100644 --- a/src/cli.cc +++ b/src/cli.cc @@ -283,25 +283,39 @@ static ot::status edit_tags_interactively(ot::opus_tags& tags, const std::option return {ot::st::error, "No editor specified in environment variable VISUAL or EDITOR."}; + // Building the temporary tags file. std::string tags_path = base_path.value_or("tags") + ".XXXXXX.opustags"; int fd = mkstemps(const_cast(tags_path.data()), 9); FILE* tags_file; if (fd == -1 || (tags_file = fdopen(fd, "w")) == nullptr) return {ot::st::standard_error, "Could not open '" + tags_path + "': " + strerror(errno)}; - ot::print_comments(tags.comments, tags_file); - fputs("\n" - "# Edit these tags to your liking and close your editor to apply them.\n" - "# If you delete all the tags however, tag edition will be cancelled.\n", - tags_file); if (fclose(tags_file) != 0) - return {ot::st::standard_error, "fclose error: "s + strerror(errno)}; + return {ot::st::standard_error, tags_path + ": fclose error: "s + strerror(errno)}; - ot::status rc = ot::run_editor(editor, tags_path.c_str()); - if (rc != ot::st::ok) + // Spawn the editor, and watch the modification timestamps. + ot::status rc; + timespec before, after; + if ((rc = ot::get_file_timestamp(tags_path.c_str(), before)) != ot::st::ok) return rc; + ot::status editor_rc = ot::run_editor(editor, tags_path.c_str()); + if ((rc = ot::get_file_timestamp(tags_path.c_str(), after)) != ot::st::ok) + return rc; // probably because the file was deleted + bool modified = (before.tv_sec != after.tv_sec || before.tv_nsec != after.tv_nsec); + if (editor_rc != ot::st::ok) { + if (modified) + fprintf(stderr, "warning: Leaving %s on the disk.\n", tags_path.c_str()); + else + remove(tags_path.c_str()); + return rc; + } else if (!modified) { + remove(tags_path.c_str()); + fputs("Cancelling edition because the tags file was not modified.\n", stderr); + return ot::st::cancel; + } + // Applying the new tags. tags_file = fopen(tags_path.c_str(), "r"); if (tags_file == nullptr) return {ot::st::standard_error, "Error opening " + tags_path + ": " + strerror(errno)}; @@ -311,11 +325,6 @@ static ot::status edit_tags_interactively(ot::opus_tags& tags, const std::option } fclose(tags_file); - if (tags.comments.size() == 0) { - remove(tags_path.c_str()); // it’s empty anyway - return {ot::st::error, "Tag edition was cancelled because all the tags were deleted."}; - } - // Remove the temporary tags file only on success, because unlike the // partial Ogg file that is irrecoverable, the edited tags file // contains user data, so let’s leave users a chance to recover it. diff --git a/src/opustags.h b/src/opustags.h index fa9fc5d..82f80c0 100644 --- a/src/opustags.h +++ b/src/opustags.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -58,6 +59,7 @@ enum class st { error, standard_error, /**< Error raised by the C standard library. */ int_overflow, + cancel, /* System */ badly_encoded, information_lost, @@ -172,6 +174,12 @@ private: */ ot::status run_editor(const char* editor, const char* path); +/** + * Return the specified path’s mtime, i.e. the last data modification + * timestamp. + */ +ot::status get_file_timestamp(const char* path, timespec& mtime); + /** \} */ /***********************************************************************************************//** diff --git a/src/system.cc b/src/system.cc index 145a893..a6f3fd4 100644 --- a/src/system.cc +++ b/src/system.cc @@ -177,3 +177,12 @@ ot::status ot::run_editor(const char* editor, const char* path) return st::ok; } + +ot::status ot::get_file_timestamp(const char* path, timespec& mtime) +{ + struct stat st; + if (stat(path, &st) == -1) + return {st::standard_error, path + ": stat error: "s + strerror(errno)}; + mtime = st.st_mtim; // more precise than st_mtime + return st::ok; +} diff --git a/t/CMakeLists.txt b/t/CMakeLists.txt index f1fafa2..42d6703 100644 --- a/t/CMakeLists.txt +++ b/t/CMakeLists.txt @@ -14,7 +14,6 @@ add_executable(oggdump EXCLUDE_FROM_ALL oggdump.cc) target_link_libraries(oggdump ot) configure_file(gobble.opus . COPYONLY) -configure_file(emptier . COPYONLY) add_custom_target( check diff --git a/t/emptier b/t/emptier deleted file mode 100755 index f463bfd..0000000 --- a/t/emptier +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -cat > "$1" << EOF - -# Here’s a file with nothing but comments and newlines. - -EOF diff --git a/t/opustags.t b/t/opustags.t index e81f159..9308ef1 100755 --- a/t/opustags.t +++ b/t/opustags.t @@ -230,14 +230,12 @@ $ENV{EDITOR} = 'sed -i -e y/a/A/'; is_deeply(opustags(qw(gobble.opus --add artist=aaah -o screaming.opus -e)), ['', '', 0], 'edit a file with EDITOR'); is(md5('screaming.opus'), '682229df1df6b0ca147e2778737d449e', 'the tags were modified'); -$ENV{EDITOR} = './emptier'; -is_deeply(opustags(qw(--add mystery=1 -i screaming.opus -e)), ['', "screaming.opus: error: Tag edition was cancelled because all the tags were deleted.\n", 256], 'edit a file with EDITOR'); +$ENV{EDITOR} = 'true'; +is_deeply(opustags(qw(--add mystery=1 -i screaming.opus -e)), ['', "Cancelling edition because the tags file was not modified.\n", 256], 'close -e without saving'); is(md5('screaming.opus'), '682229df1df6b0ca147e2778737d449e', 'the tags were not modified'); -$ENV{EDITOR} = ''; unlink('screaming.opus'); - #################################################################################################### # Test muxed streams