doc: update v3.3 release notes draft
[git-cola.git] / cola / settings.py
blob81bd5d07ffd2cd2f049e93778140baacafb9c3b7
1 """Save settings, bookmarks, etc.
2 """
3 from __future__ import division, absolute_import, unicode_literals
4 import json
5 import os
6 import sys
8 from . import core
9 from . import git
10 from . import resources
13 def mkdict(obj):
14 """Transform None and non-dicts into dicts"""
15 if isinstance(obj, dict):
16 value = obj
17 else:
18 value = {}
19 return value
22 def mklist(obj):
23 """Transform None and non-lists into lists"""
24 if isinstance(obj, list):
25 value = obj
26 elif isinstance(obj, tuple):
27 value = list(obj)
28 else:
29 value = []
30 return value
33 def read_json(path):
34 try:
35 with core.xopen(path, 'rt') as fp:
36 return mkdict(json.load(fp))
37 except (ValueError, TypeError, OSError, IOError): # bad path or json
38 return {}
41 def write_json(values, path):
42 try:
43 parent = os.path.dirname(path)
44 if not core.isdir(parent):
45 core.makedirs(parent)
46 with core.xopen(path, 'wt') as fp:
47 json.dump(values, fp, indent=4)
48 except (ValueError, TypeError, OSError, IOError):
49 sys.stderr.write('git-cola: error writing "%s"\n' % path)
52 class Settings(object):
53 config_path = resources.config_home('settings')
54 bookmarks = property(lambda self: mklist(self.values['bookmarks']))
55 gui_state = property(lambda self: mkdict(self.values['gui_state']))
56 recent = property(lambda self: mklist(self.values['recent']))
57 copy_formats = property(lambda self: mklist(self.values['copy_formats']))
59 def __init__(self, verify=git.is_git_worktree):
60 """Load existing settings if they exist"""
61 self.values = {
62 'bookmarks': [],
63 'gui_state': {},
64 'recent': [],
65 'copy_formats': [],
67 self.verify = verify
69 def remove_missing(self):
70 missing_bookmarks = []
71 missing_recent = []
73 for bookmark in self.bookmarks:
74 if not self.verify(bookmark['path']):
75 missing_bookmarks.append(bookmark)
77 for bookmark in missing_bookmarks:
78 try:
79 self.bookmarks.remove(bookmark)
80 except ValueError:
81 pass
83 for recent in self.recent:
84 if not self.verify(recent['path']):
85 missing_recent.append(recent)
87 for recent in missing_recent:
88 try:
89 self.recent.remove(recent)
90 except ValueError:
91 pass
93 def add_bookmark(self, path, name):
94 """Adds a bookmark to the saved settings"""
95 bookmark = {'path': path, 'name': name}
96 if bookmark not in self.bookmarks:
97 self.bookmarks.append(bookmark)
99 def remove_bookmark(self, path, name):
100 """Remove a bookmark"""
101 bookmark = {'path': path, 'name': name}
102 try:
103 self.bookmarks.remove(bookmark)
104 except ValueError:
105 pass
107 def rename_bookmark(self, path, name, new_name):
108 return rename_entry(self.bookmarks, path, name, new_name)
110 def add_recent(self, path, max_recent):
111 try:
112 index = [recent['path'] for recent in self.recent].index(path)
113 entry = self.recent.pop(index)
114 except (IndexError, ValueError):
115 entry = {
116 'name': os.path.basename(path),
117 'path': path,
119 self.recent.insert(0, entry)
120 if len(self.recent) >= max_recent:
121 self.recent.pop()
123 def remove_recent(self, path):
124 """Removes an item from the recent items list"""
125 try:
126 index = [
127 recent.get('path', '') for recent in self.recent
128 ].index(path)
129 except ValueError:
130 return
131 try:
132 self.recent.pop(index)
133 except IndexError:
134 return
136 def rename_recent(self, path, name, new_name):
137 return rename_entry(self.recent, path, name, new_name)
139 def path(self):
140 return self.config_path
142 def save(self):
143 write_json(self.values, self.path())
145 def load(self):
146 self.values.update(self.asdict())
147 self.upgrade_settings()
148 self.remove_missing()
150 def upgrade_settings(self):
151 """Upgrade git-cola settings"""
152 # Upgrade bookmarks to the new dict-based bookmarks format.
153 if self.bookmarks and not isinstance(self.bookmarks[0], dict):
154 bookmarks = [dict(name=os.path.basename(path), path=path)
155 for path in self.bookmarks]
156 self.values['bookmarks'] = bookmarks
158 if self.recent and not isinstance(self.recent[0], dict):
159 recent = [dict(name=os.path.basename(path), path=path)
160 for path in self.recent]
161 self.values['recent'] = recent
163 def asdict(self):
164 path = self.path()
165 if core.exists(path):
166 return read_json(path)
167 # We couldn't find ~/.config/git-cola, try ~/.cola
168 values = {}
169 path = os.path.join(core.expanduser('~'), '.cola')
170 if core.exists(path):
171 json_values = read_json(path)
172 # Keep only the entries we care about
173 for key in self.values:
174 try:
175 values[key] = json_values[key]
176 except KeyError:
177 pass
178 return values
180 def reload_recent(self):
181 values = self.asdict()
182 self.values['recent'] = mklist(values.get('recent', []))
184 def save_gui_state(self, gui):
185 """Saves settings for a cola view"""
186 name = gui.name()
187 self.gui_state[name] = mkdict(gui.export_state())
188 self.save()
190 def get_gui_state(self, gui):
191 """Returns the saved state for a gui"""
192 try:
193 state = mkdict(self.gui_state[gui.name()])
194 except KeyError:
195 state = self.gui_state[gui.name()] = {}
196 return state
199 def rename_entry(entries, path, name, new_name):
200 entry = {'name': name, 'path': path}
201 try:
202 index = entries.index(entry)
203 except ValueError:
204 return False
206 if all([item['name'] != new_name for item in entries]):
207 entries[index]['name'] = new_name
208 return True
210 return False
213 class Session(Settings):
214 """Store per-session settings"""
216 _sessions_dir = resources.config_home('sessions')
218 repo = property(lambda self: self.values['repo'])
220 def __init__(self, session_id, repo=None):
221 Settings.__init__(self)
222 self.session_id = session_id
223 self.values.update({'repo': repo})
225 def path(self):
226 return os.path.join(self._sessions_dir, self.session_id)
228 def load(self):
229 path = self.path()
230 if core.exists(path):
231 self.values.update(read_json(path))
232 try:
233 os.unlink(path)
234 except (OSError, ValueError):
235 pass
236 return True
237 return False