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)