mirror of
https://github.com/arsenetar/dupeguru.git
synced 2026-03-12 03:31:37 +00:00
Compare commits
10 Commits
4.3.0
...
4448b999ab
| Author | SHA1 | Date | |
|---|---|---|---|
|
4448b999ab
|
|||
| af1ae33598 | |||
| 265d10b261 | |||
|
|
1eee3fd7e4 | ||
|
|
db174d4e63 | ||
|
1f1dfa88dc
|
|||
|
916c5204cf
|
|||
|
71af825b37
|
|||
|
97f490b8b7
|
|||
|
d369bcddd7
|
@@ -1,2 +1,2 @@
|
|||||||
__version__ = "4.3.0"
|
__version__ = "4.3.1"
|
||||||
__appname__ = "dupeGuru"
|
__appname__ = "dupeGuru"
|
||||||
|
|||||||
@@ -303,12 +303,13 @@ def getmatches_by_contents(files, bigsize=0, j=job.nulljob):
|
|||||||
# skip hashing for zero length files
|
# skip hashing for zero length files
|
||||||
result.append(Match(first, second, 100))
|
result.append(Match(first, second, 100))
|
||||||
continue
|
continue
|
||||||
if first.digest_partial == second.digest_partial:
|
# if digests are the same (and not None) then files match
|
||||||
|
if first.digest_partial == second.digest_partial and first.digest_partial is not None:
|
||||||
if bigsize > 0 and first.size > bigsize:
|
if bigsize > 0 and first.size > bigsize:
|
||||||
if first.digest_samples == second.digest_samples:
|
if first.digest_samples == second.digest_samples and first.digest_samples is not None:
|
||||||
result.append(Match(first, second, 100))
|
result.append(Match(first, second, 100))
|
||||||
else:
|
else:
|
||||||
if first.digest == second.digest:
|
if first.digest == second.digest and first.digest is not None:
|
||||||
result.append(Match(first, second, 100))
|
result.append(Match(first, second, 100))
|
||||||
group_count += 1
|
group_count += 1
|
||||||
j.add_progress(desc=PROGRESS_MESSAGE % (len(result), group_count))
|
j.add_progress(desc=PROGRESS_MESSAGE % (len(result), group_count))
|
||||||
|
|||||||
63
core/fs.py
63
core/fs.py
@@ -144,13 +144,17 @@ class FilesDB:
|
|||||||
stat = path.stat()
|
stat = path.stat()
|
||||||
size = stat.st_size
|
size = stat.st_size
|
||||||
mtime_ns = stat.st_mtime_ns
|
mtime_ns = stat.st_mtime_ns
|
||||||
|
try:
|
||||||
|
with self.lock:
|
||||||
|
self.cur.execute(
|
||||||
|
self.select_query.format(key=key), {"path": str(path), "size": size, "mtime_ns": mtime_ns}
|
||||||
|
)
|
||||||
|
result = self.cur.fetchone()
|
||||||
|
|
||||||
with self.lock:
|
if result:
|
||||||
self.cur.execute(self.select_query.format(key=key), {"path": str(path), "size": size, "mtime_ns": mtime_ns})
|
return result[0]
|
||||||
result = self.cur.fetchone()
|
except Exception as ex:
|
||||||
|
logging.warning(f"Couldn't get {key} for {path} w/{size}, {mtime_ns}: {ex}")
|
||||||
if result:
|
|
||||||
return result[0]
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -158,12 +162,14 @@ class FilesDB:
|
|||||||
stat = path.stat()
|
stat = path.stat()
|
||||||
size = stat.st_size
|
size = stat.st_size
|
||||||
mtime_ns = stat.st_mtime_ns
|
mtime_ns = stat.st_mtime_ns
|
||||||
|
try:
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.cur.execute(
|
self.cur.execute(
|
||||||
self.insert_query.format(key=key),
|
self.insert_query.format(key=key),
|
||||||
{"path": str(path), "size": size, "mtime_ns": mtime_ns, "value": value},
|
{"path": str(path), "size": size, "mtime_ns": mtime_ns, "value": value},
|
||||||
)
|
)
|
||||||
|
except Exception as ex:
|
||||||
|
logging.warning(f"Couldn't put {key} for {path} w/{size}, {mtime_ns}: {ex}")
|
||||||
|
|
||||||
def commit(self) -> None:
|
def commit(self) -> None:
|
||||||
with self.lock:
|
with self.lock:
|
||||||
@@ -265,34 +271,25 @@ class File:
|
|||||||
self.size = nonone(stats.st_size, 0)
|
self.size = nonone(stats.st_size, 0)
|
||||||
self.mtime = nonone(stats.st_mtime, 0)
|
self.mtime = nonone(stats.st_mtime, 0)
|
||||||
elif field == "digest_partial":
|
elif field == "digest_partial":
|
||||||
try:
|
self.digest_partial = filesdb.get(self.path, "digest_partial")
|
||||||
self.digest_partial = filesdb.get(self.path, "digest_partial")
|
if self.digest_partial is None:
|
||||||
if self.digest_partial is None:
|
self.digest_partial = self._calc_digest_partial()
|
||||||
self.digest_partial = self._calc_digest_partial()
|
filesdb.put(self.path, "digest_partial", self.digest_partial)
|
||||||
filesdb.put(self.path, "digest_partial", self.digest_partial)
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning("Couldn't get digest_partial for %s: %s", self.path, e)
|
|
||||||
elif field == "digest":
|
elif field == "digest":
|
||||||
try:
|
self.digest = filesdb.get(self.path, "digest")
|
||||||
self.digest = filesdb.get(self.path, "digest")
|
if self.digest is None:
|
||||||
if self.digest is None:
|
self.digest = self._calc_digest()
|
||||||
self.digest = self._calc_digest()
|
filesdb.put(self.path, "digest", self.digest)
|
||||||
filesdb.put(self.path, "digest", self.digest)
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning("Couldn't get digest for %s: %s", self.path, e)
|
|
||||||
elif field == "digest_samples":
|
elif field == "digest_samples":
|
||||||
size = self.size
|
size = self.size
|
||||||
# Might as well hash such small files entirely.
|
# Might as well hash such small files entirely.
|
||||||
if size <= MIN_FILE_SIZE:
|
if size <= MIN_FILE_SIZE:
|
||||||
setattr(self, field, self.digest)
|
setattr(self, field, self.digest)
|
||||||
return
|
return
|
||||||
try:
|
self.digest_samples = filesdb.get(self.path, "digest_samples")
|
||||||
self.digest_samples = filesdb.get(self.path, "digest_samples")
|
if self.digest_samples is None:
|
||||||
if self.digest_samples is None:
|
self.digest_samples = self._calc_digest_samples()
|
||||||
self.digest_samples = self._calc_digest_samples()
|
filesdb.put(self.path, "digest_samples", self.digest_samples)
|
||||||
filesdb.put(self.path, "digest_samples", self.digest_samples)
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning(f"Couldn't get digest_samples for {self.path}: {e}")
|
|
||||||
|
|
||||||
def _read_all_info(self, attrnames=None):
|
def _read_all_info(self, attrnames=None):
|
||||||
"""Cache all possible info.
|
"""Cache all possible info.
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
=== 4.3.1 (2022-07-08)
|
||||||
|
* Fix issue where cache db exceptions could prevent files being hashed (#1015)
|
||||||
|
* Add extra guard for non-zero length files without digests to prevent false duplicates
|
||||||
|
* Update Italian translations
|
||||||
|
|
||||||
=== 4.3.0 (2022-07-01)
|
=== 4.3.0 (2022-07-01)
|
||||||
* Redirect stdout from custom command to the log files (#1008)
|
* Redirect stdout from custom command to the log files (#1008)
|
||||||
* Update translations
|
* Update translations
|
||||||
|
|||||||
@@ -10,110 +10,110 @@ msgstr ""
|
|||||||
#: core\gui\ignore_list_table.py:19 core\gui\ignore_list_table.py:20
|
#: core\gui\ignore_list_table.py:19 core\gui\ignore_list_table.py:20
|
||||||
#: core\gui\problem_table.py:18
|
#: core\gui\problem_table.py:18
|
||||||
msgid "File Path"
|
msgid "File Path"
|
||||||
msgstr ""
|
msgstr "مسار الملف"
|
||||||
|
|
||||||
#: core\gui\problem_table.py:19
|
#: core\gui\problem_table.py:19
|
||||||
msgid "Error Message"
|
msgid "Error Message"
|
||||||
msgstr ""
|
msgstr "رسالة خطأ"
|
||||||
|
|
||||||
#: core\me\prioritize.py:23
|
#: core\me\prioritize.py:23
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr ""
|
msgstr "مدة"
|
||||||
|
|
||||||
#: core\me\prioritize.py:30 core\me\result_table.py:23
|
#: core\me\prioritize.py:30 core\me\result_table.py:23
|
||||||
msgid "Bitrate"
|
msgid "Bitrate"
|
||||||
msgstr ""
|
msgstr "معدل البت"
|
||||||
|
|
||||||
#: core\me\prioritize.py:37
|
#: core\me\prioritize.py:37
|
||||||
msgid "Samplerate"
|
msgid "Samplerate"
|
||||||
msgstr ""
|
msgstr "معدل العينة"
|
||||||
|
|
||||||
#: core\me\result_table.py:19 core\pe\result_table.py:19 core\prioritize.py:92
|
#: core\me\result_table.py:19 core\pe\result_table.py:19 core\prioritize.py:92
|
||||||
#: core\se\result_table.py:19
|
#: core\se\result_table.py:19
|
||||||
msgid "Filename"
|
msgid "Filename"
|
||||||
msgstr ""
|
msgstr "اسم الملف"
|
||||||
|
|
||||||
#: core\me\result_table.py:20 core\pe\result_table.py:20 core\prioritize.py:75
|
#: core\me\result_table.py:20 core\pe\result_table.py:20 core\prioritize.py:75
|
||||||
#: core\se\result_table.py:20
|
#: core\se\result_table.py:20
|
||||||
msgid "Folder"
|
msgid "Folder"
|
||||||
msgstr ""
|
msgstr "مجلد"
|
||||||
|
|
||||||
#: core\me\result_table.py:21
|
#: core\me\result_table.py:21
|
||||||
msgid "Size (MB)"
|
msgid "Size (MB)"
|
||||||
msgstr ""
|
msgstr "الحجم (ميغا بايت)"
|
||||||
|
|
||||||
#: core\me\result_table.py:22
|
#: core\me\result_table.py:22
|
||||||
msgid "Time"
|
msgid "Time"
|
||||||
msgstr ""
|
msgstr "زمن"
|
||||||
|
|
||||||
#: core\me\result_table.py:24
|
#: core\me\result_table.py:24
|
||||||
msgid "Sample Rate"
|
msgid "Sample Rate"
|
||||||
msgstr ""
|
msgstr "معدل العينة"
|
||||||
|
|
||||||
#: core\me\result_table.py:25 core\pe\result_table.py:22 core\prioritize.py:65
|
#: core\me\result_table.py:25 core\pe\result_table.py:22 core\prioritize.py:65
|
||||||
#: core\se\result_table.py:22
|
#: core\se\result_table.py:22
|
||||||
msgid "Kind"
|
msgid "Kind"
|
||||||
msgstr ""
|
msgstr "طيب القلب"
|
||||||
|
|
||||||
#: core\me\result_table.py:26 core\pe\result_table.py:25
|
#: core\me\result_table.py:26 core\pe\result_table.py:25
|
||||||
#: core\prioritize.py:163 core\se\result_table.py:23
|
#: core\prioritize.py:163 core\se\result_table.py:23
|
||||||
msgid "Modification"
|
msgid "Modification"
|
||||||
msgstr ""
|
msgstr "تعديل"
|
||||||
|
|
||||||
#: core\me\result_table.py:27
|
#: core\me\result_table.py:27
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr ""
|
msgstr "عنوان"
|
||||||
|
|
||||||
#: core\me\result_table.py:28
|
#: core\me\result_table.py:28
|
||||||
msgid "Artist"
|
msgid "Artist"
|
||||||
msgstr ""
|
msgstr "فنان"
|
||||||
|
|
||||||
#: core\me\result_table.py:29
|
#: core\me\result_table.py:29
|
||||||
msgid "Album"
|
msgid "Album"
|
||||||
msgstr ""
|
msgstr "البوم"
|
||||||
|
|
||||||
#: core\me\result_table.py:30
|
#: core\me\result_table.py:30
|
||||||
msgid "Genre"
|
msgid "Genre"
|
||||||
msgstr ""
|
msgstr "النوع"
|
||||||
|
|
||||||
#: core\me\result_table.py:31
|
#: core\me\result_table.py:31
|
||||||
msgid "Year"
|
msgid "Year"
|
||||||
msgstr ""
|
msgstr "سنة"
|
||||||
|
|
||||||
#: core\me\result_table.py:32
|
#: core\me\result_table.py:32
|
||||||
msgid "Track Number"
|
msgid "Track Number"
|
||||||
msgstr ""
|
msgstr "رقم الشاحنة"
|
||||||
|
|
||||||
#: core\me\result_table.py:33
|
#: core\me\result_table.py:33
|
||||||
msgid "Comment"
|
msgid "Comment"
|
||||||
msgstr ""
|
msgstr "تعليق"
|
||||||
|
|
||||||
#: core\me\result_table.py:34 core\pe\result_table.py:26
|
#: core\me\result_table.py:34 core\pe\result_table.py:26
|
||||||
#: core\se\result_table.py:24
|
#: core\se\result_table.py:24
|
||||||
msgid "Match %"
|
msgid "Match %"
|
||||||
msgstr ""
|
msgstr "مباراة ٪"
|
||||||
|
|
||||||
#: core\me\result_table.py:35 core\se\result_table.py:25
|
#: core\me\result_table.py:35 core\se\result_table.py:25
|
||||||
msgid "Words Used"
|
msgid "Words Used"
|
||||||
msgstr ""
|
msgstr "الكلمات المستخدمة"
|
||||||
|
|
||||||
#: core\me\result_table.py:36 core\pe\result_table.py:27
|
#: core\me\result_table.py:36 core\pe\result_table.py:27
|
||||||
#: core\se\result_table.py:26
|
#: core\se\result_table.py:26
|
||||||
msgid "Dupe Count"
|
msgid "Dupe Count"
|
||||||
msgstr ""
|
msgstr "عدد المخادعين"
|
||||||
|
|
||||||
#: core\pe\prioritize.py:23 core\pe\result_table.py:23
|
#: core\pe\prioritize.py:23 core\pe\result_table.py:23
|
||||||
msgid "Dimensions"
|
msgid "Dimensions"
|
||||||
msgstr ""
|
msgstr "أبعاد"
|
||||||
|
|
||||||
#: core\pe\result_table.py:21 core\se\result_table.py:21
|
#: core\pe\result_table.py:21 core\se\result_table.py:21
|
||||||
msgid "Size (KB)"
|
msgid "Size (KB)"
|
||||||
msgstr ""
|
msgstr "الحجم (كيلو بايت)"
|
||||||
|
|
||||||
#: core\pe\result_table.py:24
|
#: core\pe\result_table.py:24
|
||||||
msgid "EXIF Timestamp"
|
msgid "EXIF Timestamp"
|
||||||
msgstr ""
|
msgstr "الطابع الزمني EXIF"
|
||||||
|
|
||||||
#: core\prioritize.py:156
|
#: core\prioritize.py:156
|
||||||
msgid "Size"
|
msgid "Size"
|
||||||
msgstr ""
|
msgstr "بحجم"
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ msgstr "Raccolte {} cartelle da scansionare"
|
|||||||
|
|
||||||
#: core\engine.py:27
|
#: core\engine.py:27
|
||||||
msgid "%d matches found from %d groups"
|
msgid "%d matches found from %d groups"
|
||||||
msgstr "%d corrispondeze trovate da %d gruppi"
|
msgstr "%d corrispondenze trovate da %d gruppi"
|
||||||
|
|
||||||
#: core\gui\deletion_options.py:71
|
#: core\gui\deletion_options.py:71
|
||||||
msgid "You are sending {} file(s) to the Trash."
|
msgid "You are sending {} file(s) to the Trash."
|
||||||
|
|||||||
@@ -2,15 +2,16 @@
|
|||||||
# Andrew Senetar <arsenetar@gmail.com>, 2022
|
# Andrew Senetar <arsenetar@gmail.com>, 2022
|
||||||
# Emanuele, 2022
|
# Emanuele, 2022
|
||||||
# Fuan <jcfrt@posteo.net>, 2022
|
# Fuan <jcfrt@posteo.net>, 2022
|
||||||
|
# Giovanni, 2022
|
||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Last-Translator: Fuan <jcfrt@posteo.net>, 2022\n"
|
"Last-Translator: Giovanni, 2022\n"
|
||||||
"Language-Team: Italian (https://www.transifex.com/voltaicideas/teams/116153/it/)\n"
|
"Language-Team: Italian (https://www.transifex.com/voltaicideas/teams/116153/it/)\n"
|
||||||
"Language: it\n"
|
"Language: it\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: utf-8\n"
|
"Content-Transfer-Encoding: utf-8\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||||
|
|
||||||
#: qt/app.py:81
|
#: qt/app.py:81
|
||||||
msgid "Quit"
|
msgid "Quit"
|
||||||
@@ -979,37 +980,40 @@ msgstr "Ignora file più grandi di"
|
|||||||
|
|
||||||
#: qt\app.py:135 qt\app.py:293
|
#: qt\app.py:135 qt\app.py:293
|
||||||
msgid "Clear Cache"
|
msgid "Clear Cache"
|
||||||
msgstr ""
|
msgstr "Svuota cache"
|
||||||
|
|
||||||
#: qt\app.py:294
|
#: qt\app.py:294
|
||||||
msgid ""
|
msgid ""
|
||||||
"Do you really want to clear the cache? This will remove all cached file "
|
"Do you really want to clear the cache? This will remove all cached file "
|
||||||
"hashes and picture analysis."
|
"hashes and picture analysis."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Vuoi davvero svuotare la cache? Ciò rimuoverà tutti gli hash dei file "
|
||||||
|
"memorizzati nella cache e le analisi delle immagini."
|
||||||
|
|
||||||
#: qt\app.py:299
|
#: qt\app.py:299
|
||||||
msgid "Cache cleared."
|
msgid "Cache cleared."
|
||||||
msgstr ""
|
msgstr "Cache svuotata"
|
||||||
|
|
||||||
#: qt\preferences_dialog.py:173
|
#: qt\preferences_dialog.py:173
|
||||||
msgid "Use dark style"
|
msgid "Use dark style"
|
||||||
msgstr ""
|
msgstr "Usa stile scuro"
|
||||||
|
|
||||||
#: qt\preferences_dialog.py:241
|
#: qt\preferences_dialog.py:241
|
||||||
msgid "Profile scan operation"
|
msgid "Profile scan operation"
|
||||||
msgstr ""
|
msgstr "Profila l'operazione di scansione"
|
||||||
|
|
||||||
#: qt\preferences_dialog.py:242
|
#: qt\preferences_dialog.py:242
|
||||||
msgid "Profile the scan operation and save logs for optimization."
|
msgid "Profile the scan operation and save logs for optimization."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Profila l'operazione di scansione e salva i registri per l'ottimizzazione."
|
||||||
|
|
||||||
#: qt\preferences_dialog.py:246
|
#: qt\preferences_dialog.py:246
|
||||||
msgid "Logs located in: <a href=\"{}\">{}</a>"
|
msgid "Logs located in: <a href=\"{}\">{}</a>"
|
||||||
msgstr ""
|
msgstr "I log si trovano in: <a href=\"{}\">{}</a>"
|
||||||
|
|
||||||
#: qt\preferences_dialog.py:291
|
#: qt\preferences_dialog.py:291
|
||||||
msgid "Debug"
|
msgid "Debug"
|
||||||
msgstr ""
|
msgstr "Debug"
|
||||||
|
|
||||||
#: qt\about_box.py:31
|
#: qt\about_box.py:31
|
||||||
msgid "About {}"
|
msgid "About {}"
|
||||||
@@ -1021,7 +1025,7 @@ msgstr "Versione {}"
|
|||||||
|
|
||||||
#: qt\about_box.py:49 qt\about_box.py:75
|
#: qt\about_box.py:49 qt\about_box.py:75
|
||||||
msgid "Checking for updates..."
|
msgid "Checking for updates..."
|
||||||
msgstr ""
|
msgstr "Controllo degli aggiornamenti..."
|
||||||
|
|
||||||
#: qt\about_box.py:54
|
#: qt\about_box.py:54
|
||||||
msgid "Licensed under GPLv3"
|
msgid "Licensed under GPLv3"
|
||||||
@@ -1029,11 +1033,11 @@ msgstr "Distribuito sotto licenza GPLv3"
|
|||||||
|
|
||||||
#: qt\about_box.py:68
|
#: qt\about_box.py:68
|
||||||
msgid "No update available."
|
msgid "No update available."
|
||||||
msgstr ""
|
msgstr "Nessun aggiornamento disponibile."
|
||||||
|
|
||||||
#: qt\about_box.py:71
|
#: qt\about_box.py:71
|
||||||
msgid "New version {} available, download <a href=\"{}\">here</a>."
|
msgid "New version {} available, download <a href=\"{}\">here</a>."
|
||||||
msgstr ""
|
msgstr "È disponibile la nuova versione {}, scaricabile <a href=\"{}\">qui</a>."
|
||||||
|
|
||||||
#: qt\error_report_dialog.py:50
|
#: qt\error_report_dialog.py:50
|
||||||
msgid "Error Report"
|
msgid "Error Report"
|
||||||
|
|||||||
2
tox.ini
2
tox.ini
@@ -19,4 +19,4 @@ deps =
|
|||||||
exclude = .tox,env,build,cocoalib,cocoa,help,./qt/dg_rc.py,cocoa/run_template.py,./pkg
|
exclude = .tox,env,build,cocoalib,cocoa,help,./qt/dg_rc.py,cocoa/run_template.py,./pkg
|
||||||
max-line-length = 120
|
max-line-length = 120
|
||||||
select = C,E,F,W,B,B950
|
select = C,E,F,W,B,B950
|
||||||
extend-ignore = E203, E501
|
extend-ignore = E203, E501, W503
|
||||||
|
|||||||
Reference in New Issue
Block a user