Completed
Push — master ( 7d8a0a...f3765b )
by
unknown
02:00
created

get_settings_hash()   B

Complexity

Conditions 6

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
c 0
b 0
f 0
dl 0
loc 22
rs 7.7857
1
import copy
2
import hashlib
3
import os
4
import pickle
5
6
from coalib.misc import Constants
7
8
9
def get_data_path(log_printer, identifier):
10
    """
11
    Get the full path of ``identifier`` present in the user's data directory.
12
13
    :param log_printer: A LogPrinter object to use for logging.
14
    :param identifier:  The file whose path needs to be expanded.
15
    :return:            Full path of the file, assuming it's present in the
16
                        user's config directory.
17
                        Returns ``None`` if there is a ``PermissionError``
18
                        in creating the directory.
19
    """
20
    try:
21
        os.makedirs(Constants.USER_DATA_DIR, exist_ok=True)
22
        return os.path.join(Constants.USER_DATA_DIR, hash_id(identifier))
23
    except PermissionError:
24
        log_printer.err("Unable to create user data directory '{}'. Continuing"
25
                        " without caching.".format(Constants.USER_DATA_DIR))
26
27
    return None
28
29
30
def delete_files(log_printer, identifiers):
31
    """
32
    Delete the given identifiers from the user's coala data directory.
33
34
    :param log_printer: A LogPrinter object to use for logging.
35
    :param identifiers: The list of files to be deleted.
36
    :return:            True if all the given files were successfully deleted.
37
                        False otherwise.
38
    """
39
    error_files = []
40
    result = True
41
    for identifier in identifiers:
42
        try:
43
            file_path = get_data_path(log_printer, identifier)
44
            if os.path.isfile(file_path):
45
                os.remove(file_path)
46
            else:
47
                result = False
48
        except (OSError, TypeError) as e:
49
            error_files.append(hash_id(identifier))
50
51
    if len(error_files) > 0:
52
        error_files = ", ".join(error_files)
53
        log_printer.warn("There was a problem deleting the following "
54
                         "files: {}. Please delete them manually from "
55
                         "'{}'.".format(error_files, Constants.USER_DATA_DIR))
56
        result = False
57
58
    return result
59
60
61
def pickle_load(log_printer, identifier, fallback=None):
62
    """
63
    Get the data stored in ``filename`` present in the user
64
    config directory. Example usage:
65
66
    >>> from pyprint.NullPrinter import NullPrinter
67
    >>> from coalib.output.printers.LogPrinter import LogPrinter
68
    >>> log_printer = LogPrinter(NullPrinter())
69
    >>> test_data = {"answer": 42}
70
    >>> pickle_dump(log_printer, "test_project", test_data)
71
    True
72
    >>> pickle_load(log_printer, "test_project")
73
    {'answer': 42}
74
    >>> pickle_load(log_printer, "nonexistent_project")
75
    >>> pickle_load(log_printer, "nonexistent_project", fallback=42)
76
    42
77
78
    :param log_printer: A LogPrinter object to use for logging.
79
    :param identifier:  The name of the file present in the user config
80
                        directory.
81
    :param fallback:    Return value to fallback to in case the file doesn't
82
                        exist.
83
    :return:            Data that is present in the file, if the file exists.
84
                        Otherwise the ``default`` value is returned.
85
    """
86
    file_path = get_data_path(log_printer, identifier)
87
    if file_path == None or not os.path.isfile(file_path):
88
        return fallback
89
    with open(file_path, "rb") as f:
90
        try:
91
            return pickle.load(f)
92
        except (pickle.UnpicklingError, EOFError) as e:
93
            log_printer.warn("The given file is corrupted and will be "
94
                             "removed.")
95
            delete_files(log_printer, [identifier])
96
            return fallback
97
98
99
def pickle_dump(log_printer, identifier, data):
100
    """
101
    Write ``data`` into the file ``filename`` present in the user
102
    config directory.
103
104
    :param log_printer: A LogPrinter object to use for logging.
105
    :param identifier:  The name of the file present in the user config
106
                        directory.
107
    :param data:        Data to be serialized and written to the file using
108
                        pickle.
109
    :return:            True if the write was successful.
110
                        False if there was a permission error in writing.
111
    """
112
    file_path = get_data_path(log_printer, identifier)
113
    if file_path == None:
114
        # Exit silently since the error has been logged in ``get_data_path``
115
        return False
116
    with open(file_path, "wb") as f:
117
        pickle.dump(data, f)
118
    return True
119
120
121
def hash_id(text):
122
    """
123
    Hashes the given text.
124
125
    :param text: String to to be hashed
126
    :return:     A MD5 hash of the given string
127
    """
128
    return hashlib.md5(text.encode("utf-8")).hexdigest()
129
130
131
def get_settings_hash(sections,
132
                      targets=[],
133
                      ignore_settings: list=["disable_caching"]):
134
    """
135
    Compute and return a unique hash for the settings.
136
137
    :param sections:        A dict containing the settings for each section.
138
    :param targets:         The list of sections that are enabled.
139
    :param ignore_settings: Setting keys to remove from sections before
140
                            hashing.
141
    :return:                A MD5 hash that is unique to the settings used.
142
    """
143
    settings = []
144
    for section in sections:
145
        if section in targets or targets == []:
146
            section_copy = copy.deepcopy(sections[section])
147
            for setting in ignore_settings:
148
                if setting in section_copy:
149
                    section_copy.delete_setting(setting)
150
            settings.append(str(section_copy))
151
152
    return hash_id(str(settings))
153
154
155
def settings_changed(log_printer, settings_hash):
156
    """
157
    Determine if the settings have changed since the last run with caching.
158
159
    :param log_printer:   A LogPrinter object to use for logging.
160
    :param settings_hash: A MD5 hash that is unique to the settings used.
161
    :return:              Return True if the settings hash has changed
162
                          Return False otherwise.
163
    """
164
    project_hash = hash_id(os.getcwd())
165
166
    settings_hash_db = pickle_load(log_printer, "settings_hash_db", {})
167
    if project_hash not in settings_hash_db:
168
        # This is the first time coala is run on this project, so the cache
169
        # will be flushed automatically.
170
        return False
171
172
    result = settings_hash_db[project_hash] != settings_hash
173
    if result:
174
        del settings_hash_db[project_hash]
175
        log_printer.debug("Since the configuration settings have "
176
                          "changed since the last run, the "
177
                          "cache will be flushed and rebuilt.")
178
179
    return result
180
181
182
def update_settings_db(log_printer, settings_hash):
183
    """
184
    Update the config file last modification date.
185
186
    :param log_printer:   A LogPrinter object to use for logging.
187
    :param settings_hash: A MD5 hash that is unique to the settings used.
188
    """
189
    project_hash = hash_id(os.getcwd())
190
191
    settings_hash_db = pickle_load(log_printer, "settings_hash_db", {})
192
    settings_hash_db[project_hash] = settings_hash
193
    pickle_dump(log_printer, "settings_hash_db", settings_hash_db)
194