diff --git a/gramps/gen/db/upgrade.py b/gramps/gen/db/upgrade.py index 0a3322883..9ce9f2021 100644 --- a/gramps/gen/db/upgrade.py +++ b/gramps/gen/db/upgrade.py @@ -24,8 +24,10 @@ from __future__ import with_statement, unicode_literals import sys +import os from ..lib.markertype import MarkerType from ..lib.tag import Tag +from ..utils.file import create_checksum import time import logging LOG = logging.getLogger(".citation") @@ -54,10 +56,11 @@ def gramps_upgrade_17(self): 1. This upgrade adds tags to event, place, repository, source and citation objects. 2. Data of Source becomes SourceAttributes Secondary Object + 3. Add checksum field to media objects. """ length = (len(self.event_map) + len(self.place_map) + len(self.repository_map) + len(self.source_map) + - len(self.citation_map)) + len(self.citation_map) + len(self.media_map)) self.set_total(length) # --------------------------------- @@ -166,6 +169,27 @@ def gramps_upgrade_17(self): txn.put(handle, new_citation) self.update() + # --------------------------------- + # Modify Media + # --------------------------------- + # Add new checksum field. + base_path = self.metadata[b'mediapath'] + for handle in self.media_map.keys(): + media = self.media_map[handle] + new_media = list(media) + if os.path.isabs(new_media[2]): + full_path = new_media[2] + else: + full_path = os.path.join(base_path, new_media[2]) + checksum = create_checksum(full_path) + new_media = new_media[:5] + [checksum] + new_media[5:] + new_media = tuple(new_media) + with BSDDBTxn(self.env, self.media_map) as txn: + if isinstance(handle, UNITYPE): + handle = handle.encode('utf-8') + txn.put(handle, new_media) + self.update() + # Bump up database version. Separate transaction to save metadata. with BSDDBTxn(self.env, self.metadata) as txn: txn.put(b'version', 17) diff --git a/gramps/gen/lib/mediaobj.py b/gramps/gen/lib/mediaobj.py index 5ea10c152..f369ba247 100644 --- a/gramps/gen/lib/mediaobj.py +++ b/gramps/gen/lib/mediaobj.py @@ -86,11 +86,13 @@ class MediaObject(CitationBase, NoteBase, DateBase, AttributeBase, self.path = source.path self.mime = source.mime self.desc = source.desc + self.checksum = source.checksum self.thumb = source.thumb else: self.path = "" self.mime = "" self.desc = "" + self.checksum = "" self.thumb = None def serialize(self, no_text_date = False): @@ -112,6 +114,7 @@ class MediaObject(CitationBase, NoteBase, DateBase, AttributeBase, :rtype: tuple """ return (self.handle, self.gramps_id, self.path, self.mime, self.desc, + self.checksum, AttributeBase.serialize(self), CitationBase.serialize(self), NoteBase.serialize(self), @@ -145,6 +148,7 @@ class MediaObject(CitationBase, NoteBase, DateBase, AttributeBase, "path": self.path, "mime": self.mime, "desc": self.desc, + "checksum": self.checksum, "attribute_list": AttributeBase.to_struct(self), "citation_list": CitationBase.to_struct(self), "note_list": NoteBase.to_struct(self), @@ -162,7 +166,7 @@ class MediaObject(CitationBase, NoteBase, DateBase, AttributeBase, :type data: tuple """ (self.handle, self.gramps_id, self.path, self.mime, self.desc, - attribute_list, citation_list, note_list, self.change, + self.checksum, attribute_list, citation_list, note_list, self.change, date, tag_list, self.private) = data AttributeBase.unserialize(self, attribute_list) @@ -287,3 +291,12 @@ class MediaObject(CitationBase, NoteBase, DateBase, AttributeBase, def get_description(self): """Return the description of the image.""" return self.desc + + def set_checksum(self, text): + """Set the checksum of the image.""" + self.checksum = text + + def get_checksum(self): + """Return the checksum of the image.""" + return self.checksum + diff --git a/gramps/gen/utils/file.py b/gramps/gen/utils/file.py index 817405033..adc3f25a4 100644 --- a/gramps/gen/utils/file.py +++ b/gramps/gen/utils/file.py @@ -34,6 +34,8 @@ File and folder related utility functions import os import sys import shutil +import io +import hashlib import logging LOG = logging.getLogger(".gen.utils.file") @@ -294,3 +296,15 @@ def fix_encoding(value, errors='strict'): return value.decode(encoding=codeset, errors=errors) else: return value + +def create_checksum(full_path): + """ + Create a md5 hash for the given file. + """ + full_path = os.path.normpath(full_path) + try: + with io.open(full_path, 'rb') as media_file: + md5sum = hashlib.md5(media_file.read()).hexdigest() + except IOError: + md5sum = '' + return md5sum diff --git a/gramps/gui/editors/displaytabs/gallerytab.py b/gramps/gui/editors/displaytabs/gallerytab.py index 522347312..992314af9 100644 --- a/gramps/gui/editors/displaytabs/gallerytab.py +++ b/gramps/gui/editors/displaytabs/gallerytab.py @@ -64,7 +64,7 @@ from gramps.gen.constfunc import cuni from gramps.gen.lib import MediaObject, MediaRef from gramps.gen.db import DbTxn from gramps.gen.utils.file import (media_path_full, media_path, relative_path, - fix_encoding) + fix_encoding, create_checksum) from ...thumbnails import get_thumbnail_image from gramps.gen.errors import WindowActiveError from gramps.gen.mime import get_type, is_valid_type @@ -525,6 +525,9 @@ class GalleryTab(ButtonTab, DbGUIElement): if not is_valid_type(mime): return photo = MediaObject() + self.uistate.set_busy_cursor(True) + photo.set_checksum(create_checksum(name)) + self.uistate.set_busy_cursor(False) base_dir = cuni(media_path(self.dbstate.db)) if os.path.exists(base_dir): name = relative_path(name, base_dir) diff --git a/gramps/gui/editors/editmedia.py b/gramps/gui/editors/editmedia.py index 5015b1c6f..b08daf4d3 100644 --- a/gramps/gui/editors/editmedia.py +++ b/gramps/gui/editors/editmedia.py @@ -49,8 +49,8 @@ from gramps.gen.lib import MediaObject, NoteType from gramps.gen.db import DbTxn from gramps.gen.mime import get_description, get_type from ..thumbnails import get_thumbnail_image, find_mime_type_pixbuf -from gramps.gen.utils.file import (media_path_full, find_file, - get_unicode_path_from_file_chooser) +from gramps.gen.utils.file import (media_path_full, find_file, create_checksum, + get_unicode_path_from_file_chooser) from .editprimary import EditPrimary from ..widgets import (MonitoredDate, MonitoredEntry, PrivacyButton, MonitoredTagList) @@ -263,8 +263,15 @@ class EditMedia(EditPrimary): fname = self.obj.get_path() self.file_path.set_text(fname) self.determine_mime() + self.update_checksum() self.draw_preview() + def update_checksum(self): + self.uistate.set_busy_cursor(True) + media_path = media_path_full(self.dbstate.db, self.obj.get_path()) + self.obj.set_checksum(create_checksum(os.path.normpath(media_path))) + self.uistate.set_busy_cursor(False) + def save(self, *obj): self.ok_button.set_sensitive(False) diff --git a/gramps/gui/editors/editmediaref.py b/gramps/gui/editors/editmediaref.py index 47713cbc5..b205d90ec 100644 --- a/gramps/gui/editors/editmediaref.py +++ b/gramps/gui/editors/editmediaref.py @@ -49,8 +49,8 @@ from ..utils import open_file_with_default_application from gramps.gen.const import THUMBSCALE from gramps.gen.mime import get_description, get_type from ..thumbnails import get_thumbnail_image, find_mime_type_pixbuf -from gramps.gen.utils.file import (media_path_full, find_file, - get_unicode_path_from_file_chooser) +from gramps.gen.utils.file import (media_path_full, find_file, create_checksum, + get_unicode_path_from_file_chooser) from gramps.gen.lib import NoteType from gramps.gen.db import DbTxn from ..glade import Glade @@ -553,7 +553,14 @@ class EditMediaRef(EditReference): for obj in (self.descr_window, self.path_obj): obj.update() self.determine_mime() + self.update_checksum() self.draw_preview() + + def update_checksum(self): + self.uistate.set_busy_cursor(True) + media_path = media_path_full(self.dbstate.db, self.source.get_path()) + self.source.set_checksum(create_checksum(os.path.normpath(media_path))) + self.uistate.set_busy_cursor(False) def select_file(self, val): self.determine_mime() diff --git a/gramps/gui/views/treemodels/mediamodel.py b/gramps/gui/views/treemodels/mediamodel.py index 1d4e43314..150b6e79d 100644 --- a/gramps/gui/views/treemodels/mediamodel.py +++ b/gramps/gui/views/treemodels/mediamodel.py @@ -137,9 +137,9 @@ class MediaModel(FlatBaseModel): return cuni(data[1]) def column_date(self,data): - if data[9]: + if data[10]: date = Date() - date.unserialize(data[9]) + date.unserialize(data[10]) return cuni(displayer.display(date)) return '' @@ -156,17 +156,17 @@ class MediaModel(FlatBaseModel): return cuni(data[0]) def column_private(self, data): - if data[11]: + if data[12]: return 'gramps-lock' else: # There is a problem returning None here. return '' def sort_change(self,data): - return "%012x" % data[8] + return "%012x" % data[9] def column_change(self,data): - return format_time(data[8]) + return format_time(data[9]) def column_tooltip(self,data): return cuni('Media tooltip') @@ -183,7 +183,7 @@ class MediaModel(FlatBaseModel): """ tag_color = "#000000000000" tag_priority = None - for handle in data[10]: + for handle in data[11]: tag = self.db.get_tag_from_handle(handle) this_priority = tag.get_priority() if tag_priority is None or this_priority < tag_priority: @@ -195,5 +195,5 @@ class MediaModel(FlatBaseModel): """ Return the sorted list of tags. """ - tag_list = list(map(self.get_tag_name, data[10])) + tag_list = list(map(self.get_tag_name, data[11])) return ', '.join(sorted(tag_list, key=glocale.sort_key)) diff --git a/gramps/plugins/export/exportxml.py b/gramps/plugins/export/exportxml.py index 1da7bece2..b260c57e3 100644 --- a/gramps/plugins/export/exportxml.py +++ b/gramps/plugins/export/exportxml.py @@ -1216,6 +1216,7 @@ class GrampsXmlWriter(UpdateCallback): mime_type = obj.get_mime_type() path = obj.get_path() desc = obj.get_description() + checksum = obj.get_checksum() if desc: desc_text = ' description="%s"' % self.fix(desc) else: @@ -1229,9 +1230,9 @@ class GrampsXmlWriter(UpdateCallback): # Always export path with \ replaced with /. Otherwise import # from Windows to Linux of gpkg's path to images does not work. path = path.replace('\\','/') - self.g.write('%s\n' + self.g.write('%s\n' % (" "*(index+1), self.fix(path), self.fix(mime_type), - desc_text)) + checksum, desc_text)) self.write_attribute_list(obj.get_attribute_list()) self.write_note_list(obj.get_note_list(), index+1) dval = obj.get_date_object() diff --git a/gramps/plugins/importer/importxml.py b/gramps/plugins/importer/importxml.py index 14aca01a3..64665b8bb 100644 --- a/gramps/plugins/importer/importxml.py +++ b/gramps/plugins/importer/importxml.py @@ -62,6 +62,7 @@ from gramps.gen.errors import GrampsImportError from gramps.gen.utils.id import create_id from gramps.gen.utils.db import family_name from gramps.gen.utils.unknown import make_unknown, create_explanation_note +from gramps.gen.utils.file import create_checksum from gramps.gen.datehandler import parser, set_date from gramps.gen.display.name import displayer as name_displayer from gramps.gen.db.dbconst import (PERSON_KEY, FAMILY_KEY, SOURCE_KEY, @@ -1603,6 +1604,14 @@ class GrampsParser(UpdateCallback): if self.all_abs and not os.path.isabs(src): self.all_abs = False self.info.add('relative-path', None, None) + if 'checksum' in attrs: + self.object.checksum = attrs['checksum'] + else: + if os.path.isabs(src): + full_path = src + else: + full_path = os.path.join(self.mediapath, src) + self.object.checksum = create_checksum(full_path) def start_childof(self, attrs): """ diff --git a/gramps/plugins/view/mediaview.py b/gramps/plugins/view/mediaview.py index 9fbe0a361..220fda2e8 100644 --- a/gramps/plugins/view/mediaview.py +++ b/gramps/plugins/view/mediaview.py @@ -63,7 +63,7 @@ from gramps.gui.views.treemodels import MediaModel from gramps.gen.constfunc import win, cuni from gramps.gen.config import config from gramps.gen.utils.file import (media_path, relative_path, media_path_full, - fix_encoding) + fix_encoding, create_checksum) from gramps.gen.utils.db import get_media_referents from gramps.gui.views.bookmarks import MediaBookmarks from gramps.gen.mime import get_type, is_valid_type @@ -199,6 +199,9 @@ class MediaView(ListView): if not is_valid_type(mime): return photo = MediaObject() + self.uistate.set_busy_cursor(True) + photo.set_checksum(create_checksum(name)) + self.uistate.set_busy_cursor(False) base_dir = cuni(media_path(self.dbstate.db)) if os.path.exists(base_dir): name = relative_path(name, base_dir)