Passed
Push — master ( 9e42ed...b2d36d )
by Jaisen
02:09
created

elodie/localstorage.py (7 issues)

1
"""
2
Methods for interacting with information Elodie caches about stored media.
3
"""
4
from builtins import map
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in map.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
5
from builtins import object
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in object.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
6
7
import hashlib
8
import json
9
import os
10
import sys
11
12
from math import radians, cos, sqrt
13
from shutil import copyfile
14
from time import strftime
15
16
from elodie import constants
17
18
19
class Db(object):
20
21
    """A class for interacting with the JSON files created by Elodie."""
22
23
    def __init__(self):
24
        # verify that the application directory (~/.elodie) exists,
25
        #   else create it
26
        if not os.path.exists(constants.application_directory):
27
            os.makedirs(constants.application_directory)
28
29
        # If the hash db doesn't exist we create it.
30
        # Otherwise we only open for reading
31
        if not os.path.isfile(constants.hash_db):
32
            with open(constants.hash_db, 'a'):
33
                os.utime(constants.hash_db, None)
34
35
        self.hash_db = {}
36
37
        # We know from above that this file exists so we open it
38
        #   for reading only.
39
        with open(constants.hash_db, 'r') as f:
40
            try:
41
                self.hash_db = json.load(f)
42
            except ValueError:
43
                pass
44
45
        # If the location db doesn't exist we create it.
46
        # Otherwise we only open for reading
47
        if not os.path.isfile(constants.location_db):
48
            with open(constants.location_db, 'a'):
49
                os.utime(constants.location_db, None)
50
51
        self.location_db = []
52
53
        # We know from above that this file exists so we open it
54
        #   for reading only.
55
        with open(constants.location_db, 'r') as f:
56
            try:
57
                self.location_db = json.load(f)
58
            except ValueError:
59
                pass
60
61
    def add_hash(self, key, value, write=False):
62
        """Add a hash to the hash db.
63
64
        :param str key:
65
        :param str value:
66
        :param bool write: If true, write the hash db to disk.
67
        """
68
        self.hash_db[key] = value
69
        if(write is True):
0 ignored issues
show
Unused Code Coding Style introduced by
There is an unnecessary parenthesis after if.
Loading history...
70
            self.update_hash_db()
71
72
    # Location database
73
    # Currently quite simple just a list of long/lat pairs with a name
74
    # If it gets many entries a lookup might take too long and a better
75
    # structure might be needed. Some speed up ideas:
76
    # - Sort it and inter-half method can be used
77
    # - Use integer part of long or lat as key to get a lower search list
78
    # - Cache a small number of lookups, photos are likely to be taken in
79
    #   clusters around a spot during import.
80
    def add_location(self, latitude, longitude, place, write=False):
81
        """Add a location to the database.
82
83
        :param float latitude: Latitude of the location.
84
        :param float longitude: Longitude of the location.
85
        :param str place: Name for the location.
86
        :param bool write: If true, write the location db to disk.
87
        """
88
        data = {}
89
        data['lat'] = latitude
90
        data['long'] = longitude
91
        data['name'] = place
92
        self.location_db.append(data)
93
        if(write is True):
0 ignored issues
show
Unused Code Coding Style introduced by
There is an unnecessary parenthesis after if.
Loading history...
94
            self.update_location_db()
95
96
    def backup_hash_db(self):
97
        """Backs up the hash db."""
98
        if os.path.isfile(constants.hash_db):
99
            mask = strftime('%Y-%m-%d_%H-%M-%S')
100
            backup_file_name = '%s-%s' % (constants.hash_db, mask)
101
            copyfile(constants.hash_db, backup_file_name)
102
            return backup_file_name
103
104
    def check_hash(self, key):
105
        """Check whether a hash is present for the given key.
106
107
        :param str key:
108
        :returns: bool
109
        """
110
        return key in self.hash_db
111
112 View Code Duplication
    def checksum(self, file_path, blocksize=65536):
113
        """Create a hash value for the given file.
114
115
        See http://stackoverflow.com/a/3431835/1318758.
116
117
        :param str file_path: Path to the file to create a hash for.
118
        :param int blocksize: Read blocks of this size from the file when
119
            creating the hash.
120
        :returns: str or None
121
        """
122
        hasher = hashlib.sha256()
123
        with open(file_path, 'rb') as f:
124
            buf = f.read(blocksize)
125
126
            while len(buf) > 0:
127
                hasher.update(buf)
128
                buf = f.read(blocksize)
129
            return hasher.hexdigest()
130
        return None
131
132
    def get_hash(self, key):
133
        """Get the hash value for a given key.
134
135
        :param str key:
136
        :returns: str or None
137
        """
138
        if(self.check_hash(key) is True):
0 ignored issues
show
Unused Code Coding Style introduced by
There is an unnecessary parenthesis after if.
Loading history...
139
            return self.hash_db[key]
140
        return None
141
142
    def get_location_name(self, latitude, longitude, threshold_m):
143
        """Find a name for a location in the database.
144
145
        :param float latitude: Latitude of the location.
146
        :param float longitude: Longitude of the location.
147
        :param int threshold_m: Location in the database must be this close to
148
            the given latitude and longitude.
149
        :returns: str, or None if a matching location couldn't be found.
150
        """
151
        last_d = sys.maxsize
152
        name = None
153
        for data in self.location_db:
154
            # As threshold is quite small use simple math
155
            # From http://stackoverflow.com/questions/15736995/how-can-i-quickly-estimate-the-distance-between-two-latitude-longitude-points  # noqa
0 ignored issues
show
This line is too long as per the coding-style (148/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
156
            # convert decimal degrees to radians
157
158
            lon1, lat1, lon2, lat2 = list(map(
159
                radians,
160
                [longitude, latitude, data['long'], data['lat']]
161
            ))
162
163
            r = 6371000  # radius of the earth in m
164
            x = (lon2 - lon1) * cos(0.5 * (lat2 + lat1))
165
            y = lat2 - lat1
166
            d = r * sqrt(x * x + y * y)
167
            # Use if closer then threshold_km reuse lookup
168
            if(d <= threshold_m and d < last_d):
0 ignored issues
show
Unused Code Coding Style introduced by
There is an unnecessary parenthesis after if.
Loading history...
169
                name = data['name']
170
            last_d = d
171
172
        return name
173
174
    def get_location_coordinates(self, name):
175
        """Get the latitude and longitude for a location.
176
177
        :param str name: Name of the location.
178
        :returns: tuple(float), or None if the location wasn't in the database.
179
        """
180
        for data in self.location_db:
181
            if data['name'] == name:
182
                return (data['lat'], data['long'])
183
184
        return None
185
186
    def all(self):
187
        """Generator to get all entries from self.hash_db
188
189
        :returns tuple(string)
190
        """
191
        for checksum, path in self.hash_db.items():
192
            yield (checksum, path)
193
194
    def reset_hash_db(self):
195
        self.hash_db = {}
196
197
    def update_hash_db(self):
198
        """Write the hash db to disk."""
199
        with open(constants.hash_db, 'w') as f:
200
            json.dump(self.hash_db, f)
201
202
    def update_location_db(self):
203
        """Write the location db to disk."""
204
        with open(constants.location_db, 'w') as f:
205
            json.dump(self.location_db, f)
206