/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
3586.1.2 by Ian Clatworthy
first cut of views.py
1
# Copyright (C) 2008 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""View management.
18
19
Views are contained within a working tree and normally constructed 
20
when first accessed.  Clients should do, for example, ...
21
22
  tree.views.lookup_view()
23
"""
24
25
26
import re
27
28
from bzrlib import (
29
    errors,
30
    )
31
32
33
_VIEWS_FORMAT_MARKER_RE = re.compile(r'Bazaar views format (\d+)')
3586.2.4 by Ian Clatworthy
fix storage after deleting the last view
34
_VIEWS_FORMAT1_MARKER = "Bazaar views format 1\n"
3586.1.2 by Ian Clatworthy
first cut of views.py
35
36
37
class _Views(object):
38
    """Base class for View managers."""
39
40
    def supports_views(self):
41
        raise NotImplementedError(self.supports_views)
42
43
44
class PathBasedViews(_Views):
45
    """View storage in an unversioned tree control file.
46
47
    Views are stored in terms of paths relative to the tree root.
48
49
    The top line of the control file is a format marker in the format:
50
51
      Bazaar views format X
52
53
    where X is an integer number. Version 1 format is stored as follows:
54
55
     * the line after the format marker holds the name of the current view
56
57
     * subsequent lines hold view definitions, one per line is the format
58
59
       name file1 file2 ...
60
61
    where the fields are separated by a nul character (\0). The views file
62
    is encoded in utf-8.
63
    """
64
65
    def __init__(self, tree):
3586.1.7 by Ian Clatworthy
first cut at WTF5
66
        self.tree = tree
3586.1.2 by Ian Clatworthy
first cut of views.py
67
        self._loaded = False
68
        self._current = None
69
        self._views = {}
70
71
    def supports_views(self):
72
        return True
73
74
    def get_view_info(self):
75
        """Get the current view and dictionary of views.
76
77
        :return: current, views where
78
          current = the name of the current view or None if no view is enabled
79
          views = a map from view name to list of files/directories
80
        """
81
        self._load_view_info()
82
        return self._current, self._views
83
84
    def set_view_info(self, current, views):
85
        """Set the current view and dictionary of views.
86
87
        :param current: the name of the current view or None if no view is
88
          enabled
89
        :param views: a map from view name to list of files/directories
90
        """
91
        if current is not None and current not in views:
92
            raise errors.NoSuchView(current)
93
        self.tree.lock_write()
94
        try:
95
            self._current = current
96
            self._views = views
97
            self._save_view_info()
98
        finally:
99
            self.tree.unlock()
100
101
    def lookup_view(self, view_name=None):
102
        """Return the contents of a view.
103
        
104
        :param view_Name: name of the view or None to lookup the current view
105
        :return: the list of files/directories in the requested view
106
        """
107
        self._load_view_info()
108
        try:
109
            if view_name is None:
110
                if self._current:
111
                    view_name = self._current
112
                else:
113
                    return []
114
            return self._views[view_name]
115
        except KeyError:
116
            raise errors.NoSuchView(view_name)
117
118
    def set_view(self, view_name, view_files, make_current=True):
119
        """Add or update a view definition.
120
        
121
        :param view_name: the name of the view
122
        :param view_files: the list of files/directories in the view
123
        :param make_current: make this view the current one or not
124
        """
125
        self.tree.lock_write()
126
        try:
127
            self._load_view_info()
128
            self._views[view_name] = view_files
129
            if make_current:
130
                self._current = view_name
131
            self._save_view_info()
132
        finally:
133
            self.tree.unlock()
134
135
    def delete_view(self, view_name):
136
        """Delete a view definition.
137
138
        If the view deleted is the current one, the current view is reset.
139
        """
140
        self.tree.lock_write()
141
        try:
142
            self._load_view_info()
143
            try:
144
                del self._views[view_name]
145
            except KeyError:
146
                raise errors.NoSuchView(view_name)
3586.1.7 by Ian Clatworthy
first cut at WTF5
147
            if view_name == self._current:
3586.1.2 by Ian Clatworthy
first cut of views.py
148
                self._current = None
149
            self._save_view_info()
150
        finally:
151
            self.tree.unlock()
152
153
    def _save_view_info(self):
154
        """Save the current view and all view definitions.
155
156
        Be sure to have initialised self._current and self._views before
157
        calling this method.
158
        """
159
        self.tree.lock_write()
160
        try:
161
            self.tree._transport.put_bytes('views',
162
                self._serialize_view_content(self._current, self._views))
163
        finally:
164
            self.tree.unlock()
165
166
    def _load_view_info(self):
167
        """Load the current view and dictionary of view definitions."""
168
        if not self._loaded:
169
            self.tree.lock_read()
170
            try:
171
                try:
172
                    view_content = self.tree._transport.get_bytes('views')
173
                except errors.NoSuchFile, e:
174
                    self._current, self._views = None, {}
175
                else:
176
                    self._current, self._views = \
177
                        self._deserialize_view_content(view_content)
178
            finally:
179
                self.tree.unlock()
180
            self._loaded = True
181
182
    def _serialize_view_content(self, current, view_dict):
183
        """Convert a current view and view dictionary into a stream."""
3586.1.7 by Ian Clatworthy
first cut at WTF5
184
        lines = [_VIEWS_FORMAT1_MARKER]
3586.1.2 by Ian Clatworthy
first cut of views.py
185
        if current is None:
3586.2.4 by Ian Clatworthy
fix storage after deleting the last view
186
            lines.append("\n")
3586.1.2 by Ian Clatworthy
first cut of views.py
187
        else:
3586.2.4 by Ian Clatworthy
fix storage after deleting the last view
188
            lines.append((current + "\n").encode('utf-8'))
3586.1.2 by Ian Clatworthy
first cut of views.py
189
        for view in sorted(view_dict):
3586.2.4 by Ian Clatworthy
fix storage after deleting the last view
190
            view_data = "%s\0%s\n" % (view, "\0".join(view_dict[view]))
3586.1.2 by Ian Clatworthy
first cut of views.py
191
            lines.append(view_data.encode('utf-8'))
3586.2.4 by Ian Clatworthy
fix storage after deleting the last view
192
        return "".join(lines)
3586.1.2 by Ian Clatworthy
first cut of views.py
193
194
    def _deserialize_view_content(self, view_content):
195
        """Convert a stream into a current view and dictionary of views."""
196
        # as a special case to make initialization easy, an empty definition
197
        # maps to no current view and an empty view dictionary
198
        if view_content == '':
199
            return None, {}
200
        lines = view_content.splitlines()
3586.1.7 by Ian Clatworthy
first cut at WTF5
201
        match = _VIEWS_FORMAT_MARKER_RE.match(lines[0])
3586.1.2 by Ian Clatworthy
first cut of views.py
202
        if not match:
203
            raise ValueError(
204
                "format marker missing from top of views file")
3586.1.7 by Ian Clatworthy
first cut at WTF5
205
        elif match.group(1) != '1':
3586.1.2 by Ian Clatworthy
first cut of views.py
206
            raise ValueError(
207
                "cannot decode views format %s" % match.group(1))
208
        try:
209
            current = lines[1].decode('utf-8')
3586.2.3 by Ian Clatworthy
deserialise empty string to no current view
210
            if current == '':
211
                current = None
3586.1.2 by Ian Clatworthy
first cut of views.py
212
            views = {}
213
            for line in lines[2:]:
214
                parts = line.decode('utf-8').split('\0')
215
                view = parts.pop(0)
216
                views[view] = parts
217
            return current, views
218
        except ValueError, e:
219
            raise ValueError("failed to deserialize views content %r: %s"
220
                % (view_content, e))
221
222
223
class DisabledViews(_Views):
224
    """View storage that refuses to store anything.
225
226
    This is used by older formats that can't store views.
227
    """
228
229
    def __init__(self, tree):
230
        self.tree = tree
231
232
    def supports_views(self):
233
        return False
234
235
    def _not_supported(self, *a, **k):
236
        raise errors.ViewsNotSupported(self.tree)
237
238
    get_view_info = _not_supported
239
    set_view_info = _not_supported
240
    lookup_view = _not_supported
241
    set_view = _not_supported
242
    delete_view = _not_supported