summaryrefslogtreecommitdiffstats
path: root/combox/silo.py
blob: 0f2de3c5dd0149fd8096f8c58ba7af826164810f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# -*- coding: utf-8 -*-
#
#    Copyright (C) 2016 Dr. Robert C. Green II.
#
#    This file is part of Combox.
#
#   Combox is free software: you can redistribute it and/or modify it
#   under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   Combox is distributed in the hope that it will be useful, but
#   WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#   General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with Combox (see COPYING).  If not, see
#   <http://www.gnu.org/licenses/>.

import pickledb

from os import path
from threading import Lock

from combox.file import hash_file


class ComboxSilo(object):
    """Helps keep track of files in combox directory.

    :param dict config:
        A dictionary that contains configuration information about
        combox.
    :param threading.Lock lock:
        Lock used by :class:`.ComboxDirMonitor` and
        :class:`.NodeDirMonitor` to access this object.

    """

    def __init__(self, config, lock):
        """Initialize ComboxSilo.

        """
        self.config = config

        self.silo_path = path.join(config['silo_dir'], 'silo.db')
        self.db = pickledb.load(self.silo_path, True)

        ## things we need for housekeep the node directories
        self.node_dicts = ['file_created', 'file_modified', 'file_moved',
                           'file_deleted', 'file_moved_info']
        """List of names of dictionaries stored in DB to housekeep the node
           directories.

        Initialized to::

            ['file_created', 'file_modified', 'file_moved',
             'file_deleted', 'file_moved_info']

        when :class:`ComboxSilo` object is created.

        """

        # created the dicts if not already created.
        for ndict in self.node_dicts:
            if not self.db.get(ndict):
                self.db.dcreate(ndict)

        self.lock = lock


    def reload(self):
        """Re-loads the DB from disk.

        """
        with self.lock:
            self.db = pickledb.load(self.silo_path, True)


    def update(self, filep):
        """Update filep's info in DB.

        Path of `filep` and the hash of its content is written to the
        DB.

        :param str filep:
            Path to the file under the combox directory.
        :returns: `True`
        :rtype: bool

        """
        self.reload()
        with self.lock:
            fhash = hash_file(filep)
            return self.db.set(filep, fhash)


    def keys(self):
        """Returns list of file' paths tracked by combox.

        :returns:
            List of file paths of files tracked by combox.
        :rtype:
            `True`

        """
        # this is why Redis or some other key-value DB should be used
        # instead of PickleDB
        self.reload()
        with self.lock:
            return self.db.db.keys()


    def remove(self, filep):
        """Removes `filep` from DB.

        :param str filep:
            Path to a file under combox directory whose information
            has to be removed from the DB.
        :returns:
            Returns `False` if `filep` is not present in the DB; `True`
            otherwise.
        :rtype: bool

        """
        try:
            self.reload()
            with self.lock:
                return self.db.rem(filep)
        except KeyError, e:
            # means `filep' not present in db.
            return False


    def exists(self, filep):
        """Checks if filep's info is stored in DB.

        :param str filep:
            Path to a file under the  combox directory.
        :returns:
            Returns `True` if filep's info is in DB; `False` otherwise.
        :rtype: bool

        """
        self.reload()
        with self.lock:
            if self.db.get(filep) is None:
                return False
            else:
                return True


    def stale(self, filep, fhash=None):
        """Checks if filep's info in the DB is outdated.

        :param str filep:
            Path to a file under the combox directory.
        :param bool fhash:
            If not `None`, it is assumed to be filep's hash.
        :returns:
            Returns `True`, if filep's hash is outdated; `False` if
            filep's hash is correct; `None` if filep's info is not yet
            stored in DB.
        :rtype: bool

        """
        if not fhash:
            fhash = hash_file(filep)

        self.reload()
        with self.lock:
            fhash_in_db = self.db.get(filep)

        if fhash_in_db is None:
            return None
        elif fhash == fhash_in_db:
            return False
        else:
            return True


    def nodedicts(self):
        """Returns :attr:`node_dicts`

        """
        return self.node_dicts


    def node_set(self, type_, file_, num=-1):
        """Update information about the shard of `file_` in dictionary `type_` in the DB.

        :param str type_:
            The name of the dictinary in DB. It must be one of the
            following values: `file_created`, `file_modified`,
            `file_moved`, `file_deleted`.
        :param str file_:
            Path of the file under the combox directory.
        :param int num:
            Integer associated with the `file_`.

        """

        self.reload()
        with self.lock:
            if num != -1:
                self.db.dadd(type_, (file_, num))
                return
            try:
                num = self.db.dget(type_, file_)
                num += 1
            except KeyError, e:
                # I don't think this is the right way to do this. :|
                #
                # If we are here it means file_ is not already there,
                # so:
                num = 1
            self.db.dadd(type_, (file_, num))


    def node_store_moved_info(self, src_path, dest_path):
        """
        Make note of a file/directory moved from `src_path` to `dest_path`.

        :param str src_path:
            The source path of the file being moved.
        :param str dest_path:
            The destination path of the file being moved.

        """
        self.reload()
        with self.lock:
            self.db.dadd('file_moved_info', (src_path, dest_path))


    def node_get(self, type_, file_):
        """Returns a number denoting the number of node directories in which the `file_`'s shards was created/modified/moved/deleted.

        :param str type_:
            The name of the dictinary in DB. It must be one of the
            following values: `file_created`, `file_modified`,
            `file_moved`, `file_deleted`.
        :param str file_:
            Path of the file under the combox directory.
        :returns:
            Number denoting the number of node directories in which
            the `file_`' shards were created/modified/moved/deleted.
        :rtype: int

        """
        self.reload()
        with self.lock:
            try:
                return self.db.dget(type_, file_)
            except KeyError, e:
                # file_ info not there under type_ dict.
                return None


    def node_rem(self, type_, file_):
        """Removes information about the shards of `file_` in the `type_` dictionary in DB.

        :param str type_:
            The name of the dictinary in DB. It must be one of the
            following values: `file_created`, `file_modified`,
            `file_moved`, `file_deleted`.
        :param str file_:
            Path of the file under the combox directory.

        """
        self.reload()
        with self.lock:
            try:
                return self.db.dpop(type_, file_)
            except KeyError, e:
                # means file_'s info was already removed.
                # do nothing
                pass