diff --git a/CHANGELOG.md b/CHANGELOG.md index b66db73c3..b0733d7c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ SPDX-License-Identifier: GPL-3.0-or-later ### Added +- api: add function file_compare - irc: add support of strikethrough text in IRC messages ([#2248](https://github.com/weechat/weechat/issues/2248)) - buflist: add variables `${number_zero}` and `${number_zero2}` (zero-padded buffer number) - tests: add fuzz testing ([#1462](https://github.com/weechat/weechat/issues/1462)) diff --git a/doc/en/weechat_plugin_api.en.adoc b/doc/en/weechat_plugin_api.en.adoc index 2671a76aa..f4a69c085 100644 --- a/doc/en/weechat_plugin_api.en.adoc +++ b/doc/en/weechat_plugin_api.en.adoc @@ -4624,6 +4624,43 @@ if (weechat_file_compress ("/tmp/test.txt", "/tmp/test.txt.zst", "zstd", 50)) [NOTE] This function is not available in scripting API. +==== file_compare + +_WeeChat ≥ 4.7.0._ + +Compare the content of two files. + +Prototype: + +[source,c] +---- +int weechat_file_compare (const char *filename1, const char *filename2); +---- + +Arguments: + +* _filename1_: first file to compare +* _filename2_: second file to compare + +Return value: + +* 0: both files have same content +* 1: content is different +* 2: error (file not found or read error) + +C example: + +[source,c] +---- +if (weechat_file_compare ("/tmp/test.txt", "/tmp/test2.txt") == 0) +{ + /* same content */ +} +---- + +[NOTE] +This function is not available in scripting API. + [[util]] === Util diff --git a/doc/fr/weechat_plugin_api.fr.adoc b/doc/fr/weechat_plugin_api.fr.adoc index 71e1397a5..f866def55 100644 --- a/doc/fr/weechat_plugin_api.fr.adoc +++ b/doc/fr/weechat_plugin_api.fr.adoc @@ -4703,6 +4703,43 @@ if (weechat_file_compress ("/tmp/test.txt", "/tmp/test.txt.zst", "zstd", 50)) [NOTE] Cette fonction n'est pas disponible dans l'API script. +==== file_compare + +_WeeChat ≥ 4.7.0._ + +Compare the content of two files. + +Prototype: + +[source,c] +---- +int weechat_file_compare (const char *filename1, const char *filename2); +---- + +Arguments: + +* _filename1_: first file to compare +* _filename2_: second file to compare + +Return value: + +* 0: both files have same content +* 1: content is different +* 2: error (file not found or read error) + +C example: + +[source,c] +---- +if (weechat_file_compare ("/tmp/test.txt", "/tmp/test2.txt") == 0) +{ + /* same content */ +} +---- + +[NOTE] +This function is not available in scripting API. + [[util]] === Util diff --git a/doc/it/weechat_plugin_api.it.adoc b/doc/it/weechat_plugin_api.it.adoc index 30c9a74b8..00ac420ea 100644 --- a/doc/it/weechat_plugin_api.it.adoc +++ b/doc/it/weechat_plugin_api.it.adoc @@ -4831,6 +4831,44 @@ if (weechat_file_compress ("/tmp/test.txt", "/tmp/test.txt.zst", "zstd", 50)) [NOTE] Questa funzione non è disponibile nelle API per lo scripting. +// TRANSLATION MISSING +==== file_compare + +_WeeChat ≥ 4.7.0._ + +Compare the content of two files. + +Prototipo: + +[source,c] +---- +int weechat_file_compare (const char *filename1, const char *filename2); +---- + +Argomenti: + +* _filename1_: first file to compare +* _filename2_: second file to compare + +Valore restituito: + +* 0: both files have same content +* 1: content is different +* 2: error (file not found or read error) + +Esempio in C: + +[source,c] +---- +if (weechat_file_compare ("/tmp/test.txt", "/tmp/test2.txt") == 0) +{ + /* same content */ +} +---- + +[NOTE] +Questa funzione non è disponibile nelle API per lo scripting. + [[util]] === Utilità diff --git a/doc/ja/weechat_plugin_api.ja.adoc b/doc/ja/weechat_plugin_api.ja.adoc index 1f913ac1a..154829bce 100644 --- a/doc/ja/weechat_plugin_api.ja.adoc +++ b/doc/ja/weechat_plugin_api.ja.adoc @@ -4756,6 +4756,44 @@ if (weechat_file_compress ("/tmp/test.txt", "/tmp/test.txt.zst", "zstd", 50)) [NOTE] スクリプト API ではこの関数を利用できません。 +// TRANSLATION MISSING +==== file_compare + +_WeeChat ≥ 4.7.0._ + +Compare the content of two files. + +プロトタイプ: + +[source,c] +---- +int weechat_file_compare (const char *filename1, const char *filename2); +---- + +引数: + +* _filename1_: first file to compare +* _filename2_: second file to compare + +戻り値: + +* 0: both files have same content +* 1: content is different +* 2: error (file not found or read error) + +C 言語での使用例: + +[source,c] +---- +if (weechat_file_compare ("/tmp/test.txt", "/tmp/test2.txt") == 0) +{ + /* same content */ +} +---- + +[NOTE] +スクリプト API ではこの関数を利用できません。 + [[util]] === ユーティリティ diff --git a/doc/sr/weechat_plugin_api.sr.adoc b/doc/sr/weechat_plugin_api.sr.adoc index 09d23d689..b6fcabd45 100644 --- a/doc/sr/weechat_plugin_api.sr.adoc +++ b/doc/sr/weechat_plugin_api.sr.adoc @@ -4484,6 +4484,44 @@ if (weechat_file_compress ("/tmp/test.txt", "/tmp/test.txt.zst", "zstd", 50)) [NOTE] Ова функција није доступна у API скриптовања. +// TRANSLATION MISSING +==== file_compare + +_WeeChat ≥ 4.7.0._ + +Compare the content of two files. + +Прототип: + +[source,c] +---- +int weechat_file_compare (const char *filename1, const char *filename2); +---- + +Аргументи: + +* _filename1_: first file to compare +* _filename2_: second file to compare + +Повратна вредност: + +* 0: both files have same content +* 1: content is different +* 2: error (file not found or read error) + +C пример: + +[source,c] +---- +if (weechat_file_compare ("/tmp/test.txt", "/tmp/test2.txt") == 0) +{ + /* same content */ +} +---- + +[NOTE] +Ова функција није доступна у API скриптовања. + [[util]] === Алати diff --git a/src/core/core-dir.c b/src/core/core-dir.c index 592e44dd0..6fada9787 100644 --- a/src/core/core-dir.c +++ b/src/core/core-dir.c @@ -1431,3 +1431,127 @@ dir_file_compress (const char *filename_input, return 0; } } + +/* + * Compares the content of two files (the attributes of the files like the + * permissions or timestamp are ignored). + * + * Comparison is done like this: + * 1. if sizes are different, return 1 (different content) + * 2. if sizes are the same, read both files until a difference is found + * + * Returns: + * 0: both files exist and their content is exactly the same + * 1: content is different + * 2: other error (file not found, read error) + */ + +int +dir_file_compare (const char *filename1, const char *filename2) +{ + char buffer1[4096], buffer2[4096]; + FILE *f1, *f2; + struct stat st; + off_t size1, size2; + size_t count1, count2; + int rc, eof1, eof2; + + if (!filename1 || !filename1[0] || !filename2 || !filename2[0]) + return 2; + + rc = 1; + + f1 = NULL; + f2 = NULL; + + /* get size of first file */ + if (access (filename1, R_OK) != 0) + { + rc = 2; + goto end; + } + if (stat (filename1, &st) != 0) + { + rc = 2; + goto end; + } + size1 = st.st_size; + + /* get size of second file */ + if (access (filename2, R_OK) != 0) + { + rc = 2; + goto end; + } + if (stat (filename2, &st) != 0) + { + rc = 2; + goto end; + } + size2 = st.st_size; + + if (size1 != size2) + { + /* different size, so different content */ + rc = 1; + goto end; + } + + f1 = fopen (filename1, "r"); + if (!f1) + { + rc = 2; + goto end; + } + f2 = fopen (filename2, "r"); + if (!f2) + { + rc = 2; + goto end; + } + + while (1) + { + eof1 = feof (f1); + eof2 = feof (f2); + if ((eof1 && !eof2) || (!eof1 && eof2)) + { + /* different content */ + rc = 1; + goto end; + } + if (eof1 && eof2) + { + /* same content */ + rc = 0; + goto end; + } + count1 = fread (buffer1, sizeof (char), sizeof (buffer1), f1); + count2 = fread (buffer2, sizeof (char), sizeof (buffer2), f2); + if (count1 != count2) + { + /* different content */ + rc = 1; + goto end; + } + if ((count1 == 0) || (count2 == 0)) + { + /* read error */ + rc = 2; + goto end; + } + if (memcmp (buffer1, buffer2, count1) != 0) + { + /* different content */ + rc = 1; + goto end; + } + } + +end: + if (f1) + fclose (f1); + if (f2) + fclose (f2); + return rc; +} diff --git a/src/core/core-dir.h b/src/core/core-dir.h index e27ac8e32..1ca146399 100644 --- a/src/core/core-dir.h +++ b/src/core/core-dir.h @@ -41,5 +41,6 @@ extern char *dir_file_get_content (const char *filename); extern int dir_file_copy (const char *from, const char *to); extern int dir_file_compress (const char *from, const char *to, const char *compressor, int compression_level); +extern int dir_file_compare (const char *filename1, const char *filename2); #endif /* WEECHAT_DIR_H */ diff --git a/src/plugins/plugin.c b/src/plugins/plugin.c index ccde523b5..c23eb2794 100644 --- a/src/plugins/plugin.c +++ b/src/plugins/plugin.c @@ -693,6 +693,7 @@ plugin_load (const char *filename, int init_plugin, int argc, char **argv) new_plugin->file_get_content = &dir_file_get_content; new_plugin->file_copy = &dir_file_copy; new_plugin->file_compress = &dir_file_compress; + new_plugin->file_compare = &dir_file_compare; new_plugin->util_timeval_cmp = &util_timeval_cmp; new_plugin->util_timeval_diff = &util_timeval_diff; diff --git a/src/plugins/weechat-plugin.h b/src/plugins/weechat-plugin.h index 590841ee7..edb11f888 100644 --- a/src/plugins/weechat-plugin.h +++ b/src/plugins/weechat-plugin.h @@ -76,7 +76,7 @@ struct t_weelist_item; * please change the date with current one; for a second change at same * date, increment the 01, otherwise please keep 01. */ -#define WEECHAT_PLUGIN_API_VERSION "20250215-01" +#define WEECHAT_PLUGIN_API_VERSION "20250507-01" /* macros for defining plugin infos */ #define WEECHAT_PLUGIN_NAME(__name) \ @@ -457,6 +457,7 @@ struct t_weechat_plugin int (*file_copy) (const char *from, const char *to); int (*file_compress) (const char *from, const char *to, const char *compressor, int compression_level); + int (*file_compare) (const char *filename1, const char *filename2); /* util */ int (*util_timeval_cmp) (struct timeval *tv1, struct timeval *tv2); @@ -1528,6 +1529,8 @@ extern int weechat_plugin_end (struct t_weechat_plugin *plugin); __compression_level) \ (weechat_plugin->file_compress)(__from, __to, __compressor, \ __compression_level) +#define weechat_file_compare(__filename1, __filename2) \ + (weechat_plugin->file_compress)(__filename1, __filename2) /* util */ #define weechat_util_timeval_cmp(__time1, __time2) \ diff --git a/tests/unit/core/test-core-dir.cpp b/tests/unit/core/test-core-dir.cpp index 5c3b63903..86b4b0fd5 100644 --- a/tests/unit/core/test-core-dir.cpp +++ b/tests/unit/core/test-core-dir.cpp @@ -227,16 +227,15 @@ TEST(CoreDir, FileGetContentCopy) LONGS_EQUAL(0, dir_file_copy ("", "")); LONGS_EQUAL(0, dir_file_copy ("/tmp/does/not/exist.xyz", "/tmp/test.txt")); - path1 = string_eval_path_home ("${weechat_data_dir}/test_file.txt", + path1 = string_eval_path_home ("${weechat_data_dir}/test_content_file1.txt", NULL, NULL, NULL); - path2 = string_eval_path_home ("${weechat_data_dir}/test_file2.txt", + path2 = string_eval_path_home ("${weechat_data_dir}/test_content_file2.txt", NULL, NULL, NULL); /* small file */ length = strlen (content_small); f = fopen (path1, "wb"); - CHECK(f); - LONGS_EQUAL(length, fwrite (content_small, 1, length, f)); + fwrite (content_small, 1, length, f); fclose (f); LONGS_EQUAL(1, dir_file_copy (path1, path2)); content_read1 = dir_file_get_content (path1); @@ -259,8 +258,7 @@ TEST(CoreDir, FileGetContentCopy) } content[26 * 5001] = '\0'; f = fopen (path1, "wb"); - CHECK(f); - LONGS_EQUAL(length, fwrite (content, 1, length, f)); + fwrite (content, 1, length, f); fclose (f); LONGS_EQUAL(1, dir_file_copy (path1, path2)); content_read1 = dir_file_get_content (path1); @@ -303,3 +301,71 @@ TEST(CoreDir, FileCompress) { /* TODO: write tests */ } + +/* + * Tests functions: + * dir_file_compare + */ + +TEST(CoreDir, FileCompare) +{ + char *path1, *path2, *content; + const char *content_small = "line 1\nline 2\nend"; + int length, i; + FILE *f; + + /* file not found */ + LONGS_EQUAL(2, dir_file_compare (NULL, NULL)); + LONGS_EQUAL(2, dir_file_compare ("", "")); + LONGS_EQUAL(2, dir_file_compare ("/tmp/does/not/exist1.xyz", "/tmp/does/not/exist2.xyz")); + + path1 = string_eval_path_home ("${weechat_data_dir}/test_compare_file1.txt", + NULL, NULL, NULL); + path2 = string_eval_path_home ("${weechat_data_dir}/test_compare_file2.txt", + NULL, NULL, NULL); + + /* small file */ + length = strlen (content_small); + f = fopen (path1, "wb"); + fwrite (content_small, 1, length, f); + fclose (f); + f = fopen (path2, "wb"); + fwrite (content_small, 1, length, f); + fclose (f); + LONGS_EQUAL(0, dir_file_compare (path1, path2)); + f = fopen (path1, "ab"); + fwrite ("A", 1, 1, f); + fclose (f); + f = fopen (path2, "ab"); + fwrite ("B", 1, 1, f); + fclose (f); + LONGS_EQUAL(1, dir_file_compare (path1, path2)); + + /* bigger file: 26 lines of 5000 bytes + 1 byte */ + length = 26 * 5001; + content = (char *)malloc (length + 1); + CHECK(content); + for (i = 0; i < 26; i++) + { + memset (content + (i * 5001), 'a' + i, 5000); + content[(i * 5001) + 5000] = '\n'; + } + content[26 * 5001] = '\0'; + f = fopen (path1, "wb"); + fwrite (content, 1, length, f); + fclose (f); + f = fopen (path2, "wb"); + fwrite (content, 1, length, f); + fclose (f); + LONGS_EQUAL(0, dir_file_compare (path1, path2)); + f = fopen (path1, "ab"); + fwrite ("A", 1, 1, f); + fclose (f); + f = fopen (path2, "ab"); + fwrite ("B", 1, 1, f); + fclose (f); + LONGS_EQUAL(1, dir_file_compare (path1, path2)); + + unlink (path1); + unlink (path2); +}