elodie.media.text   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 204
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 126
dl 0
loc 204
rs 8.96
c 0
b 0
f 0
wmc 43

14 Methods

Rating   Name   Duplication   Size   Complexity  
A Text.get_coordinate() 0 11 5
A Text.reset_cache() 0 5 1
A Text.__init__() 0 3 1
A Text.set_location() 0 4 1
A Text.set_date_taken() 0 8 2
A Text.set_original_name() 0 20 4
A Text.get_metadata() 0 3 1
A Text.set_album() 0 4 1
A Text.get_album() 0 7 3
A Text.get_date_taken() 0 16 3
A Text.get_title() 0 8 3
B Text.parse_metadata_line() 0 18 6
A Text.get_original_name() 0 9 3
C Text.write_metadata() 0 45 9

How to fix   Complexity   

Complexity

Complex classes like elodie.media.text often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""
2
The text module provides a base :class:`Text` class for text files that
3
are tracked by Elodie.
4
5
.. moduleauthor:: Jaisen Mathai <[email protected]>
6
"""
7
8
from json import dumps, loads
9
import os
10
from shutil import copy2, copyfileobj
11
import time
12
13
# load modules
14
from elodie import log
15
from elodie.media.base import Base
16
17
18
class Text(Base):
19
20
    """The class for all text files.
21
22
    :param str source: The fully qualified path to the text file.
23
    """
24
25
    __name__ = 'Text'
26
27
    #: Valid extensions for text files.
28
    extensions = ('txt',)
29
30
    def __init__(self, source=None):
31
        super(Text, self).__init__(source)
32
        self.reset_cache()
33
34
    def get_album(self):
35
        self.parse_metadata_line()
36
        if(not isinstance(self.metadata_line, dict) or
37
                'album' not in self.metadata_line):
38
            return None
39
40
        return self.metadata_line['album']
41
42
    def get_coordinate(self, type='latitude'):
43
        self.parse_metadata_line()
44
        if not self.metadata_line:
45
            return None
46
        elif type in self.metadata_line:
47
            if type == 'latitude':
48
                return self.metadata_line['latitude'] or None
49
            elif type == 'longitude':
50
                return self.metadata_line['longitude'] or None
51
52
        return None
53
54
    def get_date_taken(self):
55
        source = self.source
56
        self.parse_metadata_line()
57
58
        # We return the value if found in metadata
59
        if(isinstance(self.metadata_line, dict) and
60
                'date_taken' in self.metadata_line):
61
            return time.gmtime(self.metadata_line['date_taken'])
62
63
        # If there's no date_taken in the metadata we return
64
        #   from the filesystem
65
        seconds_since_epoch = min(
66
            os.path.getmtime(source),
67
            os.path.getctime(source)
68
        )
69
        return time.gmtime(seconds_since_epoch)
70
71
    def get_metadata(self):
72
        self.parse_metadata_line()
73
        return super(Text, self).get_metadata()
74
75
    def get_original_name(self):
76
        self.parse_metadata_line()
77
78
        # We return the value if found in metadata
79
        if(isinstance(self.metadata_line, dict) and
80
                'original_name' in self.metadata_line):
81
            return self.metadata_line['original_name']
82
83
        return super(Text, self).get_original_name()
84
85
    def get_title(self):
86
        self.parse_metadata_line()
87
88
        if(not isinstance(self.metadata_line, dict) or
89
                'title' not in self.metadata_line):
90
            return None
91
92
        return self.metadata_line['title']
93
94
    def reset_cache(self):
95
        """Resets any internal cache
96
        """
97
        self.metadata_line = None
98
        super(Text, self).reset_cache()
99
100
    def set_album(self, name):
101
        status = self.write_metadata(album=name)
102
        self.reset_cache()
103
        return status
104
105
    def set_date_taken(self, passed_in_time):
106
        if(time is None):
107
            return False
108
109
        seconds_since_epoch = time.mktime(passed_in_time.timetuple())
110
        status = self.write_metadata(date_taken=seconds_since_epoch)
111
        self.reset_cache()
112
        return status
113
114
    def set_original_name(self, name=None):
115
        """Sets the original name if not already set.
116
117
        :returns: True, False, None
118
        """
119
        if(not self.is_valid()):
120
            return None
121
122
        # If EXIF original name tag is set then we return.
123
        if self.get_original_name() is not None:
124
            return None
125
126
        source = self.source
127
128
        if not name:
129
            name = os.path.basename(source)
130
131
        status = self.write_metadata(original_name=name)
132
        self.reset_cache()
133
        return status
134
135
    def set_location(self, latitude, longitude):
136
        status = self.write_metadata(latitude=latitude, longitude=longitude)
137
        self.reset_cache()
138
        return status
139
140
    def parse_metadata_line(self):
141
        if isinstance(self.metadata_line, dict):
142
            return self.metadata_line
143
144
        source = self.source
145
        if source is None:
146
            return None
147
148
        with open(source, 'r') as f:
149
            first_line = f.readline().strip()
150
151
        try:
152
            parsed_json = loads(first_line)
153
            if isinstance(parsed_json, dict):
154
                self.metadata_line = parsed_json
155
        except ValueError:
156
            log.error('Could not parse JSON from first line: %s' % first_line)
157
            pass
158
159
    def write_metadata(self, **kwargs):
160
        if len(kwargs) == 0:
161
            return False
162
163
        source = self.source
164
165
        self.parse_metadata_line()
166
167
        # Set defaults for a file without metadata
168
        # Check if self.metadata_line is set and use that instead
169
        metadata_line = {}
170
        has_metadata = False
171
        if isinstance(self.metadata_line, dict):
172
            metadata_line = self.metadata_line
173
            has_metadata = True
174
175
        for name in kwargs:
176
            metadata_line[name] = kwargs[name]
177
178
        metadata_as_json = dumps(metadata_line)
179
        
180
        # Create an _original copy just as we do with exiftool
181
        # This is to keep all file processing logic in line with exiftool
182
        copy2(source, source + '_original')
183
184
        if has_metadata:
185
            # Update the first line of this file in place
186
            # http://stackoverflow.com/a/14947384
187
            with open(source, 'r') as f_read:
188
                f_read.readline()
189
                with open(source, 'w') as f_write:
190
                    f_write.write("{}\n".format(metadata_as_json))
191
                    copyfileobj(f_read, f_write)
192
        else:
193
            # Prepend the metadata to the file
194
            with open(source, 'r') as f_read:
195
                original_contents = f_read.read()
196
                with open(source, 'w') as f_write:
197
                    f_write.write("{}\n{}".format(
198
                        metadata_as_json,
199
                        original_contents)
200
                    )
201
202
        self.reset_cache()
203
        return True
204