diff --git a/meson.build b/meson.build index e9d06431d..25d6e469f 100644 --- a/meson.build +++ b/meson.build @@ -56,6 +56,10 @@ add_global_arguments('-Wno-overlength-strings', language : 'c') add_global_arguments('-Wno-error=deprecated-declarations', language : 'cpp') # Project requirements +# code_sources is all human-written geeqie C/C++ code +code_sources = [] +# project_sources will include code_sources, as well as auto-generated code, +# third-party code, and some other code-like dependencies. project_sources = [] gnome = import('gnome') thread_dep = dependency('threads') @@ -729,7 +733,7 @@ if running_from_git if clang_tidy_exe.found() git_exe = find_program('git', required : true) - foreach source_file : main_sources + pan_view_sources + view_file_sources + foreach source_file : code_sources if fs.name(source_file).endswith('.cc') source_file_name = fs.name(source_file) config_file = join_paths(meson.project_source_root(), '.clang-tidy') @@ -749,7 +753,7 @@ endif # Single value enum checks enum_check_sh = find_program('enum-check.sh', dirs : scriptsdir, required : true) if enum_check_sh.found() - foreach source_file : main_sources + pan_view_sources + view_file_sources + foreach source_file : code_sources source_file_name = fs.name(source_file) test('Single Value enum_ ' + source_file_name, enum_check_sh, args : [source_file], timeout : 100, suite : 'analysis') endforeach @@ -762,7 +766,7 @@ endif # Debug statement checks debug_check_sh = find_program('debug-check.sh', dirs : scriptsdir, required : true) if debug_check_sh.found() - foreach source_file : main_sources + pan_view_sources + view_file_sources + foreach source_file : code_sources source_file_name = fs.name(source_file) if (source_file_name != 'debug.h') test('Debug Statements_ ' + source_file_name, debug_check_sh, args : [source_file], timeout : 100, suite : 'analysis') @@ -777,7 +781,7 @@ endif # Temporary comments checks tmp_comments_check_sh = find_program('temporary-comments-check.sh', dirs : scriptsdir, required : true) if tmp_comments_check_sh.found() - foreach source_file : main_sources + pan_view_sources + view_file_sources + foreach source_file : code_sources source_file_name = fs.name(source_file) if (source_file_name != 'debug.h') test('Temporary Comments_ ' + source_file_name, tmp_comments_check_sh, args : [source_file], timeout : 100, suite : 'analysis') @@ -794,7 +798,7 @@ gtk4_migration_check_sh = find_program('gtk4-migration-regression-check.sh', dir if gtk4_migration_check_sh.found() compat_cc = join_paths(meson.project_source_root(), 'src', 'compat.cc') compat_h = join_paths(meson.project_source_root(), 'src', 'compat.h') - foreach source_file : main_sources + pan_view_sources + view_file_sources + foreach source_file : code_sources source_file_name = fs.name(source_file) if (source_file_name != 'debug.h') test('GTK4 migration_ ' + source_file_name, gtk4_migration_check_sh, args : [source_file, compat_cc, compat_h], timeout : 100, suite : 'analysis') @@ -809,7 +813,7 @@ endif # Untranslated text checks untranslated_text_sh = find_program('untranslated-text.sh', dirs : scriptsdir, required : true) if untranslated_text_sh.found() - foreach source_file : main_sources + pan_view_sources + view_file_sources + foreach source_file : code_sources if fs.name(source_file).endswith('.cc') source_file_name = fs.name(source_file) test('Untranslated Text_ ' + source_file_name, untranslated_text_sh, args : [source_file], timeout : 200, suite : 'analysis') diff --git a/po/POTFILES b/po/POTFILES index f6cba4026..2eb33868f 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -1,6 +1,7 @@ org.geeqie.Geeqie.appdata.xml.in org.geeqie.Geeqie.desktop.in plugins/camera-import/org.geeqie.camera-import.desktop.in +plugins/exif-datetime-to-file/org.geeqie.exif-datetime-to-file.desktop.in plugins/export-jpeg/org.geeqie.export-jpeg.desktop.in plugins/image-crop/org.geeqie.image-crop.desktop.in plugins/open-with/org.geeqie.open-with.desktop.in @@ -41,6 +42,7 @@ src/exif-common.cc src/exiv2.cc src/filecache.cc src/filedata.cc +src/filedata/filedata.cc src/filefilter.cc src/format-canon.cc src/format-fuji.cc @@ -109,6 +111,7 @@ src/secure-save.cc src/shortcuts.cc src/similar.cc src/slideshow.cc +src/tests/pixbuf-util.cc src/third-party/whereami.cc src/third-party/zonedetect.cc src/thumb.cc diff --git a/po/ar.po b/po/ar.po index 4ed674e09..a6c6fe807 100644 --- a/po/ar.po +++ b/po/ar.po @@ -2664,31 +2664,31 @@ msgstr "تاريخ الملف" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d بايت" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f ك" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f ميجابايت" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f جيجابايت" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "" @@ -2696,54 +2696,54 @@ msgstr "" "%s \n" " موجود بالفعل." -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 #, fuzzy msgid "no read permission" msgstr "(غير مصرّح للقراءة) %s بايتات" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 #, fuzzy msgid "source and destination are the same" msgstr "الملف المصدر هو الملف المهدف. تم إلغاء العملية." -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 #, fuzzy msgid "source and destination have different extension" msgstr "الملف المصدر هو الملف المهدف. تم إلغاء العملية." -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "فشل عملية حفظ قائمة تاريخ إستخدام البرنامج إلى %s \n" diff --git a/po/be.po b/po/be.po index 30060ffed..5666acb74 100644 --- a/po/be.po +++ b/po/be.po @@ -2604,31 +2604,31 @@ msgstr "Старонка %d" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d байтаў" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f K" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f MB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f GB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "" @@ -2636,54 +2636,54 @@ msgstr "" "%s\n" "ужо існуе." -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 #, fuzzy msgid "no read permission" msgstr "(няма правоў для чытання) %s байтаў" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 #, fuzzy msgid "source and destination are the same" msgstr "Крыніца і пазначэнне аднолькавыя, аперацыя перарвана" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 #, fuzzy msgid "source and destination have different extension" msgstr "Крыніца і пазначэнне аднолькавыя, аперацыя перарвана" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Немагчыма запісаць спісы гісторыі ў: %s\n" diff --git a/po/bg.po b/po/bg.po index 70b7e2e03..ee3fb9c45 100644 --- a/po/bg.po +++ b/po/bg.po @@ -2646,31 +2646,31 @@ msgstr "Страница %d" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d байта" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f K" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f МБ" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f ГБ" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "" @@ -2678,54 +2678,54 @@ msgstr "" "%s\n" "вече съществува." -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 #, fuzzy msgid "no read permission" msgstr "(няма право за четене) %s байта" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 #, fuzzy msgid "source and destination are the same" msgstr "Източникът и целта са еднакви, операцията е прекъсната." -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 #, fuzzy msgid "source and destination have different extension" msgstr "Източникът и целта са еднакви, операцията е прекъсната." -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Невъзможно е да бъдат записани списъците с историята в: %s\n" diff --git a/po/ca.po b/po/ca.po index 796f2deda..73b4cb667 100644 --- a/po/ca.po +++ b/po/ca.po @@ -2513,79 +2513,79 @@ msgstr "Pàgina número" msgid "Lens" msgstr "Lents" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d bytes" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, c-format msgid "%.1f KiB" msgstr "%.1f KB" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, c-format msgid "%.1f MiB" msgstr "%.1f MB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, c-format msgid "%.1f GiB" msgstr "%.1f GB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "el fitxer o el directori no existeix" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "el destí ja existeix" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "el destí no es pot sobreescriure" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "no es pot escriure al directori destí" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "el directori de destí no existeix" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "no es pot escriure al directori font" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "no teniu permís de lectura" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "el fitxer és de només lectura" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "el destí ja existeix i es sobreescriurà" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "l'origen i el destí son els mateixos" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "l'origen i el destí tenen extensió diferent" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "hi ha canvis a les metadades del fitxer que no s'han desat" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "un altre fitxer del destí té el mateix nom" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Error: No és possible escriure la llista de marques a: %s\n" diff --git a/po/cs.po b/po/cs.po index e3c9dc69e..c420bf015 100644 --- a/po/cs.po +++ b/po/cs.po @@ -2490,79 +2490,79 @@ msgstr "Strana č." msgid "Lens" msgstr "Objektiv" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "bajtů: %d" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, c-format msgid "%.1f KiB" msgstr "%.1f KiB" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, c-format msgid "%.1f MiB" msgstr "%.1f MiB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, c-format msgid "%.1f GiB" msgstr "%.1f GiB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "soubor nebo adresář neexistuje" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "cílový soubor již existuje" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "cílový soubor nemůže být přepsán" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "cílový adresář je jen pro čtení" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "cílový adresář neexistuje" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "cílový adresář je jen pro čtení" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "chybí právo ke čtení" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "soubor je jen pro čtení" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "cílový soubor již existuje a bude přepsán" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "zdroj a cíl jsou totožné" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "zdroj a cíl mají různé přípony souboru" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "metadata pro soubor čekají na zapsání" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "jiný cílový soubor má stejné jméno" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Chyba: Nelze zapsat seznam značek do: %s\n" diff --git a/po/da.po b/po/da.po index a39f093a4..5eb569fbc 100644 --- a/po/da.po +++ b/po/da.po @@ -2693,80 +2693,80 @@ msgstr "Filter:" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, fuzzy, c-format msgid "%d bytes" msgstr "%d filer" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, c-format msgid "%.1f KiB" msgstr "" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, c-format msgid "%.1f MiB" msgstr "" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, c-format msgid "%.1f GiB" msgstr "" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "Fil ved navn %s findes allerede." -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "" diff --git a/po/de.po b/po/de.po index aded95f2f..a4daea406 100644 --- a/po/de.po +++ b/po/de.po @@ -2526,79 +2526,79 @@ msgstr "Seite Nr." msgid "Lens" msgstr "Linse" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d Byte" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f kB" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f MB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f GB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "Datei oder Verzeichnis existiert nicht" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "Ziel existiert bereits" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "Ziel kann nicht überschrieben werden" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "Zielverzeichnis ist nicht schreibbar" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "Zielverzeichnis existiert nicht" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "Quellverzeichnis ist nicht schreibbar" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "keine Leserechte" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "Datei ist schreibgeschützt" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "Ziel existiert bereits und wird überschrieben" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "Quelle und Ziel sind gleich" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "Quelle und Ziel haben unterschiedliche Dateierweiterung" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "es gibt ungespeicherte Metadatenänderungen für die Datei" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "Eine andere Zieldatei hat den selben Dateinamen" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Fehler: Markierungslisten können nicht geschrieben werden an: %s\n" diff --git a/po/el.po b/po/el.po index 5687e6189..02e16b86f 100644 --- a/po/el.po +++ b/po/el.po @@ -2549,79 +2549,79 @@ msgstr "Σελίδα %d" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d bytes" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f K" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f MB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f GB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "το αρχείο ή ο κατάλογος δεν υπάρχει" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "ο προορισμός υπάρχει ήδη" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "ο προορισμός δεν μπορεί να αντικατασταθεί" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "ο κατάλογος προορισμού δεν είναι εγγράψιμος" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "ο κατάλογος προορισμού δεν υπάρχει" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "ο πηγαίος κατάλογος δεν είναι εγγράψιμος" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "χωρίς δικαιώματα ανάγνωσης" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "το αρχείο είναι μόνο για ανάγνωση" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "ο προορισμός υπάρχει ήδη και θα αντικατασταθεί" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "η πηγή και ο προορισμός είναι τα ίδια" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "η πηγή και ο προορισμός έχουν διαφορετική επέκταση" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "υπάρχουν μη αποθηκευμένα μεταδεδομένα για το αρχείο" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "ένα άλλο αρχείου προορισμού έχει το ίδιο όνομα αρχείου" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Αδυναμία εγγραφής κατάστιχων ιστορικού στο: %s\n" diff --git a/po/en_GB.po b/po/en_GB.po index f546d0fa0..6b4f39669 100644 --- a/po/en_GB.po +++ b/po/en_GB.po @@ -2505,79 +2505,79 @@ msgstr "Page no." msgid "Lens" msgstr "Lens" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d bytes" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, c-format msgid "%.1f KiB" msgstr "%.1f KiB" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, c-format msgid "%.1f MiB" msgstr "%.1f MiB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, c-format msgid "%.1f GiB" msgstr "%.1f GiB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "file or directory does not exist" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "destination already exists" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "destination can't be overwritten" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "destination directory is not writable" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "destination directory does not exist" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "source directory is not writable" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "no read permission" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "file is readonly" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "destination already exists and will be overwritten" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "source and destination are the same" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "source and destination have different extension" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "there are unsaved metadata changes for the file" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "another destination file has the same filename" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Error: Unable to write marks lists to: %s\n" diff --git a/po/eo.po b/po/eo.po index 65e048c03..76b1ed3da 100644 --- a/po/eo.po +++ b/po/eo.po @@ -2646,31 +2646,31 @@ msgstr "Paĝo %d" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d bajtoj" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f K " -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f MB " -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f GB " -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "" @@ -2678,54 +2678,54 @@ msgstr "" "%s\n" "jam ekzistas." -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 #, fuzzy msgid "no read permission" msgstr "(neniu rajtigo por lego) %s bajtoj" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 #, fuzzy msgid "source and destination are the same" msgstr "Fonto kaj celpunkto estas samaj: do ĉi ago estis nuligita." -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 #, fuzzy msgid "source and destination have different extension" msgstr "Fonto kaj celpunkto estas samaj: do ĉi ago estis nuligita." -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Estas neeble skribi historion en: %s\n" diff --git a/po/es.po b/po/es.po index 43e048e3e..85a9371fd 100644 --- a/po/es.po +++ b/po/es.po @@ -2570,79 +2570,79 @@ msgstr "Página %d" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, c-format msgid "%.1f KiB" msgstr "" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, c-format msgid "%.1f MiB" msgstr "" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, c-format msgid "%.1f GiB" msgstr "" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "el archivo o directorio no existe" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "el destino ya existe" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "el destino no puede ser sobrescrito" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "no se puede escribir en el directorio de destino" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "el directorio de destino no existe" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "no se puede escribir en el directorio de origen" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "sin permiso de lectura" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "archivo de sólo lectura" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "el destino ya existe y será sobrescrito" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "origen y destino son iguales" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "origen y destino tienen diferente extensión" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "hay cambios de metadatos sin guardar para el archivo" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "otro archivo tiene el mismo nombre en el destino" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Error: no se pueden escribir listas de historial en: %s\n" diff --git a/po/et.po b/po/et.po index 61e86fce8..45ec9ab5b 100644 --- a/po/et.po +++ b/po/et.po @@ -2693,83 +2693,83 @@ msgstr "Faili kuupäev:" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d baiti" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f K" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f MB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f MB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "Fail nimega %s on juba olemas." -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 #, fuzzy msgid "no read permission" msgstr "(puudub lugemisõigus) %s baiti" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 #, fuzzy msgid "source and destination are the same" msgstr "Allikas ja sihtkoht ühtivad, operatsioonist loobuti." -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 #, fuzzy msgid "source and destination have different extension" msgstr "Allikas ja sihtkoht ühtivad, operatsioonist loobuti." -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Ajaloonimekirjade kirjutamine ebaõnnestus: %s\n" diff --git a/po/eu.po b/po/eu.po index 04c77d269..9338c118b 100644 --- a/po/eu.po +++ b/po/eu.po @@ -2644,31 +2644,31 @@ msgstr "%d orrialdea" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d byte" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f K" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f MB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f GB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "" @@ -2676,54 +2676,54 @@ msgstr "" "%s\n" "badago dagoeneko." -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 #, fuzzy msgid "no read permission" msgstr "(irakurketa baimenik ez) %s byte" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 #, fuzzy msgid "source and destination are the same" msgstr "Jatorria eta helburua berdina da, alde batetara utzia." -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 #, fuzzy msgid "source and destination have different extension" msgstr "Jatorria eta helburua berdina da, alde batetara utzia." -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Ezin da historioa zerrenda hemen idatzi: %s\n" diff --git a/po/fi.po b/po/fi.po index c9d67fc86..2ac84e6f5 100644 --- a/po/fi.po +++ b/po/fi.po @@ -2925,87 +2925,87 @@ msgid "Lens" msgstr "" # src/dupe.cc:67 src/dupe.cc:1449 -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d tavua" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f kt" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f Mt" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f Gt" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" # src/filelist.c:816 -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "Tiedosto %s on jo olemassa." -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 #, fuzzy msgid "no read permission" msgstr "(ei lukuoikeutta) %s tavua" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" # src/utilops.cc:451 -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 #, fuzzy msgid "source and destination are the same" msgstr "Lähde ja kohde ovat samat, toiminto peruutettu." # src/utilops.cc:451 -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 #, fuzzy msgid "source and destination have different extension" msgstr "Lähde ja kohde ovat samat, toiminto peruutettu." -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" # src/ui-tabcomp.cc:171 -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Historialistojen kirjoitus ei onnistunut kohteeseen: %s\n" diff --git a/po/fr.po b/po/fr.po index 238844317..ec6327c44 100644 --- a/po/fr.po +++ b/po/fr.po @@ -2561,80 +2561,80 @@ msgid "Lens" msgstr "" # c-format -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d octets" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f Ko" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f Mo" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f Go" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "fichier ou dossier inexistant" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "la destination existe déjà." -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "la destination ne peut être écrasée" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "impossible d'écrire dans le dossier de destination" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "le dossier de destination n'existe pas" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "impossible d'écrire dans le dossier source " -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "pas de permission en lecture" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "fichier en lecture seule" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "la destination existe déjà et sera écrasée" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "source et destination sont identiques" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "source et destination ont des extensions différentes" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "Il y a des méta-données modifiées non sauvées pour ce fichier" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 #, fuzzy msgid "another destination file has the same filename" msgstr "source et destination sont identiques" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Impossible d'écrire l'historique dans : %s\n" diff --git a/po/hu.po b/po/hu.po index 06b75ce63..4201d9069 100644 --- a/po/hu.po +++ b/po/hu.po @@ -2960,32 +2960,32 @@ msgid "Lens" msgstr "" # src/dupe.cc:67 src/dupe.cc:1449 -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d byte" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f K" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f MB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f GB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" # src/utilops.cc:1144 -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "" @@ -2993,57 +2993,57 @@ msgstr "" "%s\n" "már létezik." -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 #, fuzzy msgid "no read permission" msgstr "(nincs olvasási jog) %s byte" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" # src/utilops.cc:451 -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 #, fuzzy msgid "source and destination are the same" msgstr "Forrás és cél ugyanaz, művelet megszakítva." # src/utilops.cc:451 -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 #, fuzzy msgid "source and destination have different extension" msgstr "Forrás és cél ugyanaz, művelet megszakítva." -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" # src/ui-tabcomp.cc:171 -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Nem lehet előzmények listát létrehozni: %s\n" diff --git a/po/id.po b/po/id.po index 847bc5e34..ebbae1f70 100644 --- a/po/id.po +++ b/po/id.po @@ -2694,83 +2694,83 @@ msgstr "Tanggal file:" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d bytes" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f K" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f MB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f MB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "File bernama %s sudah ada." -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 #, fuzzy msgid "no read permission" msgstr "(tidak ada ijin membaca) %s byte" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 #, fuzzy msgid "source and destination are the same" msgstr "Sumber dan tujuan sama, operasi dibatalkan." -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 #, fuzzy msgid "source and destination have different extension" msgstr "Sumber dan tujuan sama, operasi dibatalkan." -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Tidak dapat menulis daftar sejarah ke: %s\n" diff --git a/po/it.po b/po/it.po index 1b8f3d291..c7d22f45e 100644 --- a/po/it.po +++ b/po/it.po @@ -2644,31 +2644,31 @@ msgstr "Pagina %d" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d byte" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f K " -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f MB " -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f GB " -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "" @@ -2676,58 +2676,58 @@ msgstr "" "%s\n" "esiste già." -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 #, fuzzy msgid "no read permission" msgstr "(nessun permesso di lettura) %s byte" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 #, fuzzy msgid "source and destination are the same" msgstr "" "La sorgente e la destinazione coincidono: l'operazione è stata quindi " "annullata." -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 #, fuzzy msgid "source and destination have different extension" msgstr "" "La sorgente e la destinazione coincidono: l'operazione è stata quindi " "annullata." -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Non è possibile scrivere la cronologia in: %s\n" diff --git a/po/ja.po b/po/ja.po index 417ea900c..f114ded5b 100644 --- a/po/ja.po +++ b/po/ja.po @@ -2605,31 +2605,31 @@ msgstr "%d ページ" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d バイト" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f Kバイト" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f Mバイト" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f Gバイト" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "" @@ -2637,52 +2637,52 @@ msgstr "" "%s\n" "は既に存在しています。" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 #, fuzzy msgid "no read permission" msgstr "(読み込み権限なし) %s バイト" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "移動元と移動先が同じです" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "移動元と移動先の拡張子が異なります" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "ファイルには保存されていないメタデータの変更があります" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "同じ名前の、別の移動先ファイルがあります" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "エラー: マークのリストを書き込めません: %s\n" diff --git a/po/ko.po b/po/ko.po index 7a1b0ed2c..3ce9b38a9 100644 --- a/po/ko.po +++ b/po/ko.po @@ -2491,79 +2491,79 @@ msgstr "페이지 번호." msgid "Lens" msgstr "렌즈" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d 바이트" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, c-format msgid "%.1f KiB" msgstr "%.1f KiB" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, c-format msgid "%.1f MiB" msgstr "%.1f MiB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, c-format msgid "%.1f GiB" msgstr "%.1f GiB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "파일 또는 디렉터리가 존재하지 않습니다" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "대상이 이미 존재합니다" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "대상을 덮어쓸 수 없습니다" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "대상 디렉터리는 기록할 수 없습니다" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "대상 디렉터리가 존재하지 않습니다" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "소스 디렉터리는 기록할 수 없습니다" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "읽기 권한이 없습니다" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "파일이 읽기 전용입니다" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "대상이 이미 존재하며 덮어쓰게 됩니다" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "소스와 대상이 같습니다" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "소스와 대상의 확장자가 다릅니다" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "파일에 대해 저장되지 않은 메타데이터 변경사항이 있습니다" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "다른 대상 파일의 파일이름이 같습니다" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "오류: 다음 위치에 마크 목록을 기록할 수 없음: %s\n" diff --git a/po/nb.po b/po/nb.po index e6380f308..d895143c2 100644 --- a/po/nb.po +++ b/po/nb.po @@ -2693,82 +2693,82 @@ msgstr "Filter:" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, fuzzy, c-format msgid "%d bytes" msgstr "%d filer" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, c-format msgid "%.1f KiB" msgstr "" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, c-format msgid "%.1f MiB" msgstr "" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, c-format msgid "%.1f GiB" msgstr "" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "Fil ved navn %s finnes allerede." -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 #, fuzzy msgid "source and destination are the same" msgstr "Bane og destinasjon er den samme, operasjonen ble avbrutt." -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 #, fuzzy msgid "source and destination have different extension" msgstr "Bane og destinasjon er den samme, operasjonen ble avbrutt." -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Kunne ikke skrive historieliste til: %s\n" diff --git a/po/nl.po b/po/nl.po index cdcfa2588..75d87e70c 100644 --- a/po/nl.po +++ b/po/nl.po @@ -2517,79 +2517,79 @@ msgstr "Pagina nr." msgid "Lens" msgstr "Lens" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d bytes" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, c-format msgid "%.1f KiB" msgstr "%.1f KiB" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, c-format msgid "%.1f MiB" msgstr "%.1f MiB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, c-format msgid "%.1f GiB" msgstr "%.1f GiB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "bestand of map bestaat niet" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "doel bestaat al" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "doel kan niet overschreven worden" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "doelmap is niet beschrijfbaar" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "doelmap bestaat niet" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "bronmap is niet beschrijfbaar" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "geen leespermissie" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "bestand is niet beschrijfbaar" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "doel bestaat al en zal overschreven worden" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "bron is gelijk aan doel" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "bron en doel heben verschillende extensie" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "er zijn niet-opgeslagen wijzigingen in de metagegevens van het bestand" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "ander doelbestand heeft al dezelfde naam" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Fout: Kon markeerlijst niet wegschrijven naar: %s\n" diff --git a/po/pl.po b/po/pl.po index 2af312760..a1ab884d1 100644 --- a/po/pl.po +++ b/po/pl.po @@ -2564,79 +2564,79 @@ msgstr "Strona %d" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d bajtów" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f K" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f MB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f GB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "plik lub katalog nie istnieją" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "cel już istnieje" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "cel nie może być nadpisany" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "docelowy katalog nie jest zapisywalny" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "docelowy katalog nie istnieje" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "katalog źródłowy nie istnieje" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "brak uprawnień do odczytu" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "plik jest tylko do odczytu" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "cel już istnieje i zostanie nadpisany" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "źródło i cel są takie same" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "źródło i cel mają różne rozszerzenia" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "istnieją nie zapisane metadane dla tego pliku" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "inny plik docelowy ma taką samą nazwę" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Nie można zapisać list historii w: %s\n" diff --git a/po/pt_BR.po b/po/pt_BR.po index 00d96f298..10c3e29ed 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -2555,80 +2555,80 @@ msgstr "Página %d" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d bytes" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f KB" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f MB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f GB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "o arquivo ou o diretório não existe" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "o destino já existe" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "o destino não pode ser sobre-escrito" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "o diretório de destino não é escrevível" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "o diretório de destino não existe" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "o diretório de origem não é escrevível" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "sem permissão de leitura" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "o arquivo é somente leitura" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "o destino já existe e será sobre-escrito" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "a origem e o destino são o mesmo" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "a origem e o destino têm diferentes extensões" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "há modificações nos metadados não salvas para o arquivo" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 #, fuzzy msgid "another destination file has the same filename" msgstr "a origem e o destino são o mesmo" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Incapaz de escrever as listas do histórico em: %s\n" diff --git a/po/ro.po b/po/ro.po index 470b5a2dc..75195a039 100644 --- a/po/ro.po +++ b/po/ro.po @@ -2557,80 +2557,80 @@ msgstr "Pagina %d" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d bytes" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f K" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f MB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f GB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "fișierul sau directorul nu există" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "destinația există deja" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "destinația nu poate fi suprascrisă" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "nu se poate scrie în directorul destinație" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "directorul destinație nu există" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "nu se poate scrie în directorul sursă" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "nu există permisiune de citire) %s byte" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "fișierul este read-only" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "destinația există deja și va fi suprascrisă" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "sursa se suprapune cu destinaţia" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "sursa și destinaţia au extensii diferite" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "metadatele fișierului conțin modificări nesalvate" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 #, fuzzy msgid "another destination file has the same filename" msgstr "sursa se suprapune cu destinaţia" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Nu pot scrie istoricul în: %s\n" diff --git a/po/ru.po b/po/ru.po index 36fb15672..0a4a2d081 100644 --- a/po/ru.po +++ b/po/ru.po @@ -2503,79 +2503,79 @@ msgstr "Номер страницы." msgid "Lens" msgstr "Линзы" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d байт" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f КБ" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f МБ" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f ГБ" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "файл или каталог не существует" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "конечный файл уже существует" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "невозможно перезаписать каталог назначения" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "нет прав на запись в каталог назначения" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "каталог назначения не существует" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "нет прав на запись в исходную директорию" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "нет прав на чтение" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "файл доступен только для чтения" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "конечный файл уже существует и будет перезаписан" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "исходный и конечный файлы совпадают" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "исходный и конечный файлы имеют различные расширения" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "для файла есть несохраненные изменения в метаданных" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "исходный и конечный файлы совпадают" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Ошибка: Невозможно записать списки меток в: %s\n" diff --git a/po/sk.po b/po/sk.po index e6338146a..43d9eac22 100644 --- a/po/sk.po +++ b/po/sk.po @@ -2768,84 +2768,84 @@ msgid "Lens" msgstr "Šošovky" # src/dupe.cc:67 src/dupe.cc:1449 -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d bajtov" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, c-format msgid "%.1f KiB" msgstr "%.1f KiB" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, c-format msgid "%.1f MiB" msgstr "%.1f MiB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, c-format msgid "%.1f GiB" msgstr "%.1f GiB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "súbor alebo adresár neexistuje" # src/filelist.c:814 -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "cieľ už existuje" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "cieľové umiestnenie sa nedá prepísať" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "do cieľového adresára nie je možné zapisovať" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "cieľový adresár neexistuje" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "do zdrojového adresára nie je možné zapisovať" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "chýbajúce práva na čítanie" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "súbor je určený len na čítanie" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "cieľ už existuje a bude prepísaný" # src/utilops.cc:451 -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "zdroj a cieľ sú totožné" # src/utilops.cc:451 -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "zdroj a cieľ majú rozdielne prípony" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "metadáta súboru čakajú na uloženie" # src/utilops.cc:451 -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "iný cieľový súbor má rovnaké meno" # src/ui-tabcomp.cc:171 -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Chyba: Nemožno zapísať zoznamy značiek do: %s\n" diff --git a/po/sl.po b/po/sl.po index 70f8b5d21..fe7c58fdd 100644 --- a/po/sl.po +++ b/po/sl.po @@ -2694,82 +2694,82 @@ msgstr "Filter:" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, fuzzy, c-format msgid "%d bytes" msgstr "%d datotek" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, c-format msgid "%.1f KiB" msgstr "" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, c-format msgid "%.1f MiB" msgstr "" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, c-format msgid "%.1f GiB" msgstr "" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "Datoteka z imenom %s že obstaja." -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 #, fuzzy msgid "source and destination are the same" msgstr "Izvor in cilj sta enaka, operacija preklicana." -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 #, fuzzy msgid "source and destination have different extension" msgstr "Izvor in cilj sta enaka, operacija preklicana." -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Ne morem napisati seznama zgodovine v: %s\n" diff --git a/po/sr.po b/po/sr.po index a5e026e1c..4a2558e64 100644 --- a/po/sr.po +++ b/po/sr.po @@ -2566,80 +2566,80 @@ msgstr "%d. страница" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d бајтова" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f K" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f MB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f GB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "не постоји датотека или директоријум" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "одредиште већ постоји" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "не могу да препишем преко одредишта" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "не могу да пишем у циљни директиријум" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "циљни директоријум не постоји" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "не могу да пишем у изворни директоријум" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "немате овлашћења за читање" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "можете само читати ову датотеку" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "већ постоји одредиште и нећу га преписати" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "извор и одредиште су исти" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "извор и одредиште имају различите екстензије" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "има несачуваних измена у метаподацима датотеке" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 #, fuzzy msgid "another destination file has the same filename" msgstr "извор и одредиште су исти" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Не могу да упишем историјат у %s\n" diff --git a/po/sr@latin.po b/po/sr@latin.po index 036a32922..2bafee1b6 100644 --- a/po/sr@latin.po +++ b/po/sr@latin.po @@ -2566,80 +2566,80 @@ msgstr "%d. stranica" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d bajtova" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f K" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f MB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f GB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "ne postoji datoteka ili direktorijum" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "odredište već postoji" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "ne mogu da prepišem preko odredišta" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "ne mogu da pišem u ciljni direktirijum" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "ciljni direktorijum ne postoji" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "ne mogu da pišem u izvorni direktorijum" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "nemate ovlašćenja za čitanje" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "možete samo čitati ovu datoteku" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "već postoji odredište i neću ga prepisati" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "izvor i odredište su isti" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "izvor i odredište imaju različite ekstenzije" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "ima nesačuvanih izmena u metapodacima datoteke" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 #, fuzzy msgid "another destination file has the same filename" msgstr "izvor i odredište su isti" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Ne mogu da upišem istorijat u %s\n" diff --git a/po/sv.po b/po/sv.po index f123e65ca..da1018bdf 100644 --- a/po/sv.po +++ b/po/sv.po @@ -2494,79 +2494,79 @@ msgstr "Sida nr." msgid "Lens" msgstr "Lins" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d byte" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, c-format msgid "%.1f KiB" msgstr "%.1f KiB" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, c-format msgid "%.1f MiB" msgstr "%.1f MiB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, c-format msgid "%.1f GiB" msgstr "%.1f GiB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "filen eller mappen finns inte" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "filen finns redan" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "målfilen kan inte skrivas över" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "målmappen är skrivskyddad" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "målmappen finns inte" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "källmappen är skrivskyddad" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "läsrättighet saknas" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "filen är skrivskyddad" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "målfilen finns redan men kommer att skrivas över" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "källa och mål är desamma" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "källa och mål har oliks filnamntillägg" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "det finns osparade ändrade metadata för filen" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "en annan destinationsfil har samma filnamn" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Fel: Kan inte skriva märkeslistor till: %s\n" diff --git a/po/th.po b/po/th.po index 79a534fd7..d5b5df24e 100644 --- a/po/th.po +++ b/po/th.po @@ -2694,82 +2694,82 @@ msgstr "ฟิลเตอร์:" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, fuzzy, c-format msgid "%d bytes" msgstr "%d แฟ้ม" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, c-format msgid "%.1f KiB" msgstr "" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, c-format msgid "%.1f MiB" msgstr "" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, c-format msgid "%.1f GiB" msgstr "" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "แฟ้มชื่อ %s มีอยู่แล้ว" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 #, fuzzy msgid "source and destination are the same" msgstr "ต้นทางและปลายทางเหมือนกัน, การดำเนินการถูกยกเลิก" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 #, fuzzy msgid "source and destination have different extension" msgstr "ต้นทางและปลายทางเหมือนกัน, การดำเนินการถูกยกเลิก" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "ไม่สามารถเขียนรายการประวัติศาสตร์สู่:-%s\n" diff --git a/po/tlh.po b/po/tlh.po index 1e051829d..49735518d 100644 --- a/po/tlh.po +++ b/po/tlh.po @@ -2421,79 +2421,79 @@ msgstr "" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, c-format msgid "%.1f KiB" msgstr "" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, c-format msgid "%.1f MiB" msgstr "" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, c-format msgid "%.1f GiB" msgstr "" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "" diff --git a/po/tr.po b/po/tr.po index 15f1bb90f..70da26c19 100644 --- a/po/tr.po +++ b/po/tr.po @@ -2560,79 +2560,79 @@ msgstr "%d sayfası" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d bayt" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f K" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f MB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f GB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "dosya veya dizin yok" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "Hedef zaten var" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "hedefin üzerine yazılamaz" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "hedef dizin yazılabilir değil" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "hedef dizin yok" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "kaynak dizin yazılabilir değil" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "okuma izni yok" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "dosya salt okunur" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "hedef zaten var ve üzerine yazılacak" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "kaynak ve hedef aynı" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "kaynak ve hedef farklı uzantılara sahip" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "dosya için kaydedilmemiş meta veri değişiklikleri var" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "başka bir hedef dosya aynı dosya adına sahip" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Geçmiş listeleri yazılamıyor: %s\n" diff --git a/po/uk.po b/po/uk.po index 19fed832e..4d894b88f 100644 --- a/po/uk.po +++ b/po/uk.po @@ -2694,82 +2694,82 @@ msgstr "Фільтр:" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "байтів: %d" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f K" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f Mб" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f Mб" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "Файл з назвою %s вже існує." -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 #, fuzzy msgid "source and destination are the same" msgstr "Джерело і ціль це одне і те ж, операція припинена." -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 #, fuzzy msgid "source and destination have different extension" msgstr "Джерело і ціль це одне і те ж, операція припинена." -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Не можу записати файл історії в: %s\n" diff --git a/po/vi.po b/po/vi.po index 4e1a9ebeb..415816acd 100644 --- a/po/vi.po +++ b/po/vi.po @@ -2643,83 +2643,83 @@ msgstr "Trang %d" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d byte" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f K" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f MB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f GB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "Tập tin tên %s đã có rồi." -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 #, fuzzy msgid "no read permission" msgstr "(không có quyền đọc) %s byte" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 #, fuzzy msgid "source and destination are the same" msgstr "Nguồn và đích trùng nhau, thao tác bị hủy bỏ." -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 #, fuzzy msgid "source and destination have different extension" msgstr "Nguồn và đích trùng nhau, thao tác bị hủy bỏ." -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "Không thể lưu danh sách lượt sử vào: %s\n" diff --git a/po/zh_CN.po b/po/zh_CN.po index bb6e47235..af33f4b28 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -2619,80 +2619,80 @@ msgstr "第 %d 页" msgid "Lens" msgstr "" -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d 字节" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, c-format msgid "%.1f KiB" msgstr "" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, c-format msgid "%.1f MiB" msgstr "" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, c-format msgid "%.1f GiB" msgstr "" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "文件或目录不存在" -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 msgid "destination already exists" msgstr "目标文件已存在" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "目标文件不能覆盖" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "目标目录不可写" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "目标目录不存在" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "源目录不可写" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 msgid "no read permission" msgstr "无读取权限" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "文件为只读" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "目标文件已存在并将被覆盖" -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 msgid "source and destination are the same" msgstr "源和目标相同" -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 msgid "source and destination have different extension" msgstr "源和目标扩展名不一致" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "文件有未保存的元数据更改" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 #, fuzzy msgid "another destination file has the same filename" msgstr "源和目标相同" -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "无法保存历史记录至: %s\n" diff --git a/po/zh_TW.po b/po/zh_TW.po index 9951d5779..a048597bc 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -2956,32 +2956,32 @@ msgid "Lens" msgstr "" # src/dupe.cc:67 src/dupe.cc:1449 -#: src/filedata.cc:134 +#: src/filedata/filedata.cc:134 #, c-format msgid "%d bytes" msgstr "%d bytes" -#: src/filedata.cc:138 +#: src/filedata/filedata.cc:138 #, fuzzy, c-format msgid "%.1f KiB" msgstr "%.1f K" -#: src/filedata.cc:142 +#: src/filedata/filedata.cc:142 #, fuzzy, c-format msgid "%.1f MiB" msgstr "%.1f MB" -#: src/filedata.cc:147 +#: src/filedata/filedata.cc:147 #, fuzzy, c-format msgid "%.1f GiB" msgstr "%.1f GB" -#: src/filedata.cc:2826 +#: src/filedata/filedata.cc:2826 msgid "file or directory does not exist" msgstr "" # src/utilops.cc:1144 -#: src/filedata.cc:2832 +#: src/filedata/filedata.cc:2832 #, fuzzy msgid "destination already exists" msgstr "" @@ -2989,60 +2989,60 @@ msgstr "" "%s\n" "已經存在。" -#: src/filedata.cc:2838 +#: src/filedata/filedata.cc:2838 msgid "destination can't be overwritten" msgstr "" -#: src/filedata.cc:2844 +#: src/filedata/filedata.cc:2844 msgid "destination directory is not writable" msgstr "" -#: src/filedata.cc:2850 +#: src/filedata/filedata.cc:2850 msgid "destination directory does not exist" msgstr "" -#: src/filedata.cc:2856 +#: src/filedata/filedata.cc:2856 msgid "source directory is not writable" msgstr "" -#: src/filedata.cc:2862 +#: src/filedata/filedata.cc:2862 #, fuzzy msgid "no read permission" msgstr "(無讀取權限) %s bytes" -#: src/filedata.cc:2868 +#: src/filedata/filedata.cc:2868 msgid "file is readonly" msgstr "" -#: src/filedata.cc:2874 +#: src/filedata/filedata.cc:2874 msgid "destination already exists and will be overwritten" msgstr "" # # src/utilops.cc:451 -#: src/filedata.cc:2880 +#: src/filedata/filedata.cc:2880 #, fuzzy msgid "source and destination are the same" msgstr "來源地和目的地一樣,取消操作。" # # src/utilops.cc:451 -#: src/filedata.cc:2886 +#: src/filedata/filedata.cc:2886 #, fuzzy msgid "source and destination have different extension" msgstr "來源地和目的地一樣,取消操作。" -#: src/filedata.cc:2892 +#: src/filedata/filedata.cc:2892 msgid "there are unsaved metadata changes for the file" msgstr "" -#: src/filedata.cc:2898 +#: src/filedata/filedata.cc:2898 msgid "another destination file has the same filename" msgstr "" # # src/ui-tabcomp.cc:171 -#: src/filedata.cc:3460 +#: src/filedata/filedata.cc:3460 #, fuzzy, c-format msgid "Error: Unable to write marks lists to: %s\n" msgstr "不能將紀錄列表寫入:%s\n" diff --git a/scripts/clang-tidy-check.sh b/scripts/clang-tidy-check.sh index 474a1a710..fb430ae50 100755 --- a/scripts/clang-tidy-check.sh +++ b/scripts/clang-tidy-check.sh @@ -120,12 +120,12 @@ then $(find src -name "*.cc" | sort) EOF else - total_files=$(git diff --name-only ./src/*.cc ./src/pan-view/*.cc ./src/view-file/*.cc | wc --lines) + total_files=$(git diff --name-only ./src/*.cc ./src/filedata/*.cc ./src/pan-view/*.cc ./src/view-file/*.cc | wc --lines) while read -r file do process_file done << EOF -$(git diff --name-only ./src/*.cc ./src/pan-view/*.cc ./src/view-file/*.cc | sort) +$(git diff --name-only ./src/*.cc ./src/filedata/*.cc ./src/pan-view/*.cc ./src/view-file/*.cc | sort) EOF fi diff --git a/src/filedata.cc b/src/filedata.cc index 9bc661372..4682befc0 100644 --- a/src/filedata.cc +++ b/src/filedata.cc @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006 John Ellis + * Copyright (C) 2004 John Ellis * Copyright (C) 2008 - 2016 The Geeqie Team * * Author: John Ellis @@ -21,3561 +21,565 @@ #include "filedata.h" -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include "cache.h" -#include "debug.h" -#include "exif.h" -#include "filefilter.h" -#include "histogram.h" -#include "intl.h" -#include "main-defines.h" -#include "main.h" -#include "metadata.h" -#include "misc.h" -#include "options.h" -#include "secure-save.h" -#include "thumb-standard.h" -#include "trash.h" -#include "ui-fileops.h" - -#ifdef DEBUG_FILEDATA -gint global_file_data_count = 0; -#endif - -static GHashTable *file_data_pool = nullptr; -static GHashTable *file_data_planned_change_hash = nullptr; - -static gint sidecar_file_priority(const gchar *extension); -static void file_data_check_sidecars(const GList *basename_list); -static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd); - - -static SortType filelist_sort_method = SORT_NONE; -static gboolean filelist_sort_ascend = TRUE; -static gboolean filelist_sort_case = TRUE; - -/* - *----------------------------------------------------------------------------- - * text conversion utils - *----------------------------------------------------------------------------- - */ - gchar *text_from_size(gint64 size) { - gchar *a; - gchar *b; - gchar *s; - gchar *d; - gint l; - gint n; - gint i; - - /* what I would like to use is printf("%'d", size) - * BUT: not supported on every libc :( - */ - if (size > G_MAXINT) - { - /* the %lld conversion is not valid in all libcs, so use a simple work-around */ - a = g_strdup_printf("%d%09d", static_cast(size / 1000000000), static_cast(size % 1000000000)); - } - else - { - a = g_strdup_printf("%d", static_cast(size)); - } - l = strlen(a); - n = (l - 1)/ 3; - if (n < 1) return a; - - b = g_new(gchar, l + n + 1); - - s = a; - d = b; - i = l - n * 3; - while (*s != '\0') - { - if (i < 1) - { - i = 3; - *d = ','; - d++; - } - - *d = *s; - s++; - d++; - i--; - } - *d = '\0'; - - g_free(a); - return b; + return FileData::text_from_size(size); } gchar *text_from_size_abrev(gint64 size) { - if (size < static_cast(1024)) - { - return g_strdup_printf(_("%d bytes"), static_cast(size)); - } - if (size < static_cast(1048576)) - { - return g_strdup_printf(_("%.1f KiB"), static_cast(size) / 1024.0); - } - if (size < static_cast(1073741824)) - { - return g_strdup_printf(_("%.1f MiB"), static_cast(size) / 1048576.0); - } - - /* to avoid overflowing the gdouble, do division in two steps */ - size /= 1048576; - return g_strdup_printf(_("%.1f GiB"), static_cast(size) / 1024.0); -} - -/* note: returned string is valid until next call to text_from_time() */ -const gchar *text_from_time(time_t t) -{ - static gchar *ret = nullptr; - gchar buf[128]; - gint buflen; - struct tm *btime; - GError *error = nullptr; - - btime = localtime(&t); - - /* the %x warning about 2 digit years is not an error */ - buflen = strftime(buf, sizeof(buf), "%x %X", btime); - if (buflen < 1) return ""; - - g_free(ret); - ret = g_locale_to_utf8(buf, buflen, nullptr, nullptr, &error); - if (error) - { - log_printf("Error converting locale strftime to UTF-8: %s\n", error->message); - g_error_free(error); - return ""; - } - - return ret; -} - -/* - *----------------------------------------------------------------------------- - * changed files detection and notification - *----------------------------------------------------------------------------- - */ - -void file_data_increment_version(FileData *fd) -{ - fd->version++; - fd->valid_marks = 0; - if (fd->parent) - { - fd->parent->version++; - fd->parent->valid_marks = 0; - } -} - -static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st) -{ - if (fd->size != st->st_size || - fd->date != st->st_mtime) - { - fd->size = st->st_size; - fd->date = st->st_mtime; - fd->cdate = st->st_ctime; - fd->mode = st->st_mode; - if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf); - fd->thumb_pixbuf = nullptr; - file_data_increment_version(fd); - file_data_send_notification(fd, NOTIFY_REREAD); - return TRUE; - } - return FALSE; -} - -static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st) -{ - gboolean ret = FALSE; - GList *work; - - ret = file_data_check_changed_single_file(fd, st); - - work = fd->sidecar_files; - while (work) - { - auto sfd = static_cast(work->data); - struct stat st; - work = work->next; - - if (!stat_utf8(sfd->path, &st)) - { - fd->size = 0; - fd->date = 0; - file_data_ref(sfd); - file_data_disconnect_sidecar_file(fd, sfd); - ret = TRUE; - file_data_increment_version(sfd); - file_data_send_notification(sfd, NOTIFY_REREAD); - file_data_unref(sfd); - continue; - } - - ret |= file_data_check_changed_files_recursive(sfd, &st); - } - return ret; + return FileData::text_from_size_abrev(size); } - -gboolean file_data_check_changed_files(FileData *fd) +const gchar *text_from_time(time_t t) { - gboolean ret = FALSE; - struct stat st; - - if (fd->parent) fd = fd->parent; - - if (!stat_utf8(fd->path, &st)) - { - GList *sidecars; - GList *work; - FileData *sfd = nullptr; - - /* parent is missing, we have to rebuild whole group */ - ret = TRUE; - fd->size = 0; - fd->date = 0; - - /* file_data_disconnect_sidecar_file might delete the file, - we have to keep the reference to prevent this */ - sidecars = filelist_copy(fd->sidecar_files); - file_data_ref(fd); - work = sidecars; - while (work) - { - sfd = static_cast(work->data); - work = work->next; - - file_data_disconnect_sidecar_file(fd, sfd); - } - file_data_check_sidecars(sidecars); /* this will group the sidecars back together */ - /* now we can release the sidecars */ - filelist_free(sidecars); - file_data_increment_version(fd); - file_data_send_notification(fd, NOTIFY_REREAD); - file_data_unref(fd); - } - else - { - ret |= file_data_check_changed_files_recursive(fd, &st); - } - - return ret; + return FileData::text_from_time(t); } -/* - *----------------------------------------------------------------------------- - * file name, extension, sorting, ... - *----------------------------------------------------------------------------- +/** + * @headerfile file_data_new_group + * scan for sidecar files - expensive */ - -static void file_data_set_collate_keys(FileData *fd) +FileData *file_data_new_group(const gchar *path_utf8) { - gchar *caseless_name; - gchar *valid_name; - - valid_name = g_filename_display_name(fd->name); - caseless_name = g_utf8_casefold(valid_name, -1); - - g_free(fd->collate_key_name); - g_free(fd->collate_key_name_nocase); - - fd->collate_key_name_natural = g_utf8_collate_key_for_filename(fd->name, -1); - fd->collate_key_name_nocase_natural = g_utf8_collate_key_for_filename(caseless_name, -1); - fd->collate_key_name = g_utf8_collate_key(valid_name, -1); - fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1); - - g_free(valid_name); - g_free(caseless_name); + return FileData::file_data_new_group(path_utf8); } -static void file_data_set_path(FileData *fd, const gchar *path) -{ - g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */ - g_assert(file_data_pool); - - g_free(fd->path); - - if (fd->original_path) - { - g_hash_table_remove(file_data_pool, fd->original_path); - g_free(fd->original_path); - } - - g_assert(!g_hash_table_lookup(file_data_pool, path)); - - fd->original_path = g_strdup(path); - g_hash_table_insert(file_data_pool, fd->original_path, fd); - if (strcmp(path, G_DIR_SEPARATOR_S) == 0) - { - fd->path = g_strdup(path); - fd->name = fd->path; - fd->extension = fd->name + 1; - file_data_set_collate_keys(fd); - return; - } - - fd->path = g_strdup(path); - fd->name = filename_from_path(fd->path); - - if (strcmp(fd->name, "..") == 0) - { - gchar *dir = remove_level_from_path(path); - g_free(fd->path); - fd->path = remove_level_from_path(dir); - g_free(dir); - fd->name = ".."; - fd->extension = fd->name + 2; - file_data_set_collate_keys(fd); - return; - } - - if (strcmp(fd->name, ".") == 0) - { - g_free(fd->path); - fd->path = remove_level_from_path(path); - fd->name = "."; - fd->extension = fd->name + 1; - file_data_set_collate_keys(fd); - return; - } - - fd->extension = registered_extension_from_path(fd->path); - if (fd->extension == nullptr) - { - fd->extension = fd->name + strlen(fd->name); - } - - fd->sidecar_priority = sidecar_file_priority(fd->extension); - file_data_set_collate_keys(fd); -} - -/* - *----------------------------------------------------------------------------- - * create or reuse Filedata - *----------------------------------------------------------------------------- +/** + * @headerfile file_data_new_no_grouping + * should be used on helper files which can't have sidecars */ - -static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars) -{ - FileData *fd; - struct passwd *user; - struct group *group; - - DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars); - - if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE; - - if (!file_data_pool) - file_data_pool = g_hash_table_new(g_str_hash, g_str_equal); - - fd = static_cast(g_hash_table_lookup(file_data_pool, path_utf8)); - if (fd) - { - file_data_ref(fd); - } - - if (!fd && file_data_planned_change_hash) - { - fd = static_cast(g_hash_table_lookup(file_data_planned_change_hash, path_utf8)); - if (fd) - { - DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path); - if (!isfile(fd->path)) - { - file_data_ref(fd); - file_data_apply_ci(fd); - } - else - { - fd = nullptr; - } - } - } - - if (fd) - { - if (disable_sidecars) file_data_disable_grouping(fd, TRUE); - -#ifdef DEBUG_FILEDATA - gboolean changed = -#endif - file_data_check_changed_single_file(fd, st); - - DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : ""); - - return fd; - } - - fd = g_new0(FileData, 1); -#ifdef DEBUG_FILEDATA - global_file_data_count++; - DEBUG_2("file data count++: %d", global_file_data_count); -#endif - - fd->size = st->st_size; - fd->date = st->st_mtime; - fd->cdate = st->st_ctime; - fd->mode = st->st_mode; - fd->ref = 1; - fd->magick = FD_MAGICK; - fd->exifdate = 0; - fd->rating = STAR_RATING_NOT_READ; - fd->format_class = filter_file_get_class(path_utf8); - fd->page_num = 0; - fd->page_total = 0; - - user = getpwuid(st->st_uid); - if (!user) - { - fd->owner = g_strdup_printf("%u", st->st_uid); - } - else - { - fd->owner = g_strdup(user->pw_name); - } - - group = getgrgid(st->st_gid); - if (!group) - { - fd->group = g_strdup_printf("%u", st->st_gid); - } - else - { - fd->group = g_strdup(group->gr_name); - } - - fd->sym_link = get_symbolic_link(path_utf8); - - if (disable_sidecars) fd->disable_grouping = TRUE; - - file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */ - - return fd; -} - -static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars) -{ - gchar *path_utf8 = path_to_utf8(path); - FileData *ret = file_data_new(path_utf8, st, disable_sidecars); - - g_free(path_utf8); - return ret; -} - -FileData *file_data_new_simple(const gchar *path_utf8) -{ - struct stat st; - FileData *fd; - - if (!stat_utf8(path_utf8, &st)) - { - st.st_size = 0; - st.st_mtime = 0; - } - - fd = static_cast(g_hash_table_lookup(file_data_pool, path_utf8)); - if (!fd) fd = file_data_new(path_utf8, &st, TRUE); - if (fd) - { - file_data_ref(fd); - } - - return fd; -} - -void read_exif_time_data(FileData *file) +FileData *file_data_new_no_grouping(const gchar *path_utf8) { - if (file->exifdate > 0) - { - DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path); - return; - } - - if (!file->exif) - { - exif_read_fd(file); - } - - if (file->exif) - { - gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal"); - DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), (void *)file, file->path); - - if (tmp) - { - struct tm time_str; - uint year; - uint month; - uint day; - uint hour; - uint min; - uint sec; - - sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec); - time_str.tm_year = year - 1900; - time_str.tm_mon = month - 1; - time_str.tm_mday = day; - time_str.tm_hour = hour; - time_str.tm_min = min; - time_str.tm_sec = sec; - time_str.tm_isdst = 0; - - file->exifdate = mktime(&time_str); - g_free(tmp); - } - } + return FileData::file_data_new_no_grouping(path_utf8); } -void read_exif_time_digitized_data(FileData *file) -{ - if (file->exifdate_digitized > 0) - { - DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path); - return; - } - - if (!file->exif) - { - exif_read_fd(file); - } - - if (file->exif) - { - gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized"); - DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), (void *)file, file->path); - - if (tmp) - { - struct tm time_str; - uint year; - uint month; - uint day; - uint hour; - uint min; - uint sec; - - sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec); - time_str.tm_year = year - 1900; - time_str.tm_mon = month - 1; - time_str.tm_mday = day; - time_str.tm_hour = hour; - time_str.tm_min = min; - time_str.tm_sec = sec; - time_str.tm_isdst = 0; - - file->exifdate_digitized = mktime(&time_str); - g_free(tmp); - } - } -} -void read_rating_data(FileData *file) +/** + * @headerfile file_data_new_dir + * should be used on dirs + */ +FileData *file_data_new_dir(const gchar *path_utf8) { - gchar *rating_str; - - rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN); - if (rating_str) - { - file->rating = atoi(rating_str); - g_free(rating_str); - } - else - { - file->rating = 0; - } + return FileData::file_data_new_dir(path_utf8); } -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" -void set_exif_time_data_unused(GList *files) -{ - DEBUG_1("%s set_exif_time_data: ...", get_exec_time()); - - while (files) - { - auto *file = static_cast(files->data); - - read_exif_time_data(file); - files = files->next; - } -} -void set_exif_time_digitized_data_unused(GList *files) +FileData *file_data_new_simple(const gchar *path_utf8) { - DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time()); - - while (files) - { - auto *file = static_cast(files->data); - - read_exif_time_digitized_data(file); - files = files->next; - } + return FileData::file_data_new_simple(path_utf8); } -void set_rating_data_unused(GList *files) -{ - gchar *rating_str; - DEBUG_1("%s set_rating_data: ...", get_exec_time()); - - while (files) - { - auto *file = static_cast(files->data); - rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN); - if (rating_str ) - { - file->rating = atoi(rating_str); - g_free(rating_str); - } - files = files->next; - } -} -#pragma GCC diagnostic pop +#ifdef DEBUG_FILEDATA -FileData *file_data_new_no_grouping(const gchar *path_utf8) +FileData *file_data_ref(FileData *fd, const gchar *file, gint line) { - struct stat st; - - if (!stat_utf8(path_utf8, &st)) - { - st.st_size = 0; - st.st_mtime = 0; - } - - return file_data_new(path_utf8, &st, TRUE); + if (fd == nullptr) return nullptr; + return fd->file_data_ref(file, line); } -FileData *file_data_new_dir(const gchar *path_utf8) +void file_data_unref(FileData *fd, const gchar *file, gint line) { - struct stat st; - - if (!stat_utf8(path_utf8, &st)) - { - st.st_size = 0; - st.st_mtime = 0; - } - else - /* dir or non-existing yet */ - g_assert(S_ISDIR(st.st_mode)); - - return file_data_new(path_utf8, &st, TRUE); + if (fd == nullptr) return; + fd->file_data_unref(file, line); } -/* - *----------------------------------------------------------------------------- - * reference counting - *----------------------------------------------------------------------------- - */ - -#ifdef DEBUG_FILEDATA -FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd) #else + FileData *file_data_ref(FileData *fd) -#endif { if (fd == nullptr) return nullptr; - if (fd->magick != FD_MAGICK) -#ifdef DEBUG_FILEDATA - log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd); -#else - log_printf("Error: fd magick mismatch fd=%p", fd); -#endif - g_assert(fd->magick == FD_MAGICK); - fd->ref++; - -#ifdef DEBUG_FILEDATA - DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->path, file, line); -#else - DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path); -#endif - return fd; -} - -/** - * @brief Print ref. count and image name - * @param - * - * Print image ref. count and full path name of all images in - * the file_data_pool. - * - * Used only by debug_fd() - */ -void file_data_dump() -{ -#ifdef DEBUG_FILEDATA - FileData *fd; - GList *list; - - if (file_data_pool) - { - list = g_hash_table_get_values(file_data_pool); - - log_printf("%d", global_file_data_count); - log_printf("%d", g_list_length(list)); - - GList *work = list; - while (work) - { - fd = static_cast(work->data); - log_printf("%-4d %s", fd->ref, fd->path); - work = work->next; - } - - g_list_free(list); - } -#endif -} - -static void file_data_free(FileData *fd) -{ - g_assert(fd->magick == FD_MAGICK); - g_assert(fd->ref == 0); - g_assert(!fd->locked); - -#ifdef DEBUG_FILEDATA - global_file_data_count--; - DEBUG_2("file data count--: %d", global_file_data_count); -#endif - - metadata_cache_free(fd); - g_hash_table_remove(file_data_pool, fd->original_path); - - g_free(fd->path); - g_free(fd->original_path); - g_free(fd->collate_key_name); - g_free(fd->collate_key_name_nocase); - g_free(fd->extended_extension); - if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf); - histmap_free(fd->histmap); - g_free(fd->owner); - g_free(fd->group); - g_free(fd->sym_link); - g_free(fd->format_name); - g_assert(fd->sidecar_files == nullptr); /* sidecar files must be freed before calling this */ - - file_data_change_info_free(nullptr, fd); - g_free(fd); -} - -/** - * @brief Checks if the FileData is referenced - * - * Checks the refcount and whether the FileData is locked. - */ -static gboolean file_data_check_has_ref(FileData *fd) -{ - return fd->ref > 0 || fd->locked; -} - -/** - * @brief Consider freeing a FileData. - * - * This function will free a FileData and its children provided that neither its parent nor it has - * a positive refcount, and provided that neither is locked. - */ -static void file_data_consider_free(FileData *fd) -{ - GList *work; - FileData *parent = fd->parent ? fd->parent : fd; - - g_assert(fd->magick == FD_MAGICK); - if (file_data_check_has_ref(fd)) return; - if (file_data_check_has_ref(parent)) return; - - work = parent->sidecar_files; - while (work) - { - auto sfd = static_cast(work->data); - if (file_data_check_has_ref(sfd)) return; - work = work->next; - } - - /* Neither the parent nor the siblings are referenced, so we can free everything */ - DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'", - fd->path, fd->parent ? parent->path : "-"); - - g_list_free_full(parent->sidecar_files, reinterpret_cast(file_data_free)); - parent->sidecar_files = nullptr; - - file_data_free(parent); + return fd->file_data_ref(); } -#ifdef DEBUG_FILEDATA -void file_data_unref_debug(const gchar *file, gint line, FileData *fd) -#else void file_data_unref(FileData *fd) -#endif { if (fd == nullptr) return; - if (fd->magick != FD_MAGICK) -#ifdef DEBUG_FILEDATA - log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd); -#else - log_printf("Error: fd magick mismatch fd=%p", fd); -#endif - g_assert(fd->magick == FD_MAGICK); + fd->file_data_unref(); +} - fd->ref--; -#ifdef DEBUG_FILEDATA - DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->locked, fd->path, - file, line); -#else - DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path); #endif - // Free FileData if it's no longer ref'd - file_data_consider_free(fd); -} - -/** - * @brief Lock the FileData in memory. - * - * This allows the caller to prevent a FileData from being freed, even after its refcount is zero. - * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization, - * even if the code would continue to function properly even if the FileData were freed. Code that - * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref. - *

- * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to - * file_data_lock, a single call to file_data_unlock will unlock the FileData. - */ void file_data_lock(FileData *fd) { if (fd == nullptr) return; - if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd); - - g_assert(fd->magick == FD_MAGICK); - fd->locked = TRUE; - - DEBUG_2("file_data_ref fd=%p (%d): '%s'", (void *)fd, fd->ref, fd->path); + fd->file_data_lock(fd); } -/** - * @brief Reset the maintain-FileData-in-memory lock - * - * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees - * the FileData if its refcount is already zero (which will happen if the lock is the only thing - * keeping it from being freed. - */ void file_data_unlock(FileData *fd) { if (fd == nullptr) return; - if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd); - - g_assert(fd->magick == FD_MAGICK); - fd->locked = FALSE; - - // Free FileData if it's no longer ref'd - file_data_consider_free(fd); + fd->file_data_unlock(fd); } -/** - * @brief Lock all of the FileDatas in the provided list - * - * @see file_data_lock(#FileData) - */ void file_data_lock_list(GList *list) { - GList *work; - - work = list; - while (work) - { - auto fd = static_cast(work->data); - work = work->next; - file_data_lock(fd); - } + FileData::file_data_lock_list(list); } -/** - * @brief Unlock all of the FileDatas in the provided list - * - * @see #file_data_unlock(#FileData) - */ void file_data_unlock_list(GList *list) { - GList *work; - - work = list; - while (work) - { - auto fd = static_cast(work->data); - work = work->next; - file_data_unlock(fd); - } -} - -/* - *----------------------------------------------------------------------------- - * sidecar file info struct - *----------------------------------------------------------------------------- - */ - -static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b) -{ - auto fda = static_cast(a); - auto fdb = static_cast(b); - - if (fda->sidecar_priority < fdb->sidecar_priority) return -1; - if (fda->sidecar_priority > fdb->sidecar_priority) return 1; - - return strcmp(fdb->extension, fda->extension); -} - - -static gint sidecar_file_priority(const gchar *extension) -{ - gint i = 1; - GList *work; - - if (extension == nullptr) - return 0; - - work = sidecar_ext_get_list(); - - while (work) { - auto ext = static_cast(work->data); - - work = work->next; - if (g_ascii_strcasecmp(extension, ext) == 0) return i; - i++; - } - return 0; -} - -static void file_data_check_sidecars(const GList *basename_list) -{ - /* basename_list contains the new group - first is the parent, then sorted sidecars */ - /* all files in the list have ref count > 0 */ - - const GList *work; - GList *s_work; - GList *new_sidecars; - FileData *parent_fd; - - if (!basename_list) return; - - - DEBUG_2("basename start"); - work = basename_list; - while (work) - { - auto fd = static_cast(work->data); - work = work->next; - g_assert(fd->magick == FD_MAGICK); - DEBUG_2("basename: %p %s", (void *)fd, fd->name); - if (fd->parent) - { - g_assert(fd->parent->magick == FD_MAGICK); - DEBUG_2(" parent: %p", (void *)fd->parent); - } - s_work = fd->sidecar_files; - while (s_work) - { - auto sfd = static_cast(s_work->data); - s_work = s_work->next; - g_assert(sfd->magick == FD_MAGICK); - DEBUG_2(" sidecar: %p %s", (void *)sfd, sfd->name); - } - - g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr); - } - - parent_fd = static_cast(basename_list->data); - - /* check if the second and next entries of basename_list are already connected - as sidecars of the first entry (parent_fd) */ - work = basename_list->next; - s_work = parent_fd->sidecar_files; - - while (work && s_work) - { - if (work->data != s_work->data) break; - work = work->next; - s_work = s_work->next; - } - - if (!work && !s_work) - { - DEBUG_2("basename no change"); - return; /* no change in grouping */ - } - - /* we have to regroup it */ - - /* first, disconnect everything and send notification*/ - - work = basename_list; - while (work) - { - auto fd = static_cast(work->data); - work = work->next; - g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr); - - if (fd->parent) - { - FileData *old_parent = fd->parent; - g_assert(old_parent->parent == nullptr || old_parent->sidecar_files == nullptr); - file_data_ref(old_parent); - file_data_disconnect_sidecar_file(old_parent, fd); - file_data_send_notification(old_parent, NOTIFY_REREAD); - file_data_unref(old_parent); - } - - while (fd->sidecar_files) - { - auto sfd = static_cast(fd->sidecar_files->data); - g_assert(sfd->parent == nullptr || sfd->sidecar_files == nullptr); - file_data_ref(sfd); - file_data_disconnect_sidecar_file(fd, sfd); - file_data_send_notification(sfd, NOTIFY_REREAD); - file_data_unref(sfd); - } - file_data_send_notification(fd, NOTIFY_GROUPING); - - g_assert(fd->parent == nullptr && fd->sidecar_files == nullptr); - } - - /* now we can form the new group */ - work = basename_list->next; - new_sidecars = nullptr; - while (work) - { - auto sfd = static_cast(work->data); - g_assert(sfd->magick == FD_MAGICK); - g_assert(sfd->parent == nullptr && sfd->sidecar_files == nullptr); - sfd->parent = parent_fd; - new_sidecars = g_list_prepend(new_sidecars, sfd); - work = work->next; - } - g_assert(parent_fd->sidecar_files == nullptr); - parent_fd->sidecar_files = g_list_reverse(new_sidecars); - DEBUG_1("basename group changed for %s", parent_fd->path); -} - - -static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd) -{ - g_assert(target->magick == FD_MAGICK); - g_assert(sfd->magick == FD_MAGICK); - g_assert(g_list_find(target->sidecar_files, sfd)); - - file_data_ref(target); - file_data_ref(sfd); - - g_assert(sfd->parent == target); - - file_data_increment_version(sfd); /* increments both sfd and target */ - - target->sidecar_files = g_list_remove(target->sidecar_files, sfd); - sfd->parent = nullptr; - g_free(sfd->extended_extension); - sfd->extended_extension = nullptr; - - file_data_unref(target); - file_data_unref(sfd); -} - -/* disables / enables grouping for particular file, sends UPDATE notification */ -void file_data_disable_grouping(FileData *fd, gboolean disable) -{ - if (!fd->disable_grouping == !disable) return; - - fd->disable_grouping = !!disable; - - if (disable) - { - if (fd->parent) - { - FileData *parent = file_data_ref(fd->parent); - file_data_disconnect_sidecar_file(parent, fd); - file_data_send_notification(parent, NOTIFY_GROUPING); - file_data_unref(parent); - } - else if (fd->sidecar_files) - { - GList *sidecar_files = filelist_copy(fd->sidecar_files); - GList *work = sidecar_files; - while (work) - { - auto sfd = static_cast(work->data); - work = work->next; - file_data_disconnect_sidecar_file(fd, sfd); - file_data_send_notification(sfd, NOTIFY_GROUPING); - } - file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */ - filelist_free(sidecar_files); - } - else - { - file_data_increment_version(fd); /* the functions called in the cases above increments the version too */ - } - } - else - { - file_data_increment_version(fd); - /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */ - } - file_data_send_notification(fd, NOTIFY_GROUPING); -} - -void file_data_disable_grouping_list(GList *fd_list, gboolean disable) -{ - GList *work; - - work = fd_list; - while (work) - { - auto fd = static_cast(work->data); - - file_data_disable_grouping(fd, disable); - work = work->next; - } -} - - - -/* - *----------------------------------------------------------------------------- - * filelist sorting - *----------------------------------------------------------------------------- - */ - - -gint filelist_sort_compare_filedata(FileData *fa, FileData *fb) -{ - gint ret; - if (!filelist_sort_ascend) - { - std::swap(fa, fb); - } - - switch (filelist_sort_method) - { - case SORT_NAME: - break; - case SORT_SIZE: - if (fa->size < fb->size) return -1; - if (fa->size > fb->size) return 1; - /* fall back to name */ - break; - case SORT_TIME: - if (fa->date < fb->date) return -1; - if (fa->date > fb->date) return 1; - /* fall back to name */ - break; - case SORT_CTIME: - if (fa->cdate < fb->cdate) return -1; - if (fa->cdate > fb->cdate) return 1; - /* fall back to name */ - break; - case SORT_EXIFTIME: - if (fa->exifdate < fb->exifdate) return -1; - if (fa->exifdate > fb->exifdate) return 1; - /* fall back to name */ - break; - case SORT_EXIFTIMEDIGITIZED: - if (fa->exifdate_digitized < fb->exifdate_digitized) return -1; - if (fa->exifdate_digitized > fb->exifdate_digitized) return 1; - /* fall back to name */ - break; - case SORT_RATING: - if (fa->rating < fb->rating) return -1; - if (fa->rating > fb->rating) return 1; - /* fall back to name */ - break; - case SORT_CLASS: - if (fa->format_class < fb->format_class) return -1; - if (fa->format_class > fb->format_class) return 1; - /* fall back to name */ - break; - case SORT_NUMBER: - ret = strcmp(fa->collate_key_name_natural, fb->collate_key_name_natural); - if (ret != 0) return ret; - /* fall back to name */ - break; - default: - break; - } - - if (filelist_sort_case) - ret = strcmp(fa->collate_key_name, fb->collate_key_name); - else - ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase); - - if (ret != 0) return ret; - - /* do not return 0 unless the files are really the same - file_data_pool ensures that original_path is unique - */ - return strcmp(fa->original_path, fb->original_path); -} - -gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend) -{ - filelist_sort_method = method; - filelist_sort_ascend = ascend; - return filelist_sort_compare_filedata(fa, fb); -} - -static gint filelist_sort_file_cb(gpointer a, gpointer b) -{ - return filelist_sort_compare_filedata(static_cast(a), static_cast(b)); -} - -GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb) -{ - filelist_sort_method = method; - filelist_sort_ascend = ascend; - filelist_sort_case = case_sensitive; - return g_list_sort(list, cb); -} - -GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb) -{ - filelist_sort_method = method; - filelist_sort_ascend = ascend; - filelist_sort_case = case_sensitive; - return g_list_insert_sorted(list, data, cb); -} - -GList *filelist_sort(GList *list, SortType method, gboolean ascend, gboolean case_sensitive) -{ - return filelist_sort_full(list, method, ascend, case_sensitive, reinterpret_cast(filelist_sort_file_cb)); -} - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" -GList *filelist_insert_sort_unused(GList *list, FileData *fd, SortType method, gboolean ascend) -{ - return filelist_insert_sort_full(list, fd, method, ascend, ascend, (GCompareFunc) filelist_sort_file_cb); + FileData::file_data_unlock_list(list); } -#pragma GCC diagnostic pop - -/* - *----------------------------------------------------------------------------- - * basename hash - grouping of sidecars in filelist - *----------------------------------------------------------------------------- - */ -static GHashTable *file_data_basename_hash_new() -{ - return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, nullptr); -} - -static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd) +gboolean file_data_check_changed_files(FileData *fd) { - GList *list; - gchar *basename = g_strndup(fd->path, fd->extension - fd->path); - - list = static_cast(g_hash_table_lookup(basename_hash, basename)); - - if (!list) - { - DEBUG_1("TG: basename_hash not found for %s",fd->path); - const gchar *parent_extension = registered_extension_from_path(basename); - - if (parent_extension) - { - DEBUG_1("TG: parent extension %s",parent_extension); - gchar *parent_basename = g_strndup(basename, parent_extension - basename); - DEBUG_1("TG: parent basename %s",parent_basename); - auto parent_fd = static_cast(g_hash_table_lookup(file_data_pool, basename)); - if (parent_fd) - { - DEBUG_1("TG: parent fd found"); - list = static_cast(g_hash_table_lookup(basename_hash, parent_basename)); - if (!g_list_find(list, parent_fd)) - { - DEBUG_1("TG: parent fd doesn't fit"); - g_free(parent_basename); - list = nullptr; - } - else - { - g_free(basename); - basename = parent_basename; - fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL); - } - } - } - } - - if (!g_list_find(list, fd)) - { - list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext); - g_hash_table_insert(basename_hash, basename, list); - } - else - { - g_free(basename); - } - return list; -} - -static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash) -{ - file_data_basename_hash_insert(static_cast(basename_hash), static_cast(fd)); -} - -static void file_data_basename_hash_remove_list(gpointer, gpointer value, gpointer) -{ - filelist_free(static_cast(value)); -} - -static void file_data_basename_hash_free(GHashTable *basename_hash) -{ - g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, nullptr); - g_hash_table_destroy(basename_hash); -} - -/* - *----------------------------------------------------------------------------- - * handling sidecars in filelist - *----------------------------------------------------------------------------- - */ - -static GList *filelist_filter_out_sidecars(GList *flist) -{ - GList *work = flist; - GList *flist_filtered = nullptr; - - while (work) - { - auto fd = static_cast(work->data); - - work = work->next; - if (fd->parent) /* remove fd's that are children */ - file_data_unref(fd); - else - flist_filtered = g_list_prepend(flist_filtered, fd); - } - g_list_free(flist); - - return flist_filtered; -} - -static void file_data_basename_hash_to_sidecars(gpointer, gpointer value, gpointer) -{ - auto basename_list = static_cast(value); - file_data_check_sidecars(basename_list); -} - - -static gboolean is_hidden_file(const gchar *name) -{ - if (name[0] != '.') return FALSE; - if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE; - return TRUE; -} - -/* - *----------------------------------------------------------------------------- - * the main filelist function - *----------------------------------------------------------------------------- - */ - -static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks) -{ - DIR *dp; - struct dirent *dir; - gchar *pathl; - GList *dlist = nullptr; - GList *flist = nullptr; - GList *xmp_files = nullptr; - gint (*stat_func)(const gchar *path, struct stat *buf); - GHashTable *basename_hash = nullptr; - - g_assert(files || dirs); - - if (files) *files = nullptr; - if (dirs) *dirs = nullptr; - - pathl = path_from_utf8(dir_path); - if (!pathl) return FALSE; - - dp = opendir(pathl); - if (dp == nullptr) - { - g_free(pathl); - return FALSE; - } - - if (files) basename_hash = file_data_basename_hash_new(); - - if (follow_symlinks) - stat_func = stat; - else - stat_func = lstat; - - while ((dir = readdir(dp)) != nullptr) - { - struct stat ent_sbuf; - const gchar *name = dir->d_name; - gchar *filepath; - - if (!options->file_filter.show_hidden_files && is_hidden_file(name)) - continue; - - filepath = g_build_filename(pathl, name, NULL); - if (stat_func(filepath, &ent_sbuf) >= 0) - { - if (S_ISDIR(ent_sbuf.st_mode)) - { - /* we ignore the .thumbnails dir for cleanliness */ - if (dirs && - (name[0] != '.' || (name[1] != '\0' && (name[1] != '.' || name[2] != '\0'))) && - strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 && - strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 && - strcmp(name, THUMB_FOLDER_LOCAL) != 0) - { - dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE)); - } - } - else - { - if (files && filter_name_exists(name)) - { - FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE); - flist = g_list_prepend(flist, fd); - if (fd->sidecar_priority && !fd->disable_grouping) - { - if (strcmp(fd->extension, ".xmp") != 0) - file_data_basename_hash_insert(basename_hash, fd); - else - xmp_files = g_list_append(xmp_files, fd); - } - } - } - } - else - { - if (errno == EOVERFLOW) - { - log_printf("stat(): EOVERFLOW, skip '%s'", filepath); - } - } - g_free(filepath); - } - - closedir(dp); - - g_free(pathl); - - if (xmp_files) - { - g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash); - g_list_free(xmp_files); - } - - if (dirs) *dirs = dlist; - - if (files) - { - g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, nullptr); - - *files = filelist_filter_out_sidecars(flist); - } - if (basename_hash) file_data_basename_hash_free(basename_hash); - - return TRUE; -} - -gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs) -{ - return filelist_read_real(dir_fd->path, files, dirs, TRUE); -} - -gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs) -{ - return filelist_read_real(dir_fd->path, files, dirs, FALSE); -} - -FileData *file_data_new_group(const gchar *path_utf8) -{ - gchar *dir; - struct stat st; - FileData *fd; - GList *files; - - if (!file_data_pool) - { - file_data_pool = g_hash_table_new(g_str_hash, g_str_equal); - } - - if (!stat_utf8(path_utf8, &st)) - { - st.st_size = 0; - st.st_mtime = 0; - } - - if (S_ISDIR(st.st_mode)) - return file_data_new(path_utf8, &st, TRUE); - - dir = remove_level_from_path(path_utf8); - - filelist_read_real(dir, &files, nullptr, TRUE); - - fd = static_cast(g_hash_table_lookup(file_data_pool, path_utf8)); - if (!fd) fd = file_data_new(path_utf8, &st, TRUE); - if (fd) - { - file_data_ref(fd); - } - - filelist_free(files); - g_free(dir); - return fd; -} - - -void filelist_free(GList *list) -{ - GList *work; - - work = list; - while (work) - { - file_data_unref((FileData *)work->data); - work = work->next; - } - - g_list_free(list); -} - - -GList *filelist_copy(GList *list) -{ - GList *new_list = nullptr; - - for (GList *work = list; work; work = work->next) - { - auto fd = static_cast(work->data); - - new_list = g_list_prepend(new_list, file_data_ref(fd)); - } - - return g_list_reverse(new_list); -} - -GList *filelist_from_path_list(GList *list) -{ - GList *new_list = nullptr; - GList *work; - - work = list; - while (work) - { - gchar *path; - - path = static_cast(work->data); - work = work->next; - - new_list = g_list_prepend(new_list, file_data_new_group(path)); - } - - return g_list_reverse(new_list); -} - -GList *filelist_to_path_list(GList *list) -{ - GList *new_list = nullptr; - GList *work; - - work = list; - while (work) - { - FileData *fd; - - fd = static_cast(work->data); - work = work->next; - - new_list = g_list_prepend(new_list, g_strdup(fd->path)); - } - - return g_list_reverse(new_list); -} - -GList *filelist_filter(GList *list, gboolean is_dir_list) -{ - GList *work; - - if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list; - - work = list; - while (work) - { - auto fd = static_cast(work->data); - const gchar *name = fd->name; - GList *link = work; - work = work->next; - - if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) || - (!is_dir_list && !filter_name_exists(name)) || - (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 || - strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) ) - { - list = g_list_remove_link(list, link); - file_data_unref(fd); - g_list_free(link); - } - } - - return list; -} - -/* - *----------------------------------------------------------------------------- - * filelist recursive - *----------------------------------------------------------------------------- - */ - -static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b) -{ - return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path); -} - -GList *filelist_sort_path(GList *list) -{ - return g_list_sort(list, filelist_sort_path_cb); -} - -static void filelist_recursive_append(GList **list, GList *dirs) -{ - GList *work; - - work = dirs; - while (work) - { - auto fd = static_cast(work->data); - GList *f; - GList *d; - - if (filelist_read(fd, &f, &d)) - { - f = filelist_filter(f, FALSE); - f = filelist_sort_path(f); - *list = g_list_concat(*list, f); - - d = filelist_filter(d, TRUE); - d = filelist_sort_path(d); - filelist_recursive_append(list, d); - filelist_free(d); - } - - work = work->next; - } -} - -static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend, gboolean case_sensitive) -{ - GList *work; - - work = dirs; - while (work) - { - auto fd = static_cast(work->data); - GList *f; - GList *d; - - if (filelist_read(fd, &f, &d)) - { - f = filelist_filter(f, FALSE); - f = filelist_sort_full(f, method, ascend, case_sensitive, reinterpret_cast(filelist_sort_file_cb)); - *list = g_list_concat(*list, f); - - d = filelist_filter(d, TRUE); - d = filelist_sort_path(d); - filelist_recursive_append_full(list, d, method, ascend, case_sensitive); - filelist_free(d); - } - - work = work->next; - } -} - -GList *filelist_recursive(FileData *dir_fd) -{ - GList *list; - GList *d; - - if (!filelist_read(dir_fd, &list, &d)) return nullptr; - list = filelist_filter(list, FALSE); - list = filelist_sort_path(list); - - d = filelist_filter(d, TRUE); - d = filelist_sort_path(d); - filelist_recursive_append(&list, d); - filelist_free(d); - - return list; -} - -GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend, gboolean case_sensitive) -{ - GList *list; - GList *d; - - if (!filelist_read(dir_fd, &list, &d)) return nullptr; - list = filelist_filter(list, FALSE); - list = filelist_sort_full(list, method, ascend, case_sensitive, reinterpret_cast(filelist_sort_file_cb)); - - d = filelist_filter(d, TRUE); - d = filelist_sort_path(d); - filelist_recursive_append_full(&list, d, method, ascend, case_sensitive); - filelist_free(d); - - return list; -} - -/* - *----------------------------------------------------------------------------- - * file modification support - *----------------------------------------------------------------------------- - */ - - -void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd) -{ - if (!fdci && fd) fdci = fd->change; - - if (!fdci) return; - - g_free(fdci->source); - g_free(fdci->dest); - - g_free(fdci); - - if (fd) fd->change = nullptr; -} - -static gboolean file_data_can_write_directly(FileData *fd) -{ - return filter_name_is_writable(fd->extension); -} - -static gboolean file_data_can_write_sidecar(FileData *fd) -{ - return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension); -} - -gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only) -{ - gchar *sidecar_path = nullptr; - GList *work; - - if (!file_data_can_write_sidecar(fd)) return nullptr; - - work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files; - gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL); - while (work) - { - auto sfd = static_cast(work->data); - work = work->next; - if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0) - { - sidecar_path = g_strdup(sfd->path); - break; - } - } - g_free(extended_extension); - - if (!existing_only && !sidecar_path) - { - if (options->metadata.sidecar_extended_name) - sidecar_path = g_strconcat(fd->path, ".xmp", NULL); - else - { - gchar *base = g_strndup(fd->path, fd->extension - fd->path); - sidecar_path = g_strconcat(base, ".xmp", NULL); - g_free(base); - } - } - - return sidecar_path; -} - -/* - * marks and orientation - */ - -static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE]; -static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE]; -static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE]; -static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE]; - -gboolean file_data_get_mark(FileData *fd, gint n) -{ - gboolean valid = (fd->valid_marks & (1 << n)); - - if (file_data_get_mark_func[n] && !valid) - { - guint old = fd->marks; - gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]); - - if (!value != !(fd->marks & (1 << n))) - { - fd->marks = fd->marks ^ (1 << n); - } - - fd->valid_marks |= (1 << n); - if (old && !fd->marks) /* keep files with non-zero marks in memory */ - { - file_data_unref(fd); - } - else if (!old && fd->marks) - { - file_data_ref(fd); - } - } - - return !!(fd->marks & (1 << n)); -} - -guint file_data_get_marks(FileData *fd) -{ - gint i; - for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i); - return fd->marks; -} - -void file_data_set_mark(FileData *fd, gint n, gboolean value) -{ - guint old; - if (!value == !file_data_get_mark(fd, n)) return; - - if (file_data_set_mark_func[n]) - { - (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]); - } - - old = fd->marks; - - fd->marks = fd->marks ^ (1 << n); - - if (old && !fd->marks) /* keep files with non-zero marks in memory */ - { - file_data_unref(fd); - } - else if (!old && fd->marks) - { - file_data_ref(fd); - } - - file_data_increment_version(fd); - file_data_send_notification(fd, NOTIFY_MARKS); -} - -gboolean file_data_filter_marks(FileData *fd, guint filter) -{ - gint i; - for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i); - return ((fd->marks & filter) == filter); -} - -GList *file_data_filter_marks_list(GList *list, guint filter) -{ - GList *work; - - work = list; - while (work) - { - auto fd = static_cast(work->data); - GList *link = work; - work = work->next; - - if (!file_data_filter_marks(fd, filter)) - { - list = g_list_remove_link(list, link); - file_data_unref(fd); - g_list_free(link); - } - } - - return list; -} - -gboolean file_data_mark_to_selection(FileData *fd, gint mark, MarkToSelectionMode mode, gboolean selected) -{ - gint n = mark - 1; - gboolean mark_val = file_data_get_mark(fd, n); - - switch (mode) - { - case MTS_MODE_MINUS: return !mark_val && selected; - case MTS_MODE_SET: return mark_val; - case MTS_MODE_OR: return mark_val || selected; - case MTS_MODE_AND: return mark_val && selected; - } - - return selected; // arbitrary value, we shouldn't get here -} - -void file_data_selection_to_mark(FileData *fd, gint mark, SelectionToMarkMode mode) -{ - gint n = mark - 1; - - switch (mode) - { - case STM_MODE_RESET: file_data_set_mark(fd, n, FALSE); break; - case STM_MODE_SET: file_data_set_mark(fd, n, TRUE); break; - case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n)); break; - } -} - -gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter) -{ - return g_regex_match(filter, fd->name, static_cast(0), nullptr); -} - -GList *file_data_filter_file_filter_list(GList *list, GRegex *filter) -{ - GList *work; - - work = list; - while (work) - { - auto fd = static_cast(work->data); - GList *link = work; - work = work->next; - - if (!file_data_filter_file_filter(fd, filter)) - { - list = g_list_remove_link(list, link); - file_data_unref(fd); - g_list_free(link); - } - } - - return list; -} - -static gboolean file_data_filter_class(FileData *fd, guint filter) -{ - gint i; - - for (i = 0; i < FILE_FORMAT_CLASSES; i++) - { - if (filter & (1 << i)) - { - if (static_cast(i) == filter_file_get_class(fd->path)) - { - return TRUE; - } - } - } - - return FALSE; -} - -GList *file_data_filter_class_list(GList *list, guint filter) -{ - GList *work; - - work = list; - while (work) - { - auto fd = static_cast(work->data); - GList *link = work; - work = work->next; - - if (!file_data_filter_class(fd, filter)) - { - list = g_list_remove_link(list, link); - file_data_unref(fd); - g_list_free(link); - } - } - - return list; -} - -static void file_data_notify_mark_func(gpointer, gpointer value, gpointer) -{ - auto fd = static_cast(value); - file_data_increment_version(fd); - file_data_send_notification(fd, NOTIFY_MARKS); -} - -gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify) -{ - if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE; - - if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]); - - file_data_get_mark_func[n] = get_mark_func; - file_data_set_mark_func[n] = set_mark_func; - file_data_mark_func_data[n] = data; - file_data_destroy_mark_func[n] = notify; - - if (get_mark_func && file_data_pool) - { - /* this effectively changes all known files */ - g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, nullptr); - } - - return TRUE; -} - -void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data) -{ - if (get_mark_func) *get_mark_func = file_data_get_mark_func[n]; - if (set_mark_func) *set_mark_func = file_data_set_mark_func[n]; - if (data) *data = file_data_mark_func_data[n]; -} - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" -gint file_data_get_user_orientation_unused(FileData *fd) -{ - return fd->user_orientation; -} - -void file_data_set_user_orientation_unused(FileData *fd, gint value) -{ - if (fd->user_orientation == value) return; - - fd->user_orientation = value; - file_data_increment_version(fd); - file_data_send_notification(fd, NOTIFY_ORIENTATION); -} -#pragma GCC diagnostic pop - - -/* - * file_data - operates on the given fd - * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent - */ - - -/* return list of sidecar file extensions in a string */ -gchar *file_data_sc_list_to_string(FileData *fd) -{ - GList *work; - GString *result = g_string_new(""); - - work = fd->sidecar_files; - while (work) - { - auto sfd = static_cast(work->data); - - result = g_string_append(result, "+ "); - result = g_string_append(result, sfd->extension); - work = work->next; - if (work) result = g_string_append_c(result, ' '); - } - - return g_string_free(result, FALSE); -} - - - -/* - * add FileDataChangeInfo (see typedefs.h) for the given operation - * uses file_data_add_change_info - * - * fails if the fd->change already exists - change operations can't run in parallel - * fd->change_info works as a lock - * - * dest can be NULL - in this case the current name is used for now, it will - * be changed later - */ - -/* - FileDataChangeInfo types: - COPY - MOVE - path is changed, name may be changed too - RENAME - path remains unchanged, name is changed - extension should remain (FIXME should we allow editing extension? it will make problems with grouping) - sidecar names are changed too, extensions are not changed - DELETE - UPDATE - file size, date or grouping has been changed -*/ - -gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest) -{ - FileDataChangeInfo *fdci; - - if (fd->change) return FALSE; - - fdci = g_new0(FileDataChangeInfo, 1); - - fdci->type = type; - - if (src) - fdci->source = g_strdup(src); - else - fdci->source = g_strdup(fd->path); - - if (dest) - fdci->dest = g_strdup(dest); - - fd->change = fdci; - - return TRUE; -} - -static void file_data_planned_change_remove(FileData *fd) -{ - if (file_data_planned_change_hash && - (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME)) - { - if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd) - { - DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path); - g_hash_table_remove(file_data_planned_change_hash, fd->change->dest); - file_data_unref(fd); - if (g_hash_table_size(file_data_planned_change_hash) == 0) - { - g_hash_table_destroy(file_data_planned_change_hash); - file_data_planned_change_hash = nullptr; - DEBUG_1("planned change: empty"); - } - } - } -} - - -void file_data_free_ci(FileData *fd) -{ - FileDataChangeInfo *fdci = fd->change; - - if (!fdci) return; - - file_data_planned_change_remove(fd); - - if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE); - - g_free(fdci->source); - g_free(fdci->dest); - - g_free(fdci); - - fd->change = nullptr; -} - -void file_data_set_regroup_when_finished(FileData *fd, gboolean enable) -{ - FileDataChangeInfo *fdci = fd->change; - if (!fdci) return; - fdci->regroup_when_finished = enable; -} - -static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type) -{ - GList *work; - - if (fd->parent) fd = fd->parent; - - if (fd->change) return FALSE; - - work = fd->sidecar_files; - while (work) - { - auto sfd = static_cast(work->data); - - if (sfd->change) return FALSE; - work = work->next; - } - - file_data_add_ci(fd, type, nullptr, nullptr); - - work = fd->sidecar_files; - while (work) - { - auto sfd = static_cast(work->data); - - file_data_add_ci(sfd, type, nullptr, nullptr); - work = work->next; - } - - return TRUE; + return fd->file_data_check_changed_files(fd); } -static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type) + +void file_data_increment_version(FileData *fd) { - GList *work; + fd->file_data_increment_version(fd); +} - if (fd->parent) fd = fd->parent; - if (!fd->change || fd->change->type != type) return FALSE; +void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd) +{ + fd->file_data_change_info_free(fdci, fd); +} - work = fd->sidecar_files; - while (work) - { - auto sfd = static_cast(work->data); - if (!sfd->change || sfd->change->type != type) return FALSE; - work = work->next; - } +void file_data_disable_grouping(FileData *fd, gboolean disable) +{ + fd->file_data_disable_grouping(fd, disable); +} - return TRUE; +void file_data_disable_grouping_list(GList *fd_list, gboolean disable) +{ + FileData::file_data_disable_grouping_list(fd_list, disable); } -gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path) +gint filelist_sort_compare_filedata(FileData *fa, FileData *fb) { - if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE; - file_data_sc_update_ci_copy(fd, dest_path); - return TRUE; + return FileData::filelist_sort_compare_filedata(fa, fb); } -gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path) +gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend) { - if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE; - file_data_sc_update_ci_move(fd, dest_path); - return TRUE; + return FileData::filelist_sort_compare_filedata_full(fa, fb, method, ascend); } -gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path) +GList *filelist_sort(GList *list, SortType method, gboolean ascend, gboolean case_sensitive) { - if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE; - file_data_sc_update_ci_rename(fd, dest_path); - return TRUE; + return FileData::filelist_sort(list, method, ascend, case_sensitive); } -gboolean file_data_sc_add_ci_delete(FileData *fd) +GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb) { - return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE); + return FileData::filelist_sort_full(list, method, ascend, case_sensitive, cb); } -gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path) +GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb) { - if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE; - file_data_sc_update_ci_unspecified(fd, dest_path); - return TRUE; + return FileData::filelist_insert_sort_full(list, data, method, ascend, case_sensitive, cb); } -gboolean file_data_add_ci_write_metadata(FileData *fd) + +gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs) { - return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, nullptr, nullptr); + return dir_fd->filelist_read(dir_fd, files, dirs); } -void file_data_sc_free_ci(FileData *fd) +gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs) { - GList *work; + return dir_fd->filelist_read_lstat(dir_fd, files, dirs); +} - if (fd->parent) fd = fd->parent; +void filelist_free(GList *list) +{ + FileData::filelist_free(list); +} - file_data_free_ci(fd); +GList *filelist_copy(GList *list) +{ + return FileData::filelist_copy(list); +} - work = fd->sidecar_files; - while (work) - { - auto sfd = static_cast(work->data); +GList *filelist_from_path_list(GList *list) +{ + return FileData::filelist_from_path_list(list); +} - file_data_free_ci(sfd); - work = work->next; - } +GList *filelist_to_path_list(GList *list) +{ + return FileData::filelist_to_path_list(list); } -gboolean file_data_sc_add_ci_delete_list(GList *fd_list) + +GList *filelist_filter(GList *list, gboolean is_dir_list) { - GList *work; - gboolean ret = TRUE; + return FileData::filelist_filter(list, is_dir_list); +} - work = fd_list; - while (work) - { - auto fd = static_cast(work->data); - if (!file_data_sc_add_ci_delete(fd)) ret = FALSE; - work = work->next; - } +GList *filelist_sort_path(GList *list) +{ + return FileData::filelist_sort_path(list); +} - return ret; +GList *filelist_recursive(FileData *dir_fd) +{ + return FileData::filelist_recursive(dir_fd); } -static void file_data_sc_revert_ci_list(GList *fd_list) +GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend, gboolean case_sensitive) { - GList *work; + return FileData::filelist_recursive_full(dir_fd, method, ascend, case_sensitive); +} - work = fd_list; - while (work) - { - auto fd = static_cast(work->data); - file_data_sc_free_ci(fd); - work = work->prev; - } +using FileDataGetMarkFunc = gboolean (*)(FileData *, gint, gpointer); +using FileDataSetMarkFunc = gboolean (*)(FileData *, gint, gboolean, gpointer); +gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify) +{ + return FileData::file_data_register_mark_func(n, get_mark_func, set_mark_func, data, notify); } -static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *)) +void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data) { - GList *work; - - work = fd_list; - while (work) - { - auto fd = static_cast(work->data); + FileData::file_data_get_registered_mark_func(n, get_mark_func, set_mark_func, data); +} - if (!func(fd, dest)) - { - file_data_sc_revert_ci_list(work->prev); - return FALSE; - } - work = work->next; - } - return TRUE; -} -gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest) +gboolean file_data_get_mark(FileData *fd, gint n) { - return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy); + return fd->file_data_get_mark(fd, n); } -gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest) +guint file_data_get_marks(FileData *fd) { - return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move); + return fd->file_data_get_marks(fd); } -gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest) +void file_data_set_mark(FileData *fd, gint n, gboolean value) { - return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename); + fd->file_data_set_mark(fd, n, value); } -gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest) +gboolean file_data_filter_marks(FileData *fd, guint filter) { - return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified); + return fd->file_data_filter_marks(fd, filter); } -gboolean file_data_add_ci_write_metadata_list(GList *fd_list) +GList *file_data_filter_marks_list(GList *list, guint filter) { - GList *work; - gboolean ret = TRUE; - - work = fd_list; - while (work) - { - auto fd = static_cast(work->data); + return FileData::file_data_filter_marks_list(list, filter); +} - if (!file_data_add_ci_write_metadata(fd)) ret = FALSE; - work = work->next; - } - return ret; +gboolean file_data_mark_to_selection(FileData *fd, gint mark, MarkToSelectionMode mode, gboolean selected) +{ + return fd->file_data_mark_to_selection(fd, mark, mode, selected); } -void file_data_free_ci_list(GList *fd_list) +void file_data_selection_to_mark(FileData *fd, gint mark, SelectionToMarkMode mode) { - GList *work; + fd->file_data_selection_to_mark(fd, mark, mode); +} - work = fd_list; - while (work) - { - auto fd = static_cast(work->data); - file_data_free_ci(fd); - work = work->next; - } +gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter) +{ + return fd->file_data_filter_file_filter(fd, filter); } -void file_data_sc_free_ci_list(GList *fd_list) +GList *file_data_filter_file_filter_list(GList *list, GRegex *filter) { - GList *work; + return FileData::file_data_filter_file_filter_list(list, filter); +} - work = fd_list; - while (work) - { - auto fd = static_cast(work->data); - file_data_sc_free_ci(fd); - work = work->next; - } +GList *file_data_filter_class_list(GList *list, guint filter) +{ + return FileData::file_data_filter_class_list(list, filter); } -/* - * update existing fd->change, it will be used from dialog callbacks for interactive editing - * fails if fd->change does not exist or the change type does not match - */ -static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path) +gchar *file_data_sc_list_to_string(FileData *fd) { - FileDataChangeType type = fd->change->type; + return fd->file_data_sc_list_to_string(fd); +} - if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME) - { - FileData *ofd; - if (!file_data_planned_change_hash) - file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal); +gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only) +{ + return fd->file_data_get_sidecar_path(fd, existing_only); +} - if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd) - { - DEBUG_1("planned change: removing %s -> %s", old_path, fd->path); - g_hash_table_remove(file_data_planned_change_hash, old_path); - file_data_unref(fd); - } - ofd = static_cast(g_hash_table_lookup(file_data_planned_change_hash, new_path)); - if (ofd != fd) - { - if (ofd) - { - DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path); - g_hash_table_remove(file_data_planned_change_hash, new_path); - file_data_unref(ofd); - } - DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path); - file_data_ref(fd); - g_hash_table_insert(file_data_planned_change_hash, new_path, fd); - } - } +gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest) +{ + return fd->file_data_add_ci(fd, type, src, dest); } -static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path) +gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path) { - gchar *old_path = fd->change->dest; - - fd->change->dest = g_strdup(dest_path); - file_data_update_planned_change_hash(fd, old_path, fd->change->dest); - g_free(old_path); + return fd->file_data_sc_add_ci_copy(fd, dest_path); } -static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path) +gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path) { - const gchar *extension = registered_extension_from_path(fd->change->source); - gchar *base = remove_extension_from_path(dest_path); - gchar *old_path = fd->change->dest; - - fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL); - file_data_update_planned_change_hash(fd, old_path, fd->change->dest); - - g_free(old_path); - g_free(base); + return fd->file_data_sc_add_ci_move(fd, dest_path); } -static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path) +gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path) { - GList *work; - gchar *dest_path_full = nullptr; - - if (fd->parent) fd = fd->parent; - - if (!dest_path) - { - dest_path = fd->path; - } - else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */ - { - gchar *dir = remove_level_from_path(fd->path); - - dest_path_full = g_build_filename(dir, dest_path, NULL); - g_free(dir); - dest_path = dest_path_full; - } - else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */ - { - dest_path_full = g_build_filename(dest_path, fd->name, NULL); - dest_path = dest_path_full; - } + return fd->file_data_sc_add_ci_rename(fd, dest_path); +} - file_data_update_ci_dest(fd, dest_path); +gboolean file_data_sc_add_ci_delete(FileData *fd) +{ + return fd->file_data_sc_add_ci_delete(fd); +} - work = fd->sidecar_files; - while (work) - { - auto sfd = static_cast(work->data); +gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path) +{ + return fd->file_data_sc_add_ci_unspecified(fd, dest_path); +} - file_data_update_ci_dest_preserve_ext(sfd, dest_path); - work = work->next; - } - g_free(dest_path_full); +gboolean file_data_sc_add_ci_delete_list(GList *fd_list) +{ + return FileData::file_data_sc_add_ci_delete_list(fd_list); } -static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type) +gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest) { - if (!file_data_sc_check_ci(fd, type)) return FALSE; - file_data_sc_update_ci(fd, dest_path); - return TRUE; + return FileData::file_data_sc_add_ci_copy_list(fd_list, dest); } -gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path) +gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest) { - return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY); + return FileData::file_data_sc_add_ci_move_list(fd_list, dest); } -gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path) +gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest) { - return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE); + return FileData::file_data_sc_add_ci_rename_list(fd_list, dest); } -gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path) +gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest) { - return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME); + return FileData::file_data_sc_add_ci_unspecified_list(fd_list, dest); } -gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path) +gboolean file_data_add_ci_write_metadata(FileData *fd) { - return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED); + return fd->file_data_add_ci_write_metadata(fd); } -static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list, - const gchar *dest, - gboolean (*func)(FileData *, const gchar *)) +gboolean file_data_add_ci_write_metadata_list(GList *fd_list) { - GList *work; - gboolean ret = TRUE; - - work = fd_list; - while (work) - { - auto fd = static_cast(work->data); - - if (!func(fd, dest)) ret = FALSE; - work = work->next; - } - - return ret; + return FileData::file_data_add_ci_write_metadata_list(fd_list); } -gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest) + +gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest) { - return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move); + return FileData::file_data_sc_update_ci_copy_list(fd_list, dest); } -gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest) +gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest) { - return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy); + return FileData::file_data_sc_update_ci_move_list(fd_list, dest); } gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest) { - return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified); + return FileData::file_data_sc_update_ci_unspecified_list(fd_list, dest); } -/* - * verify source and dest paths - dest image exists, etc. - * it should detect all possible problems with the planned operation - */ -gint file_data_verify_ci(FileData *fd, GList *list) +gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path) { - gint ret = CHANGE_OK; - gchar *dir; - GList *work = nullptr; - FileData *fd1 = nullptr; - - if (!fd->change) - { - DEBUG_1("Change checked: no change info: %s", fd->path); - return ret; - } - - if (!isname(fd->path)) - { - /* this probably should not happen */ - ret |= CHANGE_NO_SRC; - DEBUG_1("Change checked: file does not exist: %s", fd->path); - return ret; - } - - dir = remove_level_from_path(fd->path); - - if (fd->change->type != FILEDATA_CHANGE_DELETE && - fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */ - fd->change->type != FILEDATA_CHANGE_RENAME && - fd->change->type != FILEDATA_CHANGE_WRITE_METADATA && - fd->modified_xmp) - { - ret |= CHANGE_WARN_UNSAVED_META; - DEBUG_1("Change checked: unsaved metadata: %s", fd->path); - } - - if (fd->change->type != FILEDATA_CHANGE_DELETE && - fd->change->type != FILEDATA_CHANGE_WRITE_METADATA && - !access_file(fd->path, R_OK)) - { - ret |= CHANGE_NO_READ_PERM; - DEBUG_1("Change checked: no read permission: %s", fd->path); - } - else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) && - !access_file(dir, W_OK)) - { - ret |= CHANGE_NO_WRITE_PERM_DIR; - DEBUG_1("Change checked: source dir is readonly: %s", fd->path); - } - else if (fd->change->type != FILEDATA_CHANGE_COPY && - fd->change->type != FILEDATA_CHANGE_UNSPECIFIED && - fd->change->type != FILEDATA_CHANGE_WRITE_METADATA && - !access_file(fd->path, W_OK)) - { - ret |= CHANGE_WARN_NO_WRITE_PERM; - DEBUG_1("Change checked: no write permission: %s", fd->path); - } - /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/... - - that means that there are no hard errors and warnings can be disabled - - the destination is determined during the check - */ - else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA) - { - /* determine destination file */ - gboolean have_dest = FALSE; - gchar *dest_dir = nullptr; - - if (options->metadata.save_in_image_file) - { - if (file_data_can_write_directly(fd)) - { - /* we can write the file directly */ - if (access_file(fd->path, W_OK)) - { - have_dest = TRUE; - } - else - { - if (options->metadata.warn_on_write_problems) - { - ret |= CHANGE_WARN_NO_WRITE_PERM; - DEBUG_1("Change checked: file is not writable: %s", fd->path); - } - } - } - else if (file_data_can_write_sidecar(fd)) - { - /* we can write sidecar */ - gchar *sidecar = file_data_get_sidecar_path(fd, FALSE); - if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK))) - { - file_data_update_ci_dest(fd, sidecar); - have_dest = TRUE; - } - else - { - if (options->metadata.warn_on_write_problems) - { - ret |= CHANGE_WARN_NO_WRITE_PERM; - DEBUG_1("Change checked: file is not writable: %s", sidecar); - } - } - g_free(sidecar); - } - } - - if (!have_dest) - { - /* write private metadata file under ~/.geeqie */ - - /* If an existing metadata file exists, we will try writing to - * it's location regardless of the user's preference. - */ - gchar *metadata_path = nullptr; -#if HAVE_EXIV2 - /* but ignore XMP if we are not able to write it */ - metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path); -#endif - if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path); - - if (metadata_path && !access_file(metadata_path, W_OK)) - { - g_free(metadata_path); - metadata_path = nullptr; - } - - if (!metadata_path) - { - mode_t mode = 0755; - - dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode); - if (recursive_mkdir_if_not_exists(dest_dir, mode)) - { - gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL); - - metadata_path = g_build_filename(dest_dir, filename, NULL); - g_free(filename); - } - } - if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK))) - { - file_data_update_ci_dest(fd, metadata_path); - } - else - { - ret |= CHANGE_NO_WRITE_PERM_DEST; - DEBUG_1("Change checked: file is not writable: %s", metadata_path); - } - g_free(metadata_path); - } - g_free(dest_dir); - } - - if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA) - { - gboolean same; - gchar *dest_dir; - - same = (strcmp(fd->path, fd->change->dest) == 0); - - if (!same) - { - const gchar *dest_ext = registered_extension_from_path(fd->change->dest); - if (!dest_ext) dest_ext = ""; - if (!options->file_filter.disable_file_extension_checks) - { - if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0) - { - ret |= CHANGE_WARN_CHANGED_EXT; - DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest); - } - } - } - else - { - if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */ - { - ret |= CHANGE_WARN_SAME; - DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest); - } - } - - dest_dir = remove_level_from_path(fd->change->dest); - - if (!isdir(dest_dir)) - { - ret |= CHANGE_NO_DEST_DIR; - DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest); - } - else if (!access_file(dest_dir, W_OK)) - { - ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR; - DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest); - } - else if (!same) - { - if (isfile(fd->change->dest)) - { - if (!access_file(fd->change->dest, W_OK)) - { - ret |= CHANGE_NO_WRITE_PERM_DEST; - DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest); - } - else - { - ret |= CHANGE_WARN_DEST_EXISTS; - DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest); - } - } - else if (isdir(fd->change->dest)) - { - ret |= CHANGE_DEST_EXISTS; - DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest); - } - } - - g_free(dest_dir); - } - - /* During a rename operation, check if another planned destination file has - * the same filename - */ - if(fd->change->type == FILEDATA_CHANGE_RENAME || - fd->change->type == FILEDATA_CHANGE_COPY || - fd->change->type == FILEDATA_CHANGE_MOVE) - { - work = list; - while (work) - { - fd1 = static_cast(work->data); - work = work->next; - if (fd1 != nullptr && fd != fd1 ) - { - if (!strcmp(fd->change->dest, fd1->change->dest)) - { - ret |= CHANGE_DUPLICATE_DEST; - } - } - } - } - - fd->change->error = ret; - if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path); - - g_free(dir); - return ret; + return fd->file_data_sc_update_ci_copy(fd, dest_path); } - -gint file_data_sc_verify_ci(FileData *fd, GList *list) +gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path) { - GList *work; - gint ret; + return fd->file_data_sc_update_ci_move(fd, dest_path); +} - ret = file_data_verify_ci(fd, list); +gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path) +{ + return fd->file_data_sc_update_ci_rename(fd, dest_path); +} - work = fd->sidecar_files; - while (work) - { - auto sfd = static_cast(work->data); +gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path) +{ + return fd->file_data_sc_update_ci_unspecified(fd, dest_path); +} - ret |= file_data_verify_ci(sfd, list); - work = work->next; - } - return ret; +gchar *file_data_get_error_string(gint error) +{ + return FileData::file_data_get_error_string(error); } -gchar *file_data_get_error_string(gint error) + +gint file_data_verify_ci(FileData *fd, GList *list) { - GString *result = g_string_new(""); - - if (error & CHANGE_NO_SRC) - { - if (result->len > 0) g_string_append(result, ", "); - g_string_append(result, _("file or directory does not exist")); - } - - if (error & CHANGE_DEST_EXISTS) - { - if (result->len > 0) g_string_append(result, ", "); - g_string_append(result, _("destination already exists")); - } - - if (error & CHANGE_NO_WRITE_PERM_DEST) - { - if (result->len > 0) g_string_append(result, ", "); - g_string_append(result, _("destination can't be overwritten")); - } - - if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR) - { - if (result->len > 0) g_string_append(result, ", "); - g_string_append(result, _("destination directory is not writable")); - } - - if (error & CHANGE_NO_DEST_DIR) - { - if (result->len > 0) g_string_append(result, ", "); - g_string_append(result, _("destination directory does not exist")); - } - - if (error & CHANGE_NO_WRITE_PERM_DIR) - { - if (result->len > 0) g_string_append(result, ", "); - g_string_append(result, _("source directory is not writable")); - } - - if (error & CHANGE_NO_READ_PERM) - { - if (result->len > 0) g_string_append(result, ", "); - g_string_append(result, _("no read permission")); - } - - if (error & CHANGE_WARN_NO_WRITE_PERM) - { - if (result->len > 0) g_string_append(result, ", "); - g_string_append(result, _("file is readonly")); - } - - if (error & CHANGE_WARN_DEST_EXISTS) - { - if (result->len > 0) g_string_append(result, ", "); - g_string_append(result, _("destination already exists and will be overwritten")); - } - - if (error & CHANGE_WARN_SAME) - { - if (result->len > 0) g_string_append(result, ", "); - g_string_append(result, _("source and destination are the same")); - } - - if (error & CHANGE_WARN_CHANGED_EXT) - { - if (result->len > 0) g_string_append(result, ", "); - g_string_append(result, _("source and destination have different extension")); - } - - if (error & CHANGE_WARN_UNSAVED_META) - { - if (result->len > 0) g_string_append(result, ", "); - g_string_append(result, _("there are unsaved metadata changes for the file")); - } - - if (error & CHANGE_DUPLICATE_DEST) - { - if (result->len > 0) g_string_append(result, ", "); - g_string_append(result, _("another destination file has the same filename")); - } - - return g_string_free(result, FALSE); + return fd->file_data_verify_ci(fd, list); } gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars) { - GList *work; - gint all_errors = 0; - gint common_errors = ~0; - gint num; - gint *errors; - gint i; - - if (!list) return 0; - - num = g_list_length(list); - errors = g_new(int, num); - work = list; - i = 0; - while (work) - { - FileData *fd; - gint error; - - fd = static_cast(work->data); - work = work->next; - - error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list); - all_errors |= error; - common_errors &= error; - - errors[i] = error; - - i++; - } - - if (desc && all_errors) - { - GList *work; - GString *result = g_string_new(""); - - if (common_errors) - { - gchar *str = file_data_get_error_string(common_errors); - g_string_append(result, str); - g_string_append(result, "\n"); - g_free(str); - } - - work = list; - i = 0; - while (work) - { - FileData *fd; - gint error; - - fd = static_cast(work->data); - work = work->next; - - error = errors[i] & ~common_errors; - - if (error) - { - gchar *str = file_data_get_error_string(error); - g_string_append_printf(result, "%s: %s\n", fd->name, str); - g_free(str); - } - i++; - } - *desc = g_string_free(result, FALSE); - } - - g_free(errors); - return all_errors; + return FileData::file_data_verify_ci_list(list, desc, with_sidecars); } -/* - * perform the change described by FileFataChangeInfo - * it is used for internal operations, - * this function actually operates with files on the filesystem - * it should implement safe delete - */ - -static gboolean file_data_perform_move(FileData *fd) +gboolean file_data_perform_ci(FileData *fd) { - g_assert(!strcmp(fd->change->source, fd->path)); - return move_file(fd->change->source, fd->change->dest); + return fd->file_data_perform_ci(fd); } -static gboolean file_data_perform_copy(FileData *fd) +gboolean file_data_apply_ci(FileData *fd) { - g_assert(!strcmp(fd->change->source, fd->path)); - return copy_file(fd->change->source, fd->change->dest); + return fd->file_data_apply_ci(fd); } -static gboolean file_data_perform_delete(FileData *fd) +void file_data_free_ci(FileData *fd) { - if (isdir(fd->path) && !islink(fd->path)) - return rmdir_utf8(fd->path); - - if (options->file_ops.safe_delete_enable) - return file_util_safe_unlink(fd->path); - - return unlink_file(fd->path); + fd->file_data_free_ci(fd); } -gboolean file_data_perform_ci(FileData *fd) +void file_data_free_ci_list(GList *fd_list) { - /** @FIXME When a directory that is a symbolic link is deleted, - * at this point fd->change is null because no FileDataChangeInfo - * has been set up. Therefore there is a seg. fault. - * This code simply aborts the delete. - */ - if (!fd->change) - { - return FALSE; - } - - FileDataChangeType type = fd->change->type; - - switch (type) - { - case FILEDATA_CHANGE_MOVE: - return file_data_perform_move(fd); - case FILEDATA_CHANGE_COPY: - return file_data_perform_copy(fd); - case FILEDATA_CHANGE_RENAME: - return file_data_perform_move(fd); /* the same as move */ - case FILEDATA_CHANGE_DELETE: - return file_data_perform_delete(fd); - case FILEDATA_CHANGE_WRITE_METADATA: - return metadata_write_perform(fd); - case FILEDATA_CHANGE_UNSPECIFIED: - /* nothing to do here */ - break; - } - return TRUE; + FileData::file_data_free_ci_list(fd_list); } - -gboolean file_data_sc_perform_ci(FileData *fd) +void file_data_set_regroup_when_finished(FileData *fd, gboolean enable) { - GList *work; - gboolean ret = TRUE; - FileDataChangeType type = fd->change->type; - - if (!file_data_sc_check_ci(fd, type)) return FALSE; - - work = fd->sidecar_files; - while (work) - { - auto sfd = static_cast(work->data); - - if (!file_data_perform_ci(sfd)) ret = FALSE; - work = work->next; - } + fd->file_data_set_regroup_when_finished(fd, enable); +} - if (!file_data_perform_ci(fd)) ret = FALSE; - return ret; +gint file_data_sc_verify_ci(FileData *fd, GList *list) +{ + return fd->file_data_sc_verify_ci(fd, list); } -/* - * updates FileData structure according to FileDataChangeInfo - */ -gboolean file_data_apply_ci(FileData *fd) +gboolean file_data_sc_perform_ci(FileData *fd) { - FileDataChangeType type = fd->change->type; - - /** @FIXME delete ?*/ - if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME) - { - DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path); - file_data_planned_change_remove(fd); - - if (g_hash_table_lookup(file_data_pool, fd->change->dest)) - { - /* this change overwrites another file which is already known to other modules - renaming fd would create duplicate FileData structure - the best thing we can do is nothing - */ - /** @FIXME maybe we could copy stuff like marks - */ - DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path); - } - else - { - file_data_set_path(fd, fd->change->dest); - } - } - file_data_increment_version(fd); - file_data_send_notification(fd, NOTIFY_CHANGE); - - return TRUE; + return fd->file_data_sc_perform_ci(fd); } gboolean file_data_sc_apply_ci(FileData *fd) { - GList *work; - FileDataChangeType type = fd->change->type; - - if (!file_data_sc_check_ci(fd, type)) return FALSE; - - work = fd->sidecar_files; - while (work) - { - auto sfd = static_cast(work->data); - - file_data_apply_ci(sfd); - work = work->next; - } - - file_data_apply_ci(fd); - - return TRUE; + return fd->file_data_sc_apply_ci(fd); } -static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd) +void file_data_sc_free_ci(FileData *fd) { - GList *work; - if (fd->parent) fd = fd->parent; - if (!g_list_find(list, fd)) return FALSE; - - work = fd->sidecar_files; - while (work) - { - if (!g_list_find(list, work->data)) return FALSE; - work = work->next; - } - return TRUE; + fd->file_data_sc_free_ci(fd); } -GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list) +void file_data_sc_free_ci_list(GList *fd_list) { - GList *out = nullptr; - GList *work = list; - - /* change partial groups to independent files */ - if (ungroup) - { - while (work) - { - auto fd = static_cast(work->data); - work = work->next; - - if (!file_data_list_contains_whole_group(list, fd)) - { - file_data_disable_grouping(fd, TRUE); - if (ungrouped_list) - { - *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd)); - } - } - } - } - - /* remove sidecars from the list, - they can be still accessed via main_fd->sidecar_files */ - work = list; - while (work) - { - auto fd = static_cast(work->data); - work = work->next; - - if (!fd->parent || - (!ungroup && !file_data_list_contains_whole_group(list, fd))) - { - out = g_list_prepend(out, file_data_ref(fd)); - } - } - - filelist_free(list); - out = g_list_reverse(out); - - return out; + FileData::file_data_sc_free_ci_list(fd_list); } - - - -/* - * notify other modules about the change described by FileDataChangeInfo - */ - -/* might use file_maint_ functions for now, later it should be changed to a system of callbacks */ -/** @FIXME do we need the ignore_list? It looks like a workaround for ineffective - implementation in view-file-list.cc */ - - -struct NotifyIdleData { - FileData *fd; - NotifyType type; -}; - - -struct NotifyData { - FileDataNotifyFunc func; - gpointer data; - NotifyPriority priority; -}; - -static GList *notify_func_list = nullptr; - -static gint file_data_notify_sort(gconstpointer a, gconstpointer b) +GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped) { - auto nda = static_cast(a); - auto ndb = static_cast(b); - - if (nda->priority < ndb->priority) return -1; - if (nda->priority > ndb->priority) return 1; - return 0; + return FileData::file_data_process_groups_in_selection(list, ungroup, ungrouped); } -gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority) -{ - NotifyData *nd; - GList *work = notify_func_list; - - while (work) - { - auto nd = static_cast(work->data); - - if (nd->func == func && nd->data == data) - { - g_warning("Notify func already registered"); - return FALSE; - } - work = work->next; - } - - nd = g_new(NotifyData, 1); - nd->func = func; - nd->data = data; - nd->priority = priority; - - notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort); - DEBUG_2("Notify func registered: %p", (void *)nd); - return TRUE; -} -gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data) +using FileDataNotifyFunc = void (*)(FileData *, NotifyType, gpointer); +gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority) { - GList *work = notify_func_list; - - while (work) - { - auto nd = static_cast(work->data); - - if (nd->func == func && nd->data == data) - { - notify_func_list = g_list_delete_link(notify_func_list, work); - DEBUG_2("Notify func unregistered: %p", (void *)nd); - g_free(nd); - return TRUE; - } - work = work->next; - } - - g_warning("Notify func not found"); - return FALSE; + return FileData::file_data_register_notify_func(func, data, priority); } -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" -gboolean file_data_send_notification_idle_cb_unused(gpointer data) +gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data) { - auto *nid = (NotifyIdleData *)data; - GList *work = notify_func_list; - - while (work) - { - auto *nd = (NotifyData *)work->data; - - nd->func(nid->fd, nid->type, nd->data); - work = work->next; - } - file_data_unref(nid->fd); - g_free(nid); - return FALSE; + return FileData::file_data_unregister_notify_func(func, data); } -#pragma GCC diagnostic pop void file_data_send_notification(FileData *fd, NotifyType type) { - GList *work = notify_func_list; - - while (work) - { - auto nd = static_cast(work->data); - - nd->func(fd, type, nd->data); - work = work->next; - } - /* - NotifyIdleData *nid = g_new0(NotifyIdleData, 1); - nid->fd = file_data_ref(fd); - nid->type = type; - g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL); - */ + fd->file_data_send_notification(fd, type); } -static GHashTable *file_data_monitor_pool = nullptr; -static guint realtime_monitor_id = 0; /* event source id */ -static void realtime_monitor_check_cb(gpointer key, gpointer, gpointer) +gboolean file_data_register_real_time_monitor(FileData *fd) { - auto fd = static_cast(key); - - file_data_check_changed_files(fd); - - DEBUG_1("monitor %s", fd->path); + return fd->file_data_register_real_time_monitor(fd); } -static gboolean realtime_monitor_cb(gpointer) +gboolean file_data_unregister_real_time_monitor(FileData *fd) { - if (!options->update_on_time_change) return TRUE; - g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, nullptr); - return TRUE; + return fd->file_data_unregister_real_time_monitor(fd); } -gboolean file_data_register_real_time_monitor(FileData *fd) -{ - gint count; - - file_data_ref(fd); - - if (!file_data_monitor_pool) - file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal); - - count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd)); - DEBUG_1("Register realtime %d %s", count, fd->path); - - count++; - g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count)); - - if (!realtime_monitor_id) - { - realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, nullptr); - } - - return TRUE; +void read_exif_time_data(FileData *file) +{ + file->read_exif_time_data(file); } -gboolean file_data_unregister_real_time_monitor(FileData *fd) +void read_exif_time_digitized_data(FileData *file) { - gint count; - - g_assert(file_data_monitor_pool); - - count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd)); - - DEBUG_1("Unregister realtime %d %s", count, fd->path); - - g_assert(count > 0); - - count--; - - if (count == 0) - g_hash_table_remove(file_data_monitor_pool, fd); - else - g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count)); - - file_data_unref(fd); - - if (g_hash_table_size(file_data_monitor_pool) == 0) - { - g_source_remove(realtime_monitor_id); - realtime_monitor_id = 0; - return FALSE; - } - - return TRUE; + file->read_exif_time_digitized_data(file); } -/* - *----------------------------------------------------------------------------- - * Saving marks list, clearing marks - * Uses file_data_pool - *----------------------------------------------------------------------------- - */ -static void marks_get_files(gpointer key, gpointer value, gpointer userdata) +gboolean marks_list_save(gchar *path, gboolean save) { - auto file_name = static_cast(key); - auto result = static_cast(userdata); - FileData *fd; - - if (isfile(file_name)) - { - fd = static_cast(value); - if (fd && fd->marks > 0) - { - g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks); - } - } + return FileData::marks_list_save(path, save); } gboolean marks_list_load(const gchar *path) { - FILE *f; - gchar s_buf[1024]; - gchar *pathl; - gchar *file_path; - gchar *marks_value; - - pathl = path_from_utf8(path); - f = fopen(pathl, "r"); - g_free(pathl); - if (!f) return FALSE; - - /* first line must start with Marks comment */ - if (!fgets(s_buf, sizeof(s_buf), f) || - strncmp(s_buf, "#Marks", 6) != 0) - { - fclose(f); - return FALSE; - } - - while (fgets(s_buf, sizeof(s_buf), f)) - { - if (s_buf[0]=='#') continue; - file_path = strtok(s_buf, ","); - marks_value = strtok(nullptr, ","); - if (isfile(file_path)) - { - FileData *fd = file_data_new_no_grouping(file_path); - file_data_ref(fd); - gint n = 0; - while (n <= 9) - { - gint mark_no = 1 << n; - if (atoi(marks_value) & mark_no) - { - file_data_set_mark(fd, n , 1); - } - n++; - } - } - } - - fclose(f); - return TRUE; -} - -gboolean marks_list_save(gchar *path, gboolean save) -{ - SecureSaveInfo *ssi; - gchar *pathl; - - pathl = path_from_utf8(path); - ssi = secure_open(pathl); - g_free(pathl); - if (!ssi) - { - log_printf(_("Error: Unable to write marks lists to: %s\n"), path); - return FALSE; - } - - secure_fprintf(ssi, "#Marks lists\n"); - - GString *marks = g_string_new(""); - if (save) - { - g_hash_table_foreach(file_data_pool, marks_get_files, marks); - } - secure_fprintf(ssi, "%s", marks->str); - g_string_free(marks, TRUE); - - secure_fprintf(ssi, "#end\n"); - return (secure_close(ssi) == 0); -} - -static void marks_clear(gpointer key, gpointer value, gpointer) -{ - auto file_name = static_cast(key); - gint mark_no; - gint n; - FileData *fd; - - if (isfile(file_name)) - { - fd = static_cast(value); - if (fd && fd->marks > 0) - { - n = 0; - while (n <= 9) - { - mark_no = 1 << n; - if (fd->marks & mark_no) - { - file_data_set_mark(fd, n , 0); - } - n++; - } - } - } + return FileData::marks_list_load(path); } void marks_clear_all() { - g_hash_table_foreach(file_data_pool, marks_clear, nullptr); + FileData::marks_clear_all(); } -void file_data_set_page_num(FileData *fd, gint page_num) +void read_rating_data(FileData *file) { - if (fd->page_total > 1 && page_num < 0) - { - fd->page_num = fd->page_total - 1; - } - else if (fd->page_total > 1 && page_num <= fd->page_total) - { - fd->page_num = page_num - 1; - } - else - { - fd->page_num = 0; - } - file_data_send_notification(fd, NOTIFY_REREAD); + file->read_rating_data(file); } + void file_data_inc_page_num(FileData *fd) { - if (fd->page_total > 0 && fd->page_num < fd->page_total - 1) - { - fd->page_num = fd->page_num + 1; - } - else if (fd->page_total == 0) - { - fd->page_num = fd->page_num + 1; - } - file_data_send_notification(fd, NOTIFY_REREAD); + fd->file_data_inc_page_num(fd); } void file_data_dec_page_num(FileData *fd) { - if (fd->page_num > 0) - { - fd->page_num = fd->page_num - 1; - } - file_data_send_notification(fd, NOTIFY_REREAD); + fd->file_data_dec_page_num(fd); } void file_data_set_page_total(FileData *fd, gint page_total) { - fd->page_total = page_total; + fd->file_data_set_page_total(fd, page_total); +} + +void file_data_set_page_num(FileData *fd, gint page_num) +{ + fd->file_data_set_page_num(fd, page_num); +} + + +void file_data_dump() +{ + FileData::file_data_dump(); } /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/filedata.h b/src/filedata.h index 547a9fbcf..ecd9f083d 100644 --- a/src/filedata.h +++ b/src/filedata.h @@ -40,10 +40,6 @@ struct HistMap; #define FD_MAGICK 0x12345678u -gchar *text_from_size(gint64 size); -gchar *text_from_size_abrev(gint64 size); -const gchar *text_from_time(time_t t); - enum FileDataChangeType { FILEDATA_CHANGE_DELETE, FILEDATA_CHANGE_MOVE, @@ -74,7 +70,11 @@ struct FileDataChangeInfo { gboolean regroup_when_finished; }; -struct FileData { +class FileData { + FileData() = delete; + + public: + // Public members from the original API guint magick; gint type; gchar *original_path; /**< key to file_data_pool hash table */ @@ -132,8 +132,182 @@ struct FileData { gint page_num; gint page_total; + + static gchar *text_from_size(gint64 size); + static gchar *text_from_size_abrev(gint64 size); + static const gchar *text_from_time(time_t t); + + /** + * @headerfile file_data_new_group + * scan for sidecar files - expensive + */ + static FileData *file_data_new_group(const gchar *path_utf8); + + /** + * @headerfile file_data_new_no_grouping + * should be used on helper files which can't have sidecars + */ + static FileData *file_data_new_no_grouping(const gchar *path_utf8); + + /** + * @headerfile file_data_new_dir + * should be used on dirs + */ + static FileData *file_data_new_dir(const gchar *path_utf8); + + static FileData *file_data_new_simple(const gchar *path_utf8); + +#ifdef DEBUG_FILEDATA + FileData *file_data_ref(const gchar *file = __builtin_FILE(), gint line = __builtin_LINE()); + void file_data_unref(const gchar *file = __builtin_FILE(), gint line = __builtin_LINE()); +#else + FileData *file_data_ref(); + void file_data_unref(); +#endif + + void file_data_lock(FileData *fd); + void file_data_unlock(FileData *fd); + static void file_data_lock_list(GList *list); + static void file_data_unlock_list(GList *list); + + gboolean file_data_check_changed_files(FileData *fd); + + void file_data_increment_version(FileData *fd); + + void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd); + + void file_data_disable_grouping(FileData *fd, gboolean disable); + static void file_data_disable_grouping_list(GList *fd_list, gboolean disable); + + static gint filelist_sort_compare_filedata(FileData *fa, FileData *fb); + static gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend); + static GList *filelist_sort(GList *list, SortType method, gboolean ascend, gboolean case_sensitive); + static GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb); + static GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb); + + gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs); + gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs); + static void filelist_free(GList *list); + static GList *filelist_copy(GList *list); + static GList *filelist_from_path_list(GList *list); + static GList *filelist_to_path_list(GList *list); + + static GList *filelist_filter(GList *list, gboolean is_dir_list); + + static GList *filelist_sort_path(GList *list); + static GList *filelist_recursive(FileData *dir_fd); + static GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend, gboolean case_sensitive); + + using FileDataGetMarkFunc = gboolean (*)(FileData *, gint, gpointer); + using FileDataSetMarkFunc = gboolean (*)(FileData *, gint, gboolean, gpointer); + static gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify); + static void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data); + + + gboolean file_data_get_mark(FileData *fd, gint n); + guint file_data_get_marks(FileData *fd); + void file_data_set_mark(FileData *fd, gint n, gboolean value); + gboolean file_data_filter_marks(FileData *fd, guint filter); + static GList *file_data_filter_marks_list(GList *list, guint filter); + + gboolean file_data_mark_to_selection(FileData *fd, gint mark, MarkToSelectionMode mode, gboolean selected); + void file_data_selection_to_mark(FileData *fd, gint mark, SelectionToMarkMode mode); + + gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter); + static GList *file_data_filter_file_filter_list(GList *list, GRegex *filter); + + static GList *file_data_filter_class_list(GList *list, guint filter); + + gchar *file_data_sc_list_to_string(FileData *fd); + + gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only); + + + gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest); + gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path); + gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path); + gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path); + gboolean file_data_sc_add_ci_delete(FileData *fd); + gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path); + + static gboolean file_data_sc_add_ci_delete_list(GList *fd_list); + static gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest); + static gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest); + static gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest); + static gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest); + gboolean file_data_add_ci_write_metadata(FileData *fd); + static gboolean file_data_add_ci_write_metadata_list(GList *fd_list); + + static gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest); + static gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest); + static gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest); + + + gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path); + gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path); + gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path); + gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path); + + static gchar *file_data_get_error_string(gint error); + + gint file_data_verify_ci(FileData *fd, GList *list); + static gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars); + + gboolean file_data_perform_ci(FileData *fd); + gboolean file_data_apply_ci(FileData *fd); + void file_data_free_ci(FileData *fd); + static void file_data_free_ci_list(GList *fd_list); + + void file_data_set_regroup_when_finished(FileData *fd, gboolean enable); + + gint file_data_sc_verify_ci(FileData *fd, GList *list); + + gboolean file_data_sc_perform_ci(FileData *fd); + gboolean file_data_sc_apply_ci(FileData *fd); + void file_data_sc_free_ci(FileData *fd); + static void file_data_sc_free_ci_list(GList *fd_list); + + static GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped); + + + using FileDataNotifyFunc = void (*)(FileData *, NotifyType, gpointer); + static gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority); + static gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data); + void file_data_send_notification(FileData *fd, NotifyType type); + + gboolean file_data_register_real_time_monitor(FileData *fd); + gboolean file_data_unregister_real_time_monitor(FileData *fd); + + void read_exif_time_data(FileData *file); + void read_exif_time_digitized_data(FileData *file); + + static gboolean marks_list_save(gchar *path, gboolean save); + static gboolean marks_list_load(const gchar *path); + static void marks_clear_all(); + void read_rating_data(FileData *file); + + void file_data_inc_page_num(FileData *fd); + void file_data_dec_page_num(FileData *fd); + void file_data_set_page_total(FileData *fd, gint page_total); + void file_data_set_page_num(FileData *fd, gint page_num); + + static void file_data_dump(); + + private: + void set_exif_time_data_unused(GList *files); + void set_exif_time_digitized_data_unused(GList *files); + void set_rating_data_unused(GList *files); + GList *filelist_insert_sort_unused(GList *list, FileData *fd, SortType method, gboolean ascend); + gint file_data_get_user_orientation_unused(FileData *fd); + void file_data_set_user_orientation_unused(FileData *fd, gint value); + gboolean file_data_send_notification_idle_cb_unused(gpointer data); }; +// C-style compatibility API. +gchar *text_from_size(gint64 size); +gchar *text_from_size_abrev(gint64 size); +const gchar *text_from_time(time_t t); + /** * @headerfile file_data_new_group * scan for sidecar files - expensive @@ -155,10 +329,8 @@ FileData *file_data_new_dir(const gchar *path_utf8); FileData *file_data_new_simple(const gchar *path_utf8); #ifdef DEBUG_FILEDATA -FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd); -void file_data_unref_debug(const gchar *file, gint line, FileData *fd); -#define file_data_ref(fd) file_data_ref_debug(__FILE__, __LINE__, fd) -#define file_data_unref(fd) file_data_unref_debug(__FILE__, __LINE__, fd) +FileData *file_data_ref(FileData *fd, const gchar *file = __builtin_FILE(), gint line = __builtin_LINE()); +void file_data_unref(FileData *fd, const gchar *file = __builtin_FILE(), gint line = __builtin_LINE()); #else FileData *file_data_ref(FileData *fd); void file_data_unref(FileData *fd); @@ -173,7 +345,6 @@ gboolean file_data_check_changed_files(FileData *fd); void file_data_increment_version(FileData *fd); -gboolean file_data_add_change_info(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest); void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd); void file_data_disable_grouping(FileData *fd, gboolean disable); @@ -235,6 +406,7 @@ gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest); gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest); gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest); gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest); +gboolean file_data_add_ci_write_metadata(FileData *fd); gboolean file_data_add_ci_write_metadata_list(GList *fd_list); gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest); @@ -291,5 +463,6 @@ void file_data_set_page_total(FileData *fd, gint page_total); void file_data_set_page_num(FileData *fd, gint page_num); void file_data_dump(); + #endif /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/filedata/filedata.cc b/src/filedata/filedata.cc new file mode 100644 index 000000000..e8dc9325d --- /dev/null +++ b/src/filedata/filedata.cc @@ -0,0 +1,3588 @@ +/* + * Copyright (C) 2006 John Ellis + * Copyright (C) 2008 - 2016 The Geeqie Team + * + * Author: John Ellis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +// NOLINTBEGIN(readability-convert-member-functions-to-static) + +#include "filedata.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "cache.h" +#include "debug.h" +#include "exif.h" +#include "filefilter.h" +#include "histogram.h" +#include "intl.h" +#include "main-defines.h" +#include "main.h" +#include "metadata.h" +#include "misc.h" +#include "options.h" +#include "secure-save.h" +#include "thumb-standard.h" +#include "trash.h" +#include "ui-fileops.h" + +#ifdef DEBUG_FILEDATA +gint global_file_data_count = 0; +#endif + +static GHashTable *file_data_pool = nullptr; +static GHashTable *file_data_planned_change_hash = nullptr; + +static gint sidecar_file_priority(const gchar *extension); +static void file_data_check_sidecars(const GList *basename_list); +static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd); + + +static SortType filelist_sort_method = SORT_NONE; +static gboolean filelist_sort_ascend = TRUE; +static gboolean filelist_sort_case = TRUE; + +/* + *----------------------------------------------------------------------------- + * text conversion utils + *----------------------------------------------------------------------------- + */ + +gchar *FileData::text_from_size(gint64 size) +{ + gchar *a; + gchar *b; + gchar *s; + gchar *d; + gint l; + gint n; + gint i; + + /* what I would like to use is printf("%'d", size) + * BUT: not supported on every libc :( + */ + if (size > G_MAXINT) + { + /* the %lld conversion is not valid in all libcs, so use a simple work-around */ + a = g_strdup_printf("%d%09d", static_cast(size / 1000000000), static_cast(size % 1000000000)); + } + else + { + a = g_strdup_printf("%d", static_cast(size)); + } + l = strlen(a); + n = (l - 1)/ 3; + if (n < 1) return a; + + b = g_new(gchar, l + n + 1); + + s = a; + d = b; + i = l - n * 3; + while (*s != '\0') + { + if (i < 1) + { + i = 3; + *d = ','; + d++; + } + + *d = *s; + s++; + d++; + i--; + } + *d = '\0'; + + g_free(a); + return b; +} + +gchar *FileData::text_from_size_abrev(gint64 size) +{ + if (size < static_cast(1024)) + { + return g_strdup_printf(_("%d bytes"), static_cast(size)); + } + if (size < static_cast(1048576)) + { + return g_strdup_printf(_("%.1f KiB"), static_cast(size) / 1024.0); + } + if (size < static_cast(1073741824)) + { + return g_strdup_printf(_("%.1f MiB"), static_cast(size) / 1048576.0); + } + + /* to avoid overflowing the gdouble, do division in two steps */ + size /= 1048576; + return g_strdup_printf(_("%.1f GiB"), static_cast(size) / 1024.0); +} + +/* note: returned string is valid until next call to text_from_time() */ +const gchar *FileData::text_from_time(time_t t) +{ + static gchar *ret = nullptr; + gchar buf[128]; + gint buflen; + struct tm *btime; + GError *error = nullptr; + + btime = localtime(&t); + + /* the %x warning about 2 digit years is not an error */ + buflen = strftime(buf, sizeof(buf), "%x %X", btime); + if (buflen < 1) return ""; + + g_free(ret); + ret = g_locale_to_utf8(buf, buflen, nullptr, nullptr, &error); + if (error) + { + log_printf("Error converting locale strftime to UTF-8: %s\n", error->message); + g_error_free(error); + return ""; + } + + return ret; +} + +/* + *----------------------------------------------------------------------------- + * changed files detection and notification + *----------------------------------------------------------------------------- + */ + +void FileData::file_data_increment_version(FileData *fd) +{ + fd->version++; + fd->valid_marks = 0; + if (fd->parent) + { + fd->parent->version++; + fd->parent->valid_marks = 0; + } +} + +static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st) +{ + if (fd->size != st->st_size || + fd->date != st->st_mtime) + { + fd->size = st->st_size; + fd->date = st->st_mtime; + fd->cdate = st->st_ctime; + fd->mode = st->st_mode; + if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf); + fd->thumb_pixbuf = nullptr; + file_data_increment_version(fd); + file_data_send_notification(fd, NOTIFY_REREAD); + return TRUE; + } + return FALSE; +} + +static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st) +{ + gboolean ret = FALSE; + GList *work; + + ret = file_data_check_changed_single_file(fd, st); + + work = fd->sidecar_files; + while (work) + { + auto sfd = static_cast(work->data); + struct stat st; + work = work->next; + + if (!stat_utf8(sfd->path, &st)) + { + fd->size = 0; + fd->date = 0; + file_data_ref(sfd); + file_data_disconnect_sidecar_file(fd, sfd); + ret = TRUE; + file_data_increment_version(sfd); + file_data_send_notification(sfd, NOTIFY_REREAD); + file_data_unref(sfd); + continue; + } + + ret |= file_data_check_changed_files_recursive(sfd, &st); + } + return ret; +} + + +gboolean FileData::file_data_check_changed_files(FileData *fd) +{ + gboolean ret = FALSE; + struct stat st; + + if (fd->parent) fd = fd->parent; + + if (!stat_utf8(fd->path, &st)) + { + GList *sidecars; + GList *work; + FileData *sfd = nullptr; + + /* parent is missing, we have to rebuild whole group */ + ret = TRUE; + fd->size = 0; + fd->date = 0; + + /* file_data_disconnect_sidecar_file might delete the file, + we have to keep the reference to prevent this */ + sidecars = filelist_copy(fd->sidecar_files); + ::file_data_ref(fd); + work = sidecars; + while (work) + { + sfd = static_cast(work->data); + work = work->next; + + file_data_disconnect_sidecar_file(fd, sfd); + } + file_data_check_sidecars(sidecars); /* this will group the sidecars back together */ + /* now we can release the sidecars */ + filelist_free(sidecars); + file_data_increment_version(fd); + file_data_send_notification(fd, NOTIFY_REREAD); + ::file_data_unref(fd); + } + else + { + ret |= file_data_check_changed_files_recursive(fd, &st); + } + + return ret; +} + +/* + *----------------------------------------------------------------------------- + * file name, extension, sorting, ... + *----------------------------------------------------------------------------- + */ + +static void file_data_set_collate_keys(FileData *fd) +{ + gchar *caseless_name; + gchar *valid_name; + + valid_name = g_filename_display_name(fd->name); + caseless_name = g_utf8_casefold(valid_name, -1); + + g_free(fd->collate_key_name); + g_free(fd->collate_key_name_nocase); + + fd->collate_key_name_natural = g_utf8_collate_key_for_filename(fd->name, -1); + fd->collate_key_name_nocase_natural = g_utf8_collate_key_for_filename(caseless_name, -1); + fd->collate_key_name = g_utf8_collate_key(valid_name, -1); + fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1); + + g_free(valid_name); + g_free(caseless_name); +} + +static void file_data_set_path(FileData *fd, const gchar *path) +{ + g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */ + g_assert(file_data_pool); + + g_free(fd->path); + + if (fd->original_path) + { + g_hash_table_remove(file_data_pool, fd->original_path); + g_free(fd->original_path); + } + + g_assert(!g_hash_table_lookup(file_data_pool, path)); + + fd->original_path = g_strdup(path); + g_hash_table_insert(file_data_pool, fd->original_path, fd); + + if (strcmp(path, G_DIR_SEPARATOR_S) == 0) + { + fd->path = g_strdup(path); + fd->name = fd->path; + fd->extension = fd->name + 1; + file_data_set_collate_keys(fd); + return; + } + + fd->path = g_strdup(path); + fd->name = filename_from_path(fd->path); + + if (strcmp(fd->name, "..") == 0) + { + gchar *dir = remove_level_from_path(path); + g_free(fd->path); + fd->path = remove_level_from_path(dir); + g_free(dir); + fd->name = ".."; + fd->extension = fd->name + 2; + file_data_set_collate_keys(fd); + return; + } + + if (strcmp(fd->name, ".") == 0) + { + g_free(fd->path); + fd->path = remove_level_from_path(path); + fd->name = "."; + fd->extension = fd->name + 1; + file_data_set_collate_keys(fd); + return; + } + + fd->extension = registered_extension_from_path(fd->path); + if (fd->extension == nullptr) + { + fd->extension = fd->name + strlen(fd->name); + } + + fd->sidecar_priority = sidecar_file_priority(fd->extension); + file_data_set_collate_keys(fd); +} + +/* + *----------------------------------------------------------------------------- + * create or reuse Filedata + *----------------------------------------------------------------------------- + */ + +static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars) +{ + FileData *fd; + struct passwd *user; + struct group *group; + + DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars); + + if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE; + + if (!file_data_pool) + file_data_pool = g_hash_table_new(g_str_hash, g_str_equal); + + fd = static_cast(g_hash_table_lookup(file_data_pool, path_utf8)); + if (fd) + { + file_data_ref(fd); + } + + if (!fd && file_data_planned_change_hash) + { + fd = static_cast(g_hash_table_lookup(file_data_planned_change_hash, path_utf8)); + if (fd) + { + DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path); + if (!isfile(fd->path)) + { + file_data_ref(fd); + file_data_apply_ci(fd); + } + else + { + fd = nullptr; + } + } + } + + if (fd) + { + if (disable_sidecars) file_data_disable_grouping(fd, TRUE); + +#ifdef DEBUG_FILEDATA + gboolean changed = +#endif + file_data_check_changed_single_file(fd, st); + + DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : ""); + + return fd; + } + + fd = g_new0(FileData, 1); +#ifdef DEBUG_FILEDATA + global_file_data_count++; + DEBUG_2("file data count++: %d", global_file_data_count); +#endif + + fd->size = st->st_size; + fd->date = st->st_mtime; + fd->cdate = st->st_ctime; + fd->mode = st->st_mode; + fd->ref = 1; + fd->magick = FD_MAGICK; + fd->exifdate = 0; + fd->rating = STAR_RATING_NOT_READ; + fd->format_class = filter_file_get_class(path_utf8); + fd->page_num = 0; + fd->page_total = 0; + + user = getpwuid(st->st_uid); + if (!user) + { + fd->owner = g_strdup_printf("%u", st->st_uid); + } + else + { + fd->owner = g_strdup(user->pw_name); + } + + group = getgrgid(st->st_gid); + if (!group) + { + fd->group = g_strdup_printf("%u", st->st_gid); + } + else + { + fd->group = g_strdup(group->gr_name); + } + + fd->sym_link = get_symbolic_link(path_utf8); + + if (disable_sidecars) fd->disable_grouping = TRUE; + + file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */ + + return fd; +} + +static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars) +{ + gchar *path_utf8 = path_to_utf8(path); + FileData *ret = file_data_new(path_utf8, st, disable_sidecars); + + g_free(path_utf8); + return ret; +} + +FileData *FileData::file_data_new_simple(const gchar *path_utf8) +{ + struct stat st; + FileData *fd; + + if (!stat_utf8(path_utf8, &st)) + { + st.st_size = 0; + st.st_mtime = 0; + } + + fd = static_cast(g_hash_table_lookup(file_data_pool, path_utf8)); + if (!fd) fd = file_data_new(path_utf8, &st, TRUE); + if (fd) + { + ::file_data_ref(fd); + } + + return fd; +} + +void FileData::read_exif_time_data(FileData *file) +{ + if (file->exifdate > 0) + { + DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path); + return; + } + + if (!file->exif) + { + exif_read_fd(file); + } + + if (file->exif) + { + gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal"); + DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), (void *)file, file->path); + + if (tmp) + { + struct tm time_str; + uint year; + uint month; + uint day; + uint hour; + uint min; + uint sec; + + sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec); + time_str.tm_year = year - 1900; + time_str.tm_mon = month - 1; + time_str.tm_mday = day; + time_str.tm_hour = hour; + time_str.tm_min = min; + time_str.tm_sec = sec; + time_str.tm_isdst = 0; + + file->exifdate = mktime(&time_str); + g_free(tmp); + } + } +} + +void FileData::read_exif_time_digitized_data(FileData *file) +{ + if (file->exifdate_digitized > 0) + { + DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path); + return; + } + + if (!file->exif) + { + exif_read_fd(file); + } + + if (file->exif) + { + gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized"); + DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), (void *)file, file->path); + + if (tmp) + { + struct tm time_str; + uint year; + uint month; + uint day; + uint hour; + uint min; + uint sec; + + sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec); + time_str.tm_year = year - 1900; + time_str.tm_mon = month - 1; + time_str.tm_mday = day; + time_str.tm_hour = hour; + time_str.tm_min = min; + time_str.tm_sec = sec; + time_str.tm_isdst = 0; + + file->exifdate_digitized = mktime(&time_str); + g_free(tmp); + } + } +} + +void FileData::read_rating_data(FileData *file) +{ + gchar *rating_str; + + rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN); + if (rating_str) + { + file->rating = atoi(rating_str); + g_free(rating_str); + } + else + { + file->rating = 0; + } +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +void FileData::set_exif_time_data_unused(GList *files) +{ + DEBUG_1("%s set_exif_time_data: ...", get_exec_time()); + + while (files) + { + auto *file = static_cast(files->data); + + read_exif_time_data(file); + files = files->next; + } +} + +void FileData::set_exif_time_digitized_data_unused(GList *files) +{ + DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time()); + + while (files) + { + auto *file = static_cast(files->data); + + read_exif_time_digitized_data(file); + files = files->next; + } +} + +void FileData::set_rating_data_unused(GList *files) +{ + gchar *rating_str; + DEBUG_1("%s set_rating_data: ...", get_exec_time()); + + while (files) + { + auto *file = static_cast(files->data); + rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN); + if (rating_str ) + { + file->rating = atoi(rating_str); + g_free(rating_str); + } + files = files->next; + } +} +#pragma GCC diagnostic pop + +FileData *FileData::file_data_new_no_grouping(const gchar *path_utf8) +{ + struct stat st; + + if (!stat_utf8(path_utf8, &st)) + { + st.st_size = 0; + st.st_mtime = 0; + } + + return file_data_new(path_utf8, &st, TRUE); +} + +FileData *FileData::file_data_new_dir(const gchar *path_utf8) +{ + struct stat st; + + if (!stat_utf8(path_utf8, &st)) + { + st.st_size = 0; + st.st_mtime = 0; + } + else + /* dir or non-existing yet */ + g_assert(S_ISDIR(st.st_mode)); + + return file_data_new(path_utf8, &st, TRUE); +} + +/* + *----------------------------------------------------------------------------- + * reference counting + *----------------------------------------------------------------------------- + */ + +#ifdef DEBUG_FILEDATA +FileData *FileData::file_data_ref(const gchar *file, gint line) +#else +FileData *FileData::file_data_ref() +#endif +{ + FileData *fd = this; + // TODO(xsdg): Do null checks at call-sites, if necessary. The following is a no-op. + if (fd == nullptr) return nullptr; + if (fd->magick != FD_MAGICK) +#ifdef DEBUG_FILEDATA + log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd); +#else + log_printf("Error: fd magick mismatch fd=%p", fd); +#endif + g_assert(fd->magick == FD_MAGICK); + fd->ref++; + +#ifdef DEBUG_FILEDATA + DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->path, file, line); +#else + DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path); +#endif + return fd; +} + +/** + * @brief Print ref. count and image name + * @param + * + * Print image ref. count and full path name of all images in + * the file_data_pool. + * + * Used only by debug_fd() + */ +void FileData::file_data_dump() +{ +#ifdef DEBUG_FILEDATA + FileData *fd; + GList *list; + + if (file_data_pool) + { + list = g_hash_table_get_values(file_data_pool); + + log_printf("%d", global_file_data_count); + log_printf("%d", g_list_length(list)); + + GList *work = list; + while (work) + { + fd = static_cast(work->data); + log_printf("%-4d %s", fd->ref, fd->path); + work = work->next; + } + + g_list_free(list); + } +#endif +} + +static void file_data_free(FileData *fd) +{ + g_assert(fd->magick == FD_MAGICK); + g_assert(fd->ref == 0); + g_assert(!fd->locked); + +#ifdef DEBUG_FILEDATA + global_file_data_count--; + DEBUG_2("file data count--: %d", global_file_data_count); +#endif + + metadata_cache_free(fd); + g_hash_table_remove(file_data_pool, fd->original_path); + + g_free(fd->path); + g_free(fd->original_path); + g_free(fd->collate_key_name); + g_free(fd->collate_key_name_nocase); + g_free(fd->extended_extension); + if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf); + histmap_free(fd->histmap); + g_free(fd->owner); + g_free(fd->group); + g_free(fd->sym_link); + g_free(fd->format_name); + g_assert(fd->sidecar_files == nullptr); /* sidecar files must be freed before calling this */ + + file_data_change_info_free(nullptr, fd); + g_free(fd); +} + +/** + * @brief Checks if the FileData is referenced + * + * Checks the refcount and whether the FileData is locked. + */ +static gboolean file_data_check_has_ref(FileData *fd) +{ + return fd->ref > 0 || fd->locked; +} + +/** + * @brief Consider freeing a FileData. + * + * This function will free a FileData and its children provided that neither its parent nor it has + * a positive refcount, and provided that neither is locked. + */ +static void file_data_consider_free(FileData *fd) +{ + GList *work; + FileData *parent = fd->parent ? fd->parent : fd; + + g_assert(fd->magick == FD_MAGICK); + if (file_data_check_has_ref(fd)) return; + if (file_data_check_has_ref(parent)) return; + + work = parent->sidecar_files; + while (work) + { + auto sfd = static_cast(work->data); + if (file_data_check_has_ref(sfd)) return; + work = work->next; + } + + /* Neither the parent nor the siblings are referenced, so we can free everything */ + DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'", + fd->path, fd->parent ? parent->path : "-"); + + g_list_free_full(parent->sidecar_files, reinterpret_cast(file_data_free)); + parent->sidecar_files = nullptr; + + file_data_free(parent); +} + +#ifdef DEBUG_FILEDATA +void FileData::file_data_unref(const gchar *file, gint line) +#else +void FileData::file_data_unref() +#endif +{ + FileData *fd = this; // TODO(xsdg): clean this up across the board. + if (fd == nullptr) return; + if (fd->magick != FD_MAGICK) +#ifdef DEBUG_FILEDATA + log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd); +#else + log_printf("Error: fd magick mismatch fd=%p", fd); +#endif + g_assert(fd->magick == FD_MAGICK); + + fd->ref--; +#ifdef DEBUG_FILEDATA + DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->locked, fd->path, + file, line); +#else + DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path); +#endif + + // Free FileData if it's no longer ref'd + file_data_consider_free(fd); +} + +/** + * @brief Lock the FileData in memory. + * + * This allows the caller to prevent a FileData from being freed, even after its refcount is zero. + * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization, + * even if the code would continue to function properly even if the FileData were freed. Code that + * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref. + *

+ * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to + * file_data_lock, a single call to file_data_unlock will unlock the FileData. + */ +void FileData::file_data_lock(FileData *fd) +{ + if (fd == nullptr) return; + if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd); + + g_assert(fd->magick == FD_MAGICK); + fd->locked = TRUE; + + DEBUG_2("file_data_ref fd=%p (%d): '%s'", (void *)fd, fd->ref, fd->path); +} + +/** + * @brief Reset the maintain-FileData-in-memory lock + * + * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees + * the FileData if its refcount is already zero (which will happen if the lock is the only thing + * keeping it from being freed. + */ +void FileData::file_data_unlock(FileData *fd) +{ + if (fd == nullptr) return; + if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd); + + g_assert(fd->magick == FD_MAGICK); + fd->locked = FALSE; + + // Free FileData if it's no longer ref'd + file_data_consider_free(fd); +} + +/** + * @brief Lock all of the FileDatas in the provided list + * + * @see file_data_lock(#FileData) + */ +void FileData::file_data_lock_list(GList *list) +{ + GList *work; + + work = list; + while (work) + { + auto fd = static_cast(work->data); + work = work->next; + ::file_data_lock(fd); + } +} + +/** + * @brief Unlock all of the FileDatas in the provided list + * + * @see #file_data_unlock(#FileData) + */ +void FileData::file_data_unlock_list(GList *list) +{ + GList *work; + + work = list; + while (work) + { + auto fd = static_cast(work->data); + work = work->next; + ::file_data_unlock(fd); + } +} + +/* + *----------------------------------------------------------------------------- + * sidecar file info struct + *----------------------------------------------------------------------------- + */ + +static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b) +{ + auto fda = static_cast(a); + auto fdb = static_cast(b); + + if (fda->sidecar_priority < fdb->sidecar_priority) return -1; + if (fda->sidecar_priority > fdb->sidecar_priority) return 1; + + return strcmp(fdb->extension, fda->extension); +} + + +static gint sidecar_file_priority(const gchar *extension) +{ + gint i = 1; + GList *work; + + if (extension == nullptr) + return 0; + + work = sidecar_ext_get_list(); + + while (work) { + auto ext = static_cast(work->data); + + work = work->next; + if (g_ascii_strcasecmp(extension, ext) == 0) return i; + i++; + } + return 0; +} + +static void file_data_check_sidecars(const GList *basename_list) +{ + /* basename_list contains the new group - first is the parent, then sorted sidecars */ + /* all files in the list have ref count > 0 */ + + const GList *work; + GList *s_work; + GList *new_sidecars; + FileData *parent_fd; + + if (!basename_list) return; + + + DEBUG_2("basename start"); + work = basename_list; + while (work) + { + auto fd = static_cast(work->data); + work = work->next; + g_assert(fd->magick == FD_MAGICK); + DEBUG_2("basename: %p %s", (void *)fd, fd->name); + if (fd->parent) + { + g_assert(fd->parent->magick == FD_MAGICK); + DEBUG_2(" parent: %p", (void *)fd->parent); + } + s_work = fd->sidecar_files; + while (s_work) + { + auto sfd = static_cast(s_work->data); + s_work = s_work->next; + g_assert(sfd->magick == FD_MAGICK); + DEBUG_2(" sidecar: %p %s", (void *)sfd, sfd->name); + } + + g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr); + } + + parent_fd = static_cast(basename_list->data); + + /* check if the second and next entries of basename_list are already connected + as sidecars of the first entry (parent_fd) */ + work = basename_list->next; + s_work = parent_fd->sidecar_files; + + while (work && s_work) + { + if (work->data != s_work->data) break; + work = work->next; + s_work = s_work->next; + } + + if (!work && !s_work) + { + DEBUG_2("basename no change"); + return; /* no change in grouping */ + } + + /* we have to regroup it */ + + /* first, disconnect everything and send notification*/ + + work = basename_list; + while (work) + { + auto fd = static_cast(work->data); + work = work->next; + g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr); + + if (fd->parent) + { + FileData *old_parent = fd->parent; + g_assert(old_parent->parent == nullptr || old_parent->sidecar_files == nullptr); + file_data_ref(old_parent); + file_data_disconnect_sidecar_file(old_parent, fd); + file_data_send_notification(old_parent, NOTIFY_REREAD); + file_data_unref(old_parent); + } + + while (fd->sidecar_files) + { + auto sfd = static_cast(fd->sidecar_files->data); + g_assert(sfd->parent == nullptr || sfd->sidecar_files == nullptr); + file_data_ref(sfd); + file_data_disconnect_sidecar_file(fd, sfd); + file_data_send_notification(sfd, NOTIFY_REREAD); + file_data_unref(sfd); + } + file_data_send_notification(fd, NOTIFY_GROUPING); + + g_assert(fd->parent == nullptr && fd->sidecar_files == nullptr); + } + + /* now we can form the new group */ + work = basename_list->next; + new_sidecars = nullptr; + while (work) + { + auto sfd = static_cast(work->data); + g_assert(sfd->magick == FD_MAGICK); + g_assert(sfd->parent == nullptr && sfd->sidecar_files == nullptr); + sfd->parent = parent_fd; + new_sidecars = g_list_prepend(new_sidecars, sfd); + work = work->next; + } + g_assert(parent_fd->sidecar_files == nullptr); + parent_fd->sidecar_files = g_list_reverse(new_sidecars); + DEBUG_1("basename group changed for %s", parent_fd->path); +} + + +static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd) +{ + g_assert(target->magick == FD_MAGICK); + g_assert(sfd->magick == FD_MAGICK); + g_assert(g_list_find(target->sidecar_files, sfd)); + + file_data_ref(target); + file_data_ref(sfd); + + g_assert(sfd->parent == target); + + file_data_increment_version(sfd); /* increments both sfd and target */ + + target->sidecar_files = g_list_remove(target->sidecar_files, sfd); + sfd->parent = nullptr; + g_free(sfd->extended_extension); + sfd->extended_extension = nullptr; + + file_data_unref(target); + file_data_unref(sfd); +} + +/* disables / enables grouping for particular file, sends UPDATE notification */ +void FileData::file_data_disable_grouping(FileData *fd, gboolean disable) +{ + if (!fd->disable_grouping == !disable) return; + + fd->disable_grouping = !!disable; + + if (disable) + { + if (fd->parent) + { + FileData *parent = ::file_data_ref(fd->parent); + file_data_disconnect_sidecar_file(parent, fd); + file_data_send_notification(parent, NOTIFY_GROUPING); + ::file_data_unref(parent); + } + else if (fd->sidecar_files) + { + GList *sidecar_files = filelist_copy(fd->sidecar_files); + GList *work = sidecar_files; + while (work) + { + auto sfd = static_cast(work->data); + work = work->next; + file_data_disconnect_sidecar_file(fd, sfd); + file_data_send_notification(sfd, NOTIFY_GROUPING); + } + file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */ + filelist_free(sidecar_files); + } + else + { + file_data_increment_version(fd); /* the functions called in the cases above increments the version too */ + } + } + else + { + file_data_increment_version(fd); + /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */ + } + file_data_send_notification(fd, NOTIFY_GROUPING); +} + +void FileData::file_data_disable_grouping_list(GList *fd_list, gboolean disable) +{ + GList *work; + + work = fd_list; + while (work) + { + auto fd = static_cast(work->data); + + ::file_data_disable_grouping(fd, disable); + work = work->next; + } +} + + + +/* + *----------------------------------------------------------------------------- + * filelist sorting + *----------------------------------------------------------------------------- + */ + + +gint FileData::filelist_sort_compare_filedata(FileData *fa, FileData *fb) +{ + gint ret; + if (!filelist_sort_ascend) + { + std::swap(fa, fb); + } + + switch (filelist_sort_method) + { + case SORT_NAME: + break; + case SORT_SIZE: + if (fa->size < fb->size) return -1; + if (fa->size > fb->size) return 1; + /* fall back to name */ + break; + case SORT_TIME: + if (fa->date < fb->date) return -1; + if (fa->date > fb->date) return 1; + /* fall back to name */ + break; + case SORT_CTIME: + if (fa->cdate < fb->cdate) return -1; + if (fa->cdate > fb->cdate) return 1; + /* fall back to name */ + break; + case SORT_EXIFTIME: + if (fa->exifdate < fb->exifdate) return -1; + if (fa->exifdate > fb->exifdate) return 1; + /* fall back to name */ + break; + case SORT_EXIFTIMEDIGITIZED: + if (fa->exifdate_digitized < fb->exifdate_digitized) return -1; + if (fa->exifdate_digitized > fb->exifdate_digitized) return 1; + /* fall back to name */ + break; + case SORT_RATING: + if (fa->rating < fb->rating) return -1; + if (fa->rating > fb->rating) return 1; + /* fall back to name */ + break; + case SORT_CLASS: + if (fa->format_class < fb->format_class) return -1; + if (fa->format_class > fb->format_class) return 1; + /* fall back to name */ + break; + case SORT_NUMBER: + ret = strcmp(fa->collate_key_name_natural, fb->collate_key_name_natural); + if (ret != 0) return ret; + /* fall back to name */ + break; + default: + break; + } + + if (filelist_sort_case) + ret = strcmp(fa->collate_key_name, fb->collate_key_name); + else + ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase); + + if (ret != 0) return ret; + + /* do not return 0 unless the files are really the same + file_data_pool ensures that original_path is unique + */ + return strcmp(fa->original_path, fb->original_path); +} + +gint FileData::filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend) +{ + filelist_sort_method = method; + filelist_sort_ascend = ascend; + return filelist_sort_compare_filedata(fa, fb); +} + +static gint filelist_sort_file_cb(gpointer a, gpointer b) +{ + return filelist_sort_compare_filedata(static_cast(a), static_cast(b)); +} + +GList *FileData::filelist_sort_full(GList *list, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb) +{ + filelist_sort_method = method; + filelist_sort_ascend = ascend; + filelist_sort_case = case_sensitive; + return g_list_sort(list, cb); +} + +GList *FileData::filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb) +{ + filelist_sort_method = method; + filelist_sort_ascend = ascend; + filelist_sort_case = case_sensitive; + return g_list_insert_sorted(list, data, cb); +} + +GList *FileData::filelist_sort(GList *list, SortType method, gboolean ascend, gboolean case_sensitive) +{ + return filelist_sort_full(list, method, ascend, case_sensitive, reinterpret_cast(filelist_sort_file_cb)); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +GList *FileData::filelist_insert_sort_unused(GList *list, FileData *fd, SortType method, gboolean ascend) +{ + return filelist_insert_sort_full(list, fd, method, ascend, ascend, (GCompareFunc) filelist_sort_file_cb); +} +#pragma GCC diagnostic pop + +/* + *----------------------------------------------------------------------------- + * basename hash - grouping of sidecars in filelist + *----------------------------------------------------------------------------- + */ + + +static GHashTable *file_data_basename_hash_new() +{ + return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, nullptr); +} + +static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd) +{ + GList *list; + gchar *basename = g_strndup(fd->path, fd->extension - fd->path); + + list = static_cast(g_hash_table_lookup(basename_hash, basename)); + + if (!list) + { + DEBUG_1("TG: basename_hash not found for %s",fd->path); + const gchar *parent_extension = registered_extension_from_path(basename); + + if (parent_extension) + { + DEBUG_1("TG: parent extension %s",parent_extension); + gchar *parent_basename = g_strndup(basename, parent_extension - basename); + DEBUG_1("TG: parent basename %s",parent_basename); + auto parent_fd = static_cast(g_hash_table_lookup(file_data_pool, basename)); + if (parent_fd) + { + DEBUG_1("TG: parent fd found"); + list = static_cast(g_hash_table_lookup(basename_hash, parent_basename)); + if (!g_list_find(list, parent_fd)) + { + DEBUG_1("TG: parent fd doesn't fit"); + g_free(parent_basename); + list = nullptr; + } + else + { + g_free(basename); + basename = parent_basename; + fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL); + } + } + } + } + + if (!g_list_find(list, fd)) + { + list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext); + g_hash_table_insert(basename_hash, basename, list); + } + else + { + g_free(basename); + } + return list; +} + +static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash) +{ + file_data_basename_hash_insert(static_cast(basename_hash), static_cast(fd)); +} + +static void file_data_basename_hash_remove_list(gpointer, gpointer value, gpointer) +{ + filelist_free(static_cast(value)); +} + +static void file_data_basename_hash_free(GHashTable *basename_hash) +{ + g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, nullptr); + g_hash_table_destroy(basename_hash); +} + +/* + *----------------------------------------------------------------------------- + * handling sidecars in filelist + *----------------------------------------------------------------------------- + */ + +static GList *filelist_filter_out_sidecars(GList *flist) +{ + GList *work = flist; + GList *flist_filtered = nullptr; + + while (work) + { + auto fd = static_cast(work->data); + + work = work->next; + if (fd->parent) /* remove fd's that are children */ + file_data_unref(fd); + else + flist_filtered = g_list_prepend(flist_filtered, fd); + } + g_list_free(flist); + + return flist_filtered; +} + +static void file_data_basename_hash_to_sidecars(gpointer, gpointer value, gpointer) +{ + auto basename_list = static_cast(value); + file_data_check_sidecars(basename_list); +} + + +static gboolean is_hidden_file(const gchar *name) +{ + if (name[0] != '.') return FALSE; + if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE; + return TRUE; +} + +/* + *----------------------------------------------------------------------------- + * the main filelist function + *----------------------------------------------------------------------------- + */ + +static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks) +{ + DIR *dp; + struct dirent *dir; + gchar *pathl; + GList *dlist = nullptr; + GList *flist = nullptr; + GList *xmp_files = nullptr; + gint (*stat_func)(const gchar *path, struct stat *buf); + GHashTable *basename_hash = nullptr; + + g_assert(files || dirs); + + if (files) *files = nullptr; + if (dirs) *dirs = nullptr; + + pathl = path_from_utf8(dir_path); + if (!pathl) return FALSE; + + dp = opendir(pathl); + if (dp == nullptr) + { + g_free(pathl); + return FALSE; + } + + if (files) basename_hash = file_data_basename_hash_new(); + + if (follow_symlinks) + stat_func = stat; + else + stat_func = lstat; + + while ((dir = readdir(dp)) != nullptr) + { + struct stat ent_sbuf; + const gchar *name = dir->d_name; + gchar *filepath; + + if (!options->file_filter.show_hidden_files && is_hidden_file(name)) + continue; + + filepath = g_build_filename(pathl, name, NULL); + if (stat_func(filepath, &ent_sbuf) >= 0) + { + if (S_ISDIR(ent_sbuf.st_mode)) + { + /* we ignore the .thumbnails dir for cleanliness */ + if (dirs && + (name[0] != '.' || (name[1] != '\0' && (name[1] != '.' || name[2] != '\0'))) && + strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 && + strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 && + strcmp(name, THUMB_FOLDER_LOCAL) != 0) + { + dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE)); + } + } + else + { + if (files && filter_name_exists(name)) + { + FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE); + flist = g_list_prepend(flist, fd); + if (fd->sidecar_priority && !fd->disable_grouping) + { + if (strcmp(fd->extension, ".xmp") != 0) + file_data_basename_hash_insert(basename_hash, fd); + else + xmp_files = g_list_append(xmp_files, fd); + } + } + } + } + else + { + if (errno == EOVERFLOW) + { + log_printf("stat(): EOVERFLOW, skip '%s'", filepath); + } + } + g_free(filepath); + } + + closedir(dp); + + g_free(pathl); + + if (xmp_files) + { + g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash); + g_list_free(xmp_files); + } + + if (dirs) *dirs = dlist; + + if (files) + { + g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, nullptr); + + *files = filelist_filter_out_sidecars(flist); + } + if (basename_hash) file_data_basename_hash_free(basename_hash); + + return TRUE; +} + +gboolean FileData::filelist_read(FileData *dir_fd, GList **files, GList **dirs) +{ + return filelist_read_real(dir_fd->path, files, dirs, TRUE); +} + +gboolean FileData::filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs) +{ + return filelist_read_real(dir_fd->path, files, dirs, FALSE); +} + +FileData *FileData::file_data_new_group(const gchar *path_utf8) +{ + gchar *dir; + struct stat st; + FileData *fd; + GList *files; + + if (!file_data_pool) + { + file_data_pool = g_hash_table_new(g_str_hash, g_str_equal); + } + + if (!stat_utf8(path_utf8, &st)) + { + st.st_size = 0; + st.st_mtime = 0; + } + + if (S_ISDIR(st.st_mode)) + return file_data_new(path_utf8, &st, TRUE); + + dir = remove_level_from_path(path_utf8); + + filelist_read_real(dir, &files, nullptr, TRUE); + + fd = static_cast(g_hash_table_lookup(file_data_pool, path_utf8)); + if (!fd) fd = file_data_new(path_utf8, &st, TRUE); + if (fd) + { + ::file_data_ref(fd); + } + + filelist_free(files); + g_free(dir); + return fd; +} + + +void FileData::filelist_free(GList *list) +{ + GList *work; + + work = list; + while (work) + { + ::file_data_unref((FileData *)work->data); + work = work->next; + } + + g_list_free(list); +} + + +GList *FileData::filelist_copy(GList *list) +{ + GList *new_list = nullptr; + + for (GList *work = list; work; work = work->next) + { + auto fd = static_cast(work->data); + + new_list = g_list_prepend(new_list, ::file_data_ref(fd)); + } + + return g_list_reverse(new_list); +} + +GList *FileData::filelist_from_path_list(GList *list) +{ + GList *new_list = nullptr; + GList *work; + + work = list; + while (work) + { + gchar *path; + + path = static_cast(work->data); + work = work->next; + + new_list = g_list_prepend(new_list, file_data_new_group(path)); + } + + return g_list_reverse(new_list); +} + +GList *FileData::filelist_to_path_list(GList *list) +{ + GList *new_list = nullptr; + GList *work; + + work = list; + while (work) + { + FileData *fd; + + fd = static_cast(work->data); + work = work->next; + + new_list = g_list_prepend(new_list, g_strdup(fd->path)); + } + + return g_list_reverse(new_list); +} + +GList *FileData::filelist_filter(GList *list, gboolean is_dir_list) +{ + GList *work; + + if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list; + + work = list; + while (work) + { + auto fd = static_cast(work->data); + const gchar *name = fd->name; + GList *link = work; + work = work->next; + + if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) || + (!is_dir_list && !filter_name_exists(name)) || + (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 || + strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) ) + { + list = g_list_remove_link(list, link); + ::file_data_unref(fd); + g_list_free(link); + } + } + + return list; +} + +/* + *----------------------------------------------------------------------------- + * filelist recursive + *----------------------------------------------------------------------------- + */ + +static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b) +{ + return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path); +} + +GList *FileData::filelist_sort_path(GList *list) +{ + return g_list_sort(list, filelist_sort_path_cb); +} + +static void filelist_recursive_append(GList **list, GList *dirs) +{ + GList *work; + + work = dirs; + while (work) + { + auto fd = static_cast(work->data); + GList *f; + GList *d; + + if (filelist_read(fd, &f, &d)) + { + f = filelist_filter(f, FALSE); + f = filelist_sort_path(f); + *list = g_list_concat(*list, f); + + d = filelist_filter(d, TRUE); + d = filelist_sort_path(d); + filelist_recursive_append(list, d); + filelist_free(d); + } + + work = work->next; + } +} + +static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend, gboolean case_sensitive) +{ + GList *work; + + work = dirs; + while (work) + { + auto fd = static_cast(work->data); + GList *f; + GList *d; + + if (filelist_read(fd, &f, &d)) + { + f = filelist_filter(f, FALSE); + f = filelist_sort_full(f, method, ascend, case_sensitive, reinterpret_cast(filelist_sort_file_cb)); + *list = g_list_concat(*list, f); + + d = filelist_filter(d, TRUE); + d = filelist_sort_path(d); + filelist_recursive_append_full(list, d, method, ascend, case_sensitive); + filelist_free(d); + } + + work = work->next; + } +} + +GList *FileData::filelist_recursive(FileData *dir_fd) +{ + GList *list; + GList *d; + + if (!::filelist_read(dir_fd, &list, &d)) return nullptr; + list = filelist_filter(list, FALSE); + list = filelist_sort_path(list); + + d = filelist_filter(d, TRUE); + d = filelist_sort_path(d); + filelist_recursive_append(&list, d); + filelist_free(d); + + return list; +} + +GList *FileData::filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend, gboolean case_sensitive) +{ + GList *list; + GList *d; + + if (!::filelist_read(dir_fd, &list, &d)) return nullptr; + list = filelist_filter(list, FALSE); + list = filelist_sort_full(list, method, ascend, case_sensitive, reinterpret_cast(filelist_sort_file_cb)); + + d = filelist_filter(d, TRUE); + d = filelist_sort_path(d); + filelist_recursive_append_full(&list, d, method, ascend, case_sensitive); + filelist_free(d); + + return list; +} + +/* + *----------------------------------------------------------------------------- + * file modification support + *----------------------------------------------------------------------------- + */ + + +void FileData::file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd) +{ + if (!fdci && fd) fdci = fd->change; + + if (!fdci) return; + + g_free(fdci->source); + g_free(fdci->dest); + + g_free(fdci); + + if (fd) fd->change = nullptr; +} + +static gboolean file_data_can_write_directly(FileData *fd) +{ + return filter_name_is_writable(fd->extension); +} + +static gboolean file_data_can_write_sidecar(FileData *fd) +{ + return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension); +} + +gchar *FileData::file_data_get_sidecar_path(FileData *fd, gboolean existing_only) +{ + gchar *sidecar_path = nullptr; + GList *work; + + if (!file_data_can_write_sidecar(fd)) return nullptr; + + work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files; + gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL); + while (work) + { + auto sfd = static_cast(work->data); + work = work->next; + if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0) + { + sidecar_path = g_strdup(sfd->path); + break; + } + } + g_free(extended_extension); + + if (!existing_only && !sidecar_path) + { + if (options->metadata.sidecar_extended_name) + sidecar_path = g_strconcat(fd->path, ".xmp", NULL); + else + { + gchar *base = g_strndup(fd->path, fd->extension - fd->path); + sidecar_path = g_strconcat(base, ".xmp", NULL); + g_free(base); + } + } + + return sidecar_path; +} + +/* + * marks and orientation + */ + +static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE]; +static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE]; +static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE]; +static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE]; + +gboolean FileData::file_data_get_mark(FileData *fd, gint n) +{ + gboolean valid = (fd->valid_marks & (1 << n)); + + if (file_data_get_mark_func[n] && !valid) + { + guint old = fd->marks; + gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]); + + if (!value != !(fd->marks & (1 << n))) + { + fd->marks = fd->marks ^ (1 << n); + } + + fd->valid_marks |= (1 << n); + if (old && !fd->marks) /* keep files with non-zero marks in memory */ + { + ::file_data_unref(fd); + } + else if (!old && fd->marks) + { + ::file_data_ref(fd); + } + } + + return !!(fd->marks & (1 << n)); +} + +guint FileData::file_data_get_marks(FileData *fd) +{ + gint i; + for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i); + return fd->marks; +} + +void FileData::file_data_set_mark(FileData *fd, gint n, gboolean value) +{ + guint old; + if (!value == !file_data_get_mark(fd, n)) return; + + if (file_data_set_mark_func[n]) + { + (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]); + } + + old = fd->marks; + + fd->marks = fd->marks ^ (1 << n); + + if (old && !fd->marks) /* keep files with non-zero marks in memory */ + { + ::file_data_unref(fd); + } + else if (!old && fd->marks) + { + ::file_data_ref(fd); + } + + file_data_increment_version(fd); + file_data_send_notification(fd, NOTIFY_MARKS); +} + +gboolean FileData::file_data_filter_marks(FileData *fd, guint filter) +{ + gint i; + for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i); + return ((fd->marks & filter) == filter); +} + +GList *FileData::file_data_filter_marks_list(GList *list, guint filter) +{ + GList *work; + + work = list; + while (work) + { + auto fd = static_cast(work->data); + GList *link = work; + work = work->next; + + if (!::file_data_filter_marks(fd, filter)) + { + list = g_list_remove_link(list, link); + ::file_data_unref(fd); + g_list_free(link); + } + } + + return list; +} + +gboolean FileData::file_data_mark_to_selection(FileData *fd, gint mark, MarkToSelectionMode mode, gboolean selected) +{ + gint n = mark - 1; + gboolean mark_val = file_data_get_mark(fd, n); + + switch (mode) + { + case MTS_MODE_MINUS: return !mark_val && selected; + case MTS_MODE_SET: return mark_val; + case MTS_MODE_OR: return mark_val || selected; + case MTS_MODE_AND: return mark_val && selected; + } + + return selected; // arbitrary value, we shouldn't get here +} + +void FileData::file_data_selection_to_mark(FileData *fd, gint mark, SelectionToMarkMode mode) +{ + gint n = mark - 1; + + switch (mode) + { + case STM_MODE_RESET: file_data_set_mark(fd, n, FALSE); break; + case STM_MODE_SET: file_data_set_mark(fd, n, TRUE); break; + case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n)); break; + } +} + +gboolean FileData::file_data_filter_file_filter(FileData *fd, GRegex *filter) +{ + return g_regex_match(filter, fd->name, static_cast(0), nullptr); +} + +GList *FileData::file_data_filter_file_filter_list(GList *list, GRegex *filter) +{ + GList *work; + + work = list; + while (work) + { + auto fd = static_cast(work->data); + GList *link = work; + work = work->next; + + if (!::file_data_filter_file_filter(fd, filter)) + { + list = g_list_remove_link(list, link); + ::file_data_unref(fd); + g_list_free(link); + } + } + + return list; +} + +static gboolean file_data_filter_class(FileData *fd, guint filter) +{ + gint i; + + for (i = 0; i < FILE_FORMAT_CLASSES; i++) + { + if (filter & (1 << i)) + { + if (static_cast(i) == filter_file_get_class(fd->path)) + { + return TRUE; + } + } + } + + return FALSE; +} + +GList *FileData::file_data_filter_class_list(GList *list, guint filter) +{ + GList *work; + + work = list; + while (work) + { + auto fd = static_cast(work->data); + GList *link = work; + work = work->next; + + if (!file_data_filter_class(fd, filter)) + { + list = g_list_remove_link(list, link); + ::file_data_unref(fd); + g_list_free(link); + } + } + + return list; +} + +static void file_data_notify_mark_func(gpointer, gpointer value, gpointer) +{ + auto fd = static_cast(value); + file_data_increment_version(fd); + file_data_send_notification(fd, NOTIFY_MARKS); +} + +gboolean FileData::file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify) +{ + if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE; + + if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]); + + file_data_get_mark_func[n] = get_mark_func; + file_data_set_mark_func[n] = set_mark_func; + file_data_mark_func_data[n] = data; + file_data_destroy_mark_func[n] = notify; + + if (get_mark_func && file_data_pool) + { + /* this effectively changes all known files */ + g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, nullptr); + } + + return TRUE; +} + +void FileData::file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data) +{ + if (get_mark_func) *get_mark_func = file_data_get_mark_func[n]; + if (set_mark_func) *set_mark_func = file_data_set_mark_func[n]; + if (data) *data = file_data_mark_func_data[n]; +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +gint FileData::file_data_get_user_orientation_unused(FileData *fd) +{ + return fd->user_orientation; +} + +void FileData::file_data_set_user_orientation_unused(FileData *fd, gint value) +{ + if (fd->user_orientation == value) return; + + fd->user_orientation = value; + file_data_increment_version(fd); + file_data_send_notification(fd, NOTIFY_ORIENTATION); +} +#pragma GCC diagnostic pop + + +/* + * file_data - operates on the given fd + * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent + */ + + +/* return list of sidecar file extensions in a string */ +gchar *FileData::file_data_sc_list_to_string(FileData *fd) +{ + GList *work; + GString *result = g_string_new(""); + + work = fd->sidecar_files; + while (work) + { + auto sfd = static_cast(work->data); + + result = g_string_append(result, "+ "); + result = g_string_append(result, sfd->extension); + work = work->next; + if (work) result = g_string_append_c(result, ' '); + } + + return g_string_free(result, FALSE); +} + + + +/* + * add FileDataChangeInfo (see typedefs.h) for the given operation + * uses file_data_add_change_info + * + * fails if the fd->change already exists - change operations can't run in parallel + * fd->change_info works as a lock + * + * dest can be NULL - in this case the current name is used for now, it will + * be changed later + */ + +/* + FileDataChangeInfo types: + COPY + MOVE - path is changed, name may be changed too + RENAME - path remains unchanged, name is changed + extension should remain (FIXME should we allow editing extension? it will make problems with grouping) + sidecar names are changed too, extensions are not changed + DELETE + UPDATE - file size, date or grouping has been changed +*/ + +gboolean FileData::file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest) +{ + FileDataChangeInfo *fdci; + + if (fd->change) return FALSE; + + fdci = g_new0(FileDataChangeInfo, 1); + + fdci->type = type; + + if (src) + fdci->source = g_strdup(src); + else + fdci->source = g_strdup(fd->path); + + if (dest) + fdci->dest = g_strdup(dest); + + fd->change = fdci; + + return TRUE; +} + +static void file_data_planned_change_remove(FileData *fd) +{ + if (file_data_planned_change_hash && + (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME)) + { + if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd) + { + DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path); + g_hash_table_remove(file_data_planned_change_hash, fd->change->dest); + ::file_data_unref(fd); + if (g_hash_table_size(file_data_planned_change_hash) == 0) + { + g_hash_table_destroy(file_data_planned_change_hash); + file_data_planned_change_hash = nullptr; + DEBUG_1("planned change: empty"); + } + } + } +} + + +void FileData::file_data_free_ci(FileData *fd) +{ + FileDataChangeInfo *fdci = fd->change; + + if (!fdci) return; + + file_data_planned_change_remove(fd); + + if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE); + + g_free(fdci->source); + g_free(fdci->dest); + + g_free(fdci); + + fd->change = nullptr; +} + +void FileData::file_data_set_regroup_when_finished(FileData *fd, gboolean enable) +{ + FileDataChangeInfo *fdci = fd->change; + if (!fdci) return; + fdci->regroup_when_finished = enable; +} + +static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type) +{ + GList *work; + + if (fd->parent) fd = fd->parent; + + if (fd->change) return FALSE; + + work = fd->sidecar_files; + while (work) + { + auto sfd = static_cast(work->data); + + if (sfd->change) return FALSE; + work = work->next; + } + + file_data_add_ci(fd, type, nullptr, nullptr); + + work = fd->sidecar_files; + while (work) + { + auto sfd = static_cast(work->data); + + file_data_add_ci(sfd, type, nullptr, nullptr); + work = work->next; + } + + return TRUE; +} + +static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type) +{ + GList *work; + + if (fd->parent) fd = fd->parent; + + if (!fd->change || fd->change->type != type) return FALSE; + + work = fd->sidecar_files; + while (work) + { + auto sfd = static_cast(work->data); + + if (!sfd->change || sfd->change->type != type) return FALSE; + work = work->next; + } + + return TRUE; +} + + +gboolean FileData::file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path) +{ + if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE; + file_data_sc_update_ci_copy(fd, dest_path); + return TRUE; +} + +gboolean FileData::file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path) +{ + if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE; + file_data_sc_update_ci_move(fd, dest_path); + return TRUE; +} + +gboolean FileData::file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path) +{ + if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE; + file_data_sc_update_ci_rename(fd, dest_path); + return TRUE; +} + +gboolean FileData::file_data_sc_add_ci_delete(FileData *fd) +{ + return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE); +} + +gboolean FileData::file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path) +{ + if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE; + file_data_sc_update_ci_unspecified(fd, dest_path); + return TRUE; +} + +gboolean FileData::file_data_add_ci_write_metadata(FileData *fd) +{ + return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, nullptr, nullptr); +} + +void FileData::file_data_sc_free_ci(FileData *fd) +{ + GList *work; + + if (fd->parent) fd = fd->parent; + + file_data_free_ci(fd); + + work = fd->sidecar_files; + while (work) + { + auto sfd = static_cast(work->data); + + file_data_free_ci(sfd); + work = work->next; + } +} + +gboolean FileData::file_data_sc_add_ci_delete_list(GList *fd_list) +{ + GList *work; + gboolean ret = TRUE; + + work = fd_list; + while (work) + { + auto fd = static_cast(work->data); + + if (!::file_data_sc_add_ci_delete(fd)) ret = FALSE; + work = work->next; + } + + return ret; +} + +static void file_data_sc_revert_ci_list(GList *fd_list) +{ + GList *work; + + work = fd_list; + while (work) + { + auto fd = static_cast(work->data); + + file_data_sc_free_ci(fd); + work = work->prev; + } +} + +static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *)) +{ + GList *work; + + work = fd_list; + while (work) + { + auto fd = static_cast(work->data); + + if (!func(fd, dest)) + { + file_data_sc_revert_ci_list(work->prev); + return FALSE; + } + work = work->next; + } + + return TRUE; +} + +gboolean FileData::file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest) +{ + return file_data_sc_add_ci_list_call_func(fd_list, dest, ::file_data_sc_add_ci_copy); +} + +gboolean FileData::file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest) +{ + return file_data_sc_add_ci_list_call_func(fd_list, dest, ::file_data_sc_add_ci_move); +} + +gboolean FileData::file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest) +{ + return file_data_sc_add_ci_list_call_func(fd_list, dest, ::file_data_sc_add_ci_rename); +} + +gboolean FileData::file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest) +{ + return file_data_sc_add_ci_list_call_func(fd_list, dest, ::file_data_sc_add_ci_unspecified); +} + +gboolean FileData::file_data_add_ci_write_metadata_list(GList *fd_list) +{ + GList *work; + gboolean ret = TRUE; + + work = fd_list; + while (work) + { + auto fd = static_cast(work->data); + + if (!::file_data_add_ci_write_metadata(fd)) ret = FALSE; + work = work->next; + } + + return ret; +} + +void FileData::file_data_free_ci_list(GList *fd_list) +{ + GList *work; + + work = fd_list; + while (work) + { + auto fd = static_cast(work->data); + + ::file_data_free_ci(fd); + work = work->next; + } +} + +void FileData::file_data_sc_free_ci_list(GList *fd_list) +{ + GList *work; + + work = fd_list; + while (work) + { + auto fd = static_cast(work->data); + + ::file_data_sc_free_ci(fd); + work = work->next; + } +} + +/* + * update existing fd->change, it will be used from dialog callbacks for interactive editing + * fails if fd->change does not exist or the change type does not match + */ + +static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path) +{ + FileDataChangeType type = fd->change->type; + + if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME) + { + FileData *ofd; + + if (!file_data_planned_change_hash) + file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal); + + if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd) + { + DEBUG_1("planned change: removing %s -> %s", old_path, fd->path); + g_hash_table_remove(file_data_planned_change_hash, old_path); + ::file_data_unref(fd); + } + + ofd = static_cast(g_hash_table_lookup(file_data_planned_change_hash, new_path)); + if (ofd != fd) + { + if (ofd) + { + DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path); + g_hash_table_remove(file_data_planned_change_hash, new_path); + ::file_data_unref(ofd); + } + + DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path); + ::file_data_ref(fd); + g_hash_table_insert(file_data_planned_change_hash, new_path, fd); + } + } +} + +static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path) +{ + gchar *old_path = fd->change->dest; + + fd->change->dest = g_strdup(dest_path); + file_data_update_planned_change_hash(fd, old_path, fd->change->dest); + g_free(old_path); +} + +static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path) +{ + const gchar *extension = registered_extension_from_path(fd->change->source); + gchar *base = remove_extension_from_path(dest_path); + gchar *old_path = fd->change->dest; + + fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL); + file_data_update_planned_change_hash(fd, old_path, fd->change->dest); + + g_free(old_path); + g_free(base); +} + +static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path) +{ + GList *work; + gchar *dest_path_full = nullptr; + + if (fd->parent) fd = fd->parent; + + if (!dest_path) + { + dest_path = fd->path; + } + else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */ + { + gchar *dir = remove_level_from_path(fd->path); + + dest_path_full = g_build_filename(dir, dest_path, NULL); + g_free(dir); + dest_path = dest_path_full; + } + else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */ + { + dest_path_full = g_build_filename(dest_path, fd->name, NULL); + dest_path = dest_path_full; + } + + file_data_update_ci_dest(fd, dest_path); + + work = fd->sidecar_files; + while (work) + { + auto sfd = static_cast(work->data); + + file_data_update_ci_dest_preserve_ext(sfd, dest_path); + work = work->next; + } + + g_free(dest_path_full); +} + +static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type) +{ + if (!file_data_sc_check_ci(fd, type)) return FALSE; + file_data_sc_update_ci(fd, dest_path); + return TRUE; +} + +gboolean FileData::file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path) +{ + return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY); +} + +gboolean FileData::file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path) +{ + return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE); +} + +gboolean FileData::file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path) +{ + return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME); +} + +gboolean FileData::file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path) +{ + return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED); +} + +static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list, + const gchar *dest, + gboolean (*func)(FileData *, const gchar *)) +{ + GList *work; + gboolean ret = TRUE; + + work = fd_list; + while (work) + { + auto fd = static_cast(work->data); + + if (!func(fd, dest)) ret = FALSE; + work = work->next; + } + + return ret; +} + +gboolean FileData::file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest) +{ + return file_data_sc_update_ci_list_call_func(fd_list, dest, ::file_data_sc_update_ci_move); +} + +gboolean FileData::file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest) +{ + return file_data_sc_update_ci_list_call_func(fd_list, dest, ::file_data_sc_update_ci_copy); +} + +gboolean FileData::file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest) +{ + return file_data_sc_update_ci_list_call_func(fd_list, dest, ::file_data_sc_update_ci_unspecified); +} + + +/* + * verify source and dest paths - dest image exists, etc. + * it should detect all possible problems with the planned operation + */ + +gint FileData::file_data_verify_ci(FileData *fd, GList *list) +{ + gint ret = CHANGE_OK; + gchar *dir; + GList *work = nullptr; + FileData *fd1 = nullptr; + + if (!fd->change) + { + DEBUG_1("Change checked: no change info: %s", fd->path); + return ret; + } + + if (!isname(fd->path)) + { + /* this probably should not happen */ + ret |= CHANGE_NO_SRC; + DEBUG_1("Change checked: file does not exist: %s", fd->path); + return ret; + } + + dir = remove_level_from_path(fd->path); + + if (fd->change->type != FILEDATA_CHANGE_DELETE && + fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */ + fd->change->type != FILEDATA_CHANGE_RENAME && + fd->change->type != FILEDATA_CHANGE_WRITE_METADATA && + fd->modified_xmp) + { + ret |= CHANGE_WARN_UNSAVED_META; + DEBUG_1("Change checked: unsaved metadata: %s", fd->path); + } + + if (fd->change->type != FILEDATA_CHANGE_DELETE && + fd->change->type != FILEDATA_CHANGE_WRITE_METADATA && + !access_file(fd->path, R_OK)) + { + ret |= CHANGE_NO_READ_PERM; + DEBUG_1("Change checked: no read permission: %s", fd->path); + } + else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) && + !access_file(dir, W_OK)) + { + ret |= CHANGE_NO_WRITE_PERM_DIR; + DEBUG_1("Change checked: source dir is readonly: %s", fd->path); + } + else if (fd->change->type != FILEDATA_CHANGE_COPY && + fd->change->type != FILEDATA_CHANGE_UNSPECIFIED && + fd->change->type != FILEDATA_CHANGE_WRITE_METADATA && + !access_file(fd->path, W_OK)) + { + ret |= CHANGE_WARN_NO_WRITE_PERM; + DEBUG_1("Change checked: no write permission: %s", fd->path); + } + /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/... + - that means that there are no hard errors and warnings can be disabled + - the destination is determined during the check + */ + else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA) + { + /* determine destination file */ + gboolean have_dest = FALSE; + gchar *dest_dir = nullptr; + + if (options->metadata.save_in_image_file) + { + if (file_data_can_write_directly(fd)) + { + /* we can write the file directly */ + if (access_file(fd->path, W_OK)) + { + have_dest = TRUE; + } + else + { + if (options->metadata.warn_on_write_problems) + { + ret |= CHANGE_WARN_NO_WRITE_PERM; + DEBUG_1("Change checked: file is not writable: %s", fd->path); + } + } + } + else if (file_data_can_write_sidecar(fd)) + { + /* we can write sidecar */ + gchar *sidecar = file_data_get_sidecar_path(fd, FALSE); + if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK))) + { + file_data_update_ci_dest(fd, sidecar); + have_dest = TRUE; + } + else + { + if (options->metadata.warn_on_write_problems) + { + ret |= CHANGE_WARN_NO_WRITE_PERM; + DEBUG_1("Change checked: file is not writable: %s", sidecar); + } + } + g_free(sidecar); + } + } + + if (!have_dest) + { + /* write private metadata file under ~/.geeqie */ + + /* If an existing metadata file exists, we will try writing to + * it's location regardless of the user's preference. + */ + gchar *metadata_path = nullptr; +#if HAVE_EXIV2 + /* but ignore XMP if we are not able to write it */ + metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path); +#endif + if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path); + + if (metadata_path && !access_file(metadata_path, W_OK)) + { + g_free(metadata_path); + metadata_path = nullptr; + } + + if (!metadata_path) + { + mode_t mode = 0755; + + dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode); + if (recursive_mkdir_if_not_exists(dest_dir, mode)) + { + gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL); + + metadata_path = g_build_filename(dest_dir, filename, NULL); + g_free(filename); + } + } + if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK))) + { + file_data_update_ci_dest(fd, metadata_path); + } + else + { + ret |= CHANGE_NO_WRITE_PERM_DEST; + DEBUG_1("Change checked: file is not writable: %s", metadata_path); + } + g_free(metadata_path); + } + g_free(dest_dir); + } + + if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA) + { + gboolean same; + gchar *dest_dir; + + same = (strcmp(fd->path, fd->change->dest) == 0); + + if (!same) + { + const gchar *dest_ext = registered_extension_from_path(fd->change->dest); + if (!dest_ext) dest_ext = ""; + if (!options->file_filter.disable_file_extension_checks) + { + if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0) + { + ret |= CHANGE_WARN_CHANGED_EXT; + DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest); + } + } + } + else + { + if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */ + { + ret |= CHANGE_WARN_SAME; + DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest); + } + } + + dest_dir = remove_level_from_path(fd->change->dest); + + if (!isdir(dest_dir)) + { + ret |= CHANGE_NO_DEST_DIR; + DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest); + } + else if (!access_file(dest_dir, W_OK)) + { + ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR; + DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest); + } + else if (!same) + { + if (isfile(fd->change->dest)) + { + if (!access_file(fd->change->dest, W_OK)) + { + ret |= CHANGE_NO_WRITE_PERM_DEST; + DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest); + } + else + { + ret |= CHANGE_WARN_DEST_EXISTS; + DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest); + } + } + else if (isdir(fd->change->dest)) + { + ret |= CHANGE_DEST_EXISTS; + DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest); + } + } + + g_free(dest_dir); + } + + /* During a rename operation, check if another planned destination file has + * the same filename + */ + if(fd->change->type == FILEDATA_CHANGE_RENAME || + fd->change->type == FILEDATA_CHANGE_COPY || + fd->change->type == FILEDATA_CHANGE_MOVE) + { + work = list; + while (work) + { + fd1 = static_cast(work->data); + work = work->next; + if (fd1 != nullptr && fd != fd1 ) + { + if (!strcmp(fd->change->dest, fd1->change->dest)) + { + ret |= CHANGE_DUPLICATE_DEST; + } + } + } + } + + fd->change->error = ret; + if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path); + + g_free(dir); + return ret; +} + + +gint FileData::file_data_sc_verify_ci(FileData *fd, GList *list) +{ + GList *work; + gint ret; + + ret = file_data_verify_ci(fd, list); + + work = fd->sidecar_files; + while (work) + { + auto sfd = static_cast(work->data); + + ret |= file_data_verify_ci(sfd, list); + work = work->next; + } + + return ret; +} + +gchar *FileData::file_data_get_error_string(gint error) +{ + GString *result = g_string_new(""); + + if (error & CHANGE_NO_SRC) + { + if (result->len > 0) g_string_append(result, ", "); + g_string_append(result, _("file or directory does not exist")); + } + + if (error & CHANGE_DEST_EXISTS) + { + if (result->len > 0) g_string_append(result, ", "); + g_string_append(result, _("destination already exists")); + } + + if (error & CHANGE_NO_WRITE_PERM_DEST) + { + if (result->len > 0) g_string_append(result, ", "); + g_string_append(result, _("destination can't be overwritten")); + } + + if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR) + { + if (result->len > 0) g_string_append(result, ", "); + g_string_append(result, _("destination directory is not writable")); + } + + if (error & CHANGE_NO_DEST_DIR) + { + if (result->len > 0) g_string_append(result, ", "); + g_string_append(result, _("destination directory does not exist")); + } + + if (error & CHANGE_NO_WRITE_PERM_DIR) + { + if (result->len > 0) g_string_append(result, ", "); + g_string_append(result, _("source directory is not writable")); + } + + if (error & CHANGE_NO_READ_PERM) + { + if (result->len > 0) g_string_append(result, ", "); + g_string_append(result, _("no read permission")); + } + + if (error & CHANGE_WARN_NO_WRITE_PERM) + { + if (result->len > 0) g_string_append(result, ", "); + g_string_append(result, _("file is readonly")); + } + + if (error & CHANGE_WARN_DEST_EXISTS) + { + if (result->len > 0) g_string_append(result, ", "); + g_string_append(result, _("destination already exists and will be overwritten")); + } + + if (error & CHANGE_WARN_SAME) + { + if (result->len > 0) g_string_append(result, ", "); + g_string_append(result, _("source and destination are the same")); + } + + if (error & CHANGE_WARN_CHANGED_EXT) + { + if (result->len > 0) g_string_append(result, ", "); + g_string_append(result, _("source and destination have different extension")); + } + + if (error & CHANGE_WARN_UNSAVED_META) + { + if (result->len > 0) g_string_append(result, ", "); + g_string_append(result, _("there are unsaved metadata changes for the file")); + } + + if (error & CHANGE_DUPLICATE_DEST) + { + if (result->len > 0) g_string_append(result, ", "); + g_string_append(result, _("another destination file has the same filename")); + } + + return g_string_free(result, FALSE); +} + +gint FileData::file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars) +{ + GList *work; + gint all_errors = 0; + gint common_errors = ~0; + gint num; + gint *errors; + gint i; + + if (!list) return 0; + + num = g_list_length(list); + errors = g_new(int, num); + work = list; + i = 0; + while (work) + { + FileData *fd; + gint error; + + fd = static_cast(work->data); + work = work->next; + + error = with_sidecars ? ::file_data_sc_verify_ci(fd, list) : ::file_data_verify_ci(fd, list); + all_errors |= error; + common_errors &= error; + + errors[i] = error; + + i++; + } + + if (desc && all_errors) + { + GList *work; + GString *result = g_string_new(""); + + if (common_errors) + { + gchar *str = file_data_get_error_string(common_errors); + g_string_append(result, str); + g_string_append(result, "\n"); + g_free(str); + } + + work = list; + i = 0; + while (work) + { + FileData *fd; + gint error; + + fd = static_cast(work->data); + work = work->next; + + error = errors[i] & ~common_errors; + + if (error) + { + gchar *str = file_data_get_error_string(error); + g_string_append_printf(result, "%s: %s\n", fd->name, str); + g_free(str); + } + i++; + } + *desc = g_string_free(result, FALSE); + } + + g_free(errors); + return all_errors; +} + + +/* + * perform the change described by FileFataChangeInfo + * it is used for internal operations, + * this function actually operates with files on the filesystem + * it should implement safe delete + */ + +static gboolean file_data_perform_move(FileData *fd) +{ + g_assert(!strcmp(fd->change->source, fd->path)); + return move_file(fd->change->source, fd->change->dest); +} + +static gboolean file_data_perform_copy(FileData *fd) +{ + g_assert(!strcmp(fd->change->source, fd->path)); + return copy_file(fd->change->source, fd->change->dest); +} + +static gboolean file_data_perform_delete(FileData *fd) +{ + if (isdir(fd->path) && !islink(fd->path)) + return rmdir_utf8(fd->path); + + if (options->file_ops.safe_delete_enable) + return file_util_safe_unlink(fd->path); + + return unlink_file(fd->path); +} + +gboolean FileData::file_data_perform_ci(FileData *fd) +{ + /** @FIXME When a directory that is a symbolic link is deleted, + * at this point fd->change is null because no FileDataChangeInfo + * has been set up. Therefore there is a seg. fault. + * This code simply aborts the delete. + */ + if (!fd->change) + { + return FALSE; + } + + FileDataChangeType type = fd->change->type; + + switch (type) + { + case FILEDATA_CHANGE_MOVE: + return file_data_perform_move(fd); + case FILEDATA_CHANGE_COPY: + return file_data_perform_copy(fd); + case FILEDATA_CHANGE_RENAME: + return file_data_perform_move(fd); /* the same as move */ + case FILEDATA_CHANGE_DELETE: + return file_data_perform_delete(fd); + case FILEDATA_CHANGE_WRITE_METADATA: + return metadata_write_perform(fd); + case FILEDATA_CHANGE_UNSPECIFIED: + /* nothing to do here */ + break; + } + return TRUE; +} + + + +gboolean FileData::file_data_sc_perform_ci(FileData *fd) +{ + GList *work; + gboolean ret = TRUE; + FileDataChangeType type = fd->change->type; + + if (!file_data_sc_check_ci(fd, type)) return FALSE; + + work = fd->sidecar_files; + while (work) + { + auto sfd = static_cast(work->data); + + if (!file_data_perform_ci(sfd)) ret = FALSE; + work = work->next; + } + + if (!file_data_perform_ci(fd)) ret = FALSE; + + return ret; +} + +/* + * updates FileData structure according to FileDataChangeInfo + */ + +gboolean FileData::file_data_apply_ci(FileData *fd) +{ + FileDataChangeType type = fd->change->type; + + /** @FIXME delete ?*/ + if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME) + { + DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path); + file_data_planned_change_remove(fd); + + if (g_hash_table_lookup(file_data_pool, fd->change->dest)) + { + /* this change overwrites another file which is already known to other modules + renaming fd would create duplicate FileData structure + the best thing we can do is nothing + */ + /** @FIXME maybe we could copy stuff like marks + */ + DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path); + } + else + { + file_data_set_path(fd, fd->change->dest); + } + } + file_data_increment_version(fd); + file_data_send_notification(fd, NOTIFY_CHANGE); + + return TRUE; +} + +gboolean FileData::file_data_sc_apply_ci(FileData *fd) +{ + GList *work; + FileDataChangeType type = fd->change->type; + + if (!file_data_sc_check_ci(fd, type)) return FALSE; + + work = fd->sidecar_files; + while (work) + { + auto sfd = static_cast(work->data); + + file_data_apply_ci(sfd); + work = work->next; + } + + file_data_apply_ci(fd); + + return TRUE; +} + +static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd) +{ + GList *work; + if (fd->parent) fd = fd->parent; + if (!g_list_find(list, fd)) return FALSE; + + work = fd->sidecar_files; + while (work) + { + if (!g_list_find(list, work->data)) return FALSE; + work = work->next; + } + return TRUE; +} + +GList *FileData::file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list) +{ + GList *out = nullptr; + GList *work = list; + + /* change partial groups to independent files */ + if (ungroup) + { + while (work) + { + auto fd = static_cast(work->data); + work = work->next; + + if (!file_data_list_contains_whole_group(list, fd)) + { + ::file_data_disable_grouping(fd, TRUE); + if (ungrouped_list) + { + *ungrouped_list = g_list_prepend(*ungrouped_list, ::file_data_ref(fd)); + } + } + } + } + + /* remove sidecars from the list, + they can be still accessed via main_fd->sidecar_files */ + work = list; + while (work) + { + auto fd = static_cast(work->data); + work = work->next; + + if (!fd->parent || + (!ungroup && !file_data_list_contains_whole_group(list, fd))) + { + out = g_list_prepend(out, ::file_data_ref(fd)); + } + } + + filelist_free(list); + out = g_list_reverse(out); + + return out; +} + + + + + +/* + * notify other modules about the change described by FileDataChangeInfo + */ + +/* might use file_maint_ functions for now, later it should be changed to a system of callbacks */ +/** @FIXME do we need the ignore_list? It looks like a workaround for ineffective + implementation in view-file-list.cc */ + + +struct NotifyIdleData { + FileData *fd; + NotifyType type; +}; + + +struct NotifyData { + FileDataNotifyFunc func; + gpointer data; + NotifyPriority priority; +}; + +static GList *notify_func_list = nullptr; + +static gint file_data_notify_sort(gconstpointer a, gconstpointer b) +{ + auto nda = static_cast(a); + auto ndb = static_cast(b); + + if (nda->priority < ndb->priority) return -1; + if (nda->priority > ndb->priority) return 1; + return 0; +} + +gboolean FileData::file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority) +{ + NotifyData *nd; + GList *work = notify_func_list; + + while (work) + { + auto nd = static_cast(work->data); + + if (nd->func == func && nd->data == data) + { + g_warning("Notify func already registered"); + return FALSE; + } + work = work->next; + } + + nd = g_new(NotifyData, 1); + nd->func = func; + nd->data = data; + nd->priority = priority; + + notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort); + DEBUG_2("Notify func registered: %p", (void *)nd); + + return TRUE; +} + +gboolean FileData::file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data) +{ + GList *work = notify_func_list; + + while (work) + { + auto nd = static_cast(work->data); + + if (nd->func == func && nd->data == data) + { + notify_func_list = g_list_delete_link(notify_func_list, work); + DEBUG_2("Notify func unregistered: %p", (void *)nd); + g_free(nd); + return TRUE; + } + work = work->next; + } + + g_warning("Notify func not found"); + return FALSE; +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +gboolean FileData::file_data_send_notification_idle_cb_unused(gpointer data) +{ + auto *nid = (NotifyIdleData *)data; + GList *work = notify_func_list; + + while (work) + { + auto *nd = (NotifyData *)work->data; + + nd->func(nid->fd, nid->type, nd->data); + work = work->next; + } + ::file_data_unref(nid->fd); + g_free(nid); + return FALSE; +} +#pragma GCC diagnostic pop + +void FileData::file_data_send_notification(FileData *fd, NotifyType type) +{ + GList *work = notify_func_list; + + while (work) + { + auto nd = static_cast(work->data); + + nd->func(fd, type, nd->data); + work = work->next; + } + /* + NotifyIdleData *nid = g_new0(NotifyIdleData, 1); + nid->fd = ::file_data_ref(fd); + nid->type = type; + g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL); + */ +} + +static GHashTable *file_data_monitor_pool = nullptr; +static guint realtime_monitor_id = 0; /* event source id */ + +static void realtime_monitor_check_cb(gpointer key, gpointer, gpointer) +{ + auto fd = static_cast(key); + + file_data_check_changed_files(fd); + + DEBUG_1("monitor %s", fd->path); +} + +static gboolean realtime_monitor_cb(gpointer) +{ + if (!options->update_on_time_change) return TRUE; + g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, nullptr); + return TRUE; +} + +gboolean FileData::file_data_register_real_time_monitor(FileData *fd) +{ + gint count; + + ::file_data_ref(fd); + + if (!file_data_monitor_pool) + file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal); + + count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd)); + + DEBUG_1("Register realtime %d %s", count, fd->path); + + count++; + g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count)); + + if (!realtime_monitor_id) + { + realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, nullptr); + } + + return TRUE; +} + +gboolean FileData::file_data_unregister_real_time_monitor(FileData *fd) +{ + gint count; + + g_assert(file_data_monitor_pool); + + count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd)); + + DEBUG_1("Unregister realtime %d %s", count, fd->path); + + g_assert(count > 0); + + count--; + + if (count == 0) + g_hash_table_remove(file_data_monitor_pool, fd); + else + g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count)); + + ::file_data_unref(fd); + + if (g_hash_table_size(file_data_monitor_pool) == 0) + { + g_source_remove(realtime_monitor_id); + realtime_monitor_id = 0; + return FALSE; + } + + return TRUE; +} + +/* + *----------------------------------------------------------------------------- + * Saving marks list, clearing marks + * Uses file_data_pool + *----------------------------------------------------------------------------- + */ + +static void marks_get_files(gpointer key, gpointer value, gpointer userdata) +{ + auto file_name = static_cast(key); + auto result = static_cast(userdata); + FileData *fd; + + if (isfile(file_name)) + { + fd = static_cast(value); + if (fd && fd->marks > 0) + { + g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks); + } + } +} + +gboolean FileData::marks_list_load(const gchar *path) +{ + FILE *f; + gchar s_buf[1024]; + gchar *pathl; + gchar *file_path; + gchar *marks_value; + + pathl = path_from_utf8(path); + f = fopen(pathl, "r"); + g_free(pathl); + if (!f) return FALSE; + + /* first line must start with Marks comment */ + if (!fgets(s_buf, sizeof(s_buf), f) || + strncmp(s_buf, "#Marks", 6) != 0) + { + fclose(f); + return FALSE; + } + + while (fgets(s_buf, sizeof(s_buf), f)) + { + if (s_buf[0]=='#') continue; + file_path = strtok(s_buf, ","); + marks_value = strtok(nullptr, ","); + if (isfile(file_path)) + { + FileData *fd = file_data_new_no_grouping(file_path); + ::file_data_ref(fd); + gint n = 0; + while (n <= 9) + { + gint mark_no = 1 << n; + if (atoi(marks_value) & mark_no) + { + ::file_data_set_mark(fd, n , 1); + } + n++; + } + } + } + + fclose(f); + return TRUE; +} + +gboolean FileData::marks_list_save(gchar *path, gboolean save) +{ + SecureSaveInfo *ssi; + gchar *pathl; + + pathl = path_from_utf8(path); + ssi = secure_open(pathl); + g_free(pathl); + if (!ssi) + { + log_printf(_("Error: Unable to write marks lists to: %s\n"), path); + return FALSE; + } + + secure_fprintf(ssi, "#Marks lists\n"); + + GString *marks = g_string_new(""); + if (save) + { + g_hash_table_foreach(file_data_pool, marks_get_files, marks); + } + secure_fprintf(ssi, "%s", marks->str); + g_string_free(marks, TRUE); + + secure_fprintf(ssi, "#end\n"); + return (secure_close(ssi) == 0); +} + +static void marks_clear(gpointer key, gpointer value, gpointer) +{ + auto file_name = static_cast(key); + gint mark_no; + gint n; + FileData *fd; + + if (isfile(file_name)) + { + fd = static_cast(value); + if (fd && fd->marks > 0) + { + n = 0; + while (n <= 9) + { + mark_no = 1 << n; + if (fd->marks & mark_no) + { + file_data_set_mark(fd, n , 0); + } + n++; + } + } + } +} + +void FileData::marks_clear_all() +{ + g_hash_table_foreach(file_data_pool, marks_clear, nullptr); +} + +void FileData::file_data_set_page_num(FileData *fd, gint page_num) +{ + if (fd->page_total > 1 && page_num < 0) + { + fd->page_num = fd->page_total - 1; + } + else if (fd->page_total > 1 && page_num <= fd->page_total) + { + fd->page_num = page_num - 1; + } + else + { + fd->page_num = 0; + } + file_data_send_notification(fd, NOTIFY_REREAD); +} + +void FileData::file_data_inc_page_num(FileData *fd) +{ + if (fd->page_total > 0 && fd->page_num < fd->page_total - 1) + { + fd->page_num = fd->page_num + 1; + } + else if (fd->page_total == 0) + { + fd->page_num = fd->page_num + 1; + } + file_data_send_notification(fd, NOTIFY_REREAD); +} + +void FileData::file_data_dec_page_num(FileData *fd) +{ + if (fd->page_num > 0) + { + fd->page_num = fd->page_num - 1; + } + file_data_send_notification(fd, NOTIFY_REREAD); +} + +void FileData::file_data_set_page_total(FileData *fd, gint page_total) +{ + fd->page_total = page_total; +} + +// NOLINTEND(readability-convert-member-functions-to-static) + +/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/filedata/meson.build b/src/filedata/meson.build new file mode 100644 index 000000000..54e7b04da --- /dev/null +++ b/src/filedata/meson.build @@ -0,0 +1,24 @@ +# +# Copyright (C) 2024 The Geeqie Team +# +# Author: Omari Stephens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Build file for filedata sub-components. + +filedata_sources = files('filedata.cc') + +code_sources += filedata_sources diff --git a/src/meson.build b/src/meson.build index e39330e77..011d7eaf3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -300,9 +300,10 @@ if conf_data.get('HAVE_WEBP', 0) == 1 ) endif -project_sources += main_sources +code_sources += main_sources subdir('authors') +subdir('filedata') subdir('icons') subdir('pan-view') subdir('third-party') @@ -313,6 +314,9 @@ if conf_data.get('ENABLE_UNIT_TESTS', 0) == 1 subdir('tests') endif +# We assume that code_sources is only set above. +project_sources += code_sources + gq_marshal = gnome.genmarshal('gq-marshal', prefix : 'gq_marshal', sources : 'gq-marshal.list') project_sources += gq_marshal[1] diff --git a/src/pan-view/meson.build b/src/pan-view/meson.build index 49c5b3e91..f376a98d1 100644 --- a/src/pan-view/meson.build +++ b/src/pan-view/meson.build @@ -30,4 +30,5 @@ pan_view_sources = files('pan-calendar.cc', 'pan-view.h', 'pan-view-search.cc', 'pan-view-search.h') -project_sources += pan_view_sources + +code_sources += pan_view_sources diff --git a/src/tests/meson.build b/src/tests/meson.build index b7a995136..e6145b8d8 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -21,4 +21,4 @@ unit_test_sources = files('pixbuf-util.cc') -project_sources += unit_test_sources +code_sources += unit_test_sources