1 """Save settings, bookmarks, etc.
3 from __future__
import division
, absolute_import
, unicode_literals
11 from . import resources
15 """Transform None and non-dicts into dicts"""
16 if isinstance(obj
, dict):
24 """Transform None and non-lists into lists"""
25 if isinstance(obj
, list):
27 elif isinstance(obj
, tuple):
36 with core
.xopen(path
, 'rt') as fp
:
37 return mkdict(json
.load(fp
))
38 except (ValueError, TypeError, OSError, IOError): # bad path or json
42 def write_json(values
, path
):
44 parent
= os
.path
.dirname(path
)
45 if not core
.isdir(parent
):
47 with core
.xopen(path
, 'wt') as fp
:
48 json
.dump(values
, fp
, indent
=4)
49 except (ValueError, TypeError, OSError, IOError):
50 sys
.stderr
.write('git-cola: error writing "%s"\n' % path
)
53 class Settings(object):
54 config_path
= resources
.config_home('settings')
55 bookmarks
= property(lambda self
: mklist(self
.values
['bookmarks']))
56 gui_state
= property(lambda self
: mkdict(self
.values
['gui_state']))
57 recent
= property(lambda self
: mklist(self
.values
['recent']))
58 copy_formats
= property(lambda self
: mklist(self
.values
['copy_formats']))
60 def __init__(self
, verify
=git
.is_git_worktree
):
61 """Load existing settings if they exist"""
70 def remove_missing_bookmarks(self
):
71 """Remove "favorites" bookmarks that no longer exist"""
72 missing_bookmarks
= []
73 for bookmark
in self
.bookmarks
:
74 if not self
.verify(bookmark
['path']):
75 missing_bookmarks
.append(bookmark
)
77 for bookmark
in missing_bookmarks
:
79 self
.bookmarks
.remove(bookmark
)
83 def remove_missing_recent(self
):
84 """Remove "recent" repositories that no longer exist"""
86 for recent
in self
.recent
:
87 if not self
.verify(recent
['path']):
88 missing_recent
.append(recent
)
90 for recent
in missing_recent
:
92 self
.recent
.remove(recent
)
96 def add_bookmark(self
, path
, name
):
97 """Adds a bookmark to the saved settings"""
98 bookmark
= {'path': display
.normalize_path(path
), 'name': name
}
99 if bookmark
not in self
.bookmarks
:
100 self
.bookmarks
.append(bookmark
)
102 def remove_bookmark(self
, path
, name
):
103 """Remove a bookmark"""
104 bookmark
= {'path': display
.normalize_path(path
), 'name': name
}
106 self
.bookmarks
.remove(bookmark
)
110 def rename_bookmark(self
, path
, name
, new_name
):
111 return rename_entry(self
.bookmarks
, path
, name
, new_name
)
113 def add_recent(self
, path
, max_recent
):
114 normalize
= display
.normalize_path
115 path
= normalize(path
)
118 normalize(recent
['path']) for recent
in self
.recent
120 entry
= self
.recent
.pop(index
)
121 except (IndexError, ValueError):
123 'name': os
.path
.basename(path
),
126 self
.recent
.insert(0, entry
)
127 if len(self
.recent
) > max_recent
:
130 def remove_recent(self
, path
):
131 """Removes an item from the recent items list"""
132 normalize
= display
.normalize_path
133 path
= normalize(path
)
136 normalize(recent
.get('path', ''))
137 for recent
in self
.recent
142 self
.recent
.pop(index
)
146 def rename_recent(self
, path
, name
, new_name
):
147 return rename_entry(self
.recent
, path
, name
, new_name
)
150 return self
.config_path
153 write_json(self
.values
, self
.path())
155 def load(self
, path
=None):
156 self
.values
.update(self
.asdict(path
=path
))
157 self
.upgrade_settings()
161 def read(verify
=git
.is_git_worktree
):
162 """Load settings from disk"""
163 settings
= Settings(verify
=verify
)
167 def upgrade_settings(self
):
168 """Upgrade git-cola settings"""
169 # Upgrade bookmarks to the new dict-based bookmarks format.
170 normalize
= display
.normalize_path
171 if self
.bookmarks
and not isinstance(self
.bookmarks
[0], dict):
173 dict(name
=os
.path
.basename(path
), path
=normalize(path
))
174 for path
in self
.bookmarks
176 self
.values
['bookmarks'] = bookmarks
178 if self
.recent
and not isinstance(self
.recent
[0], dict):
180 dict(name
=os
.path
.basename(path
), path
=normalize(path
))
181 for path
in self
.recent
183 self
.values
['recent'] = recent
185 def asdict(self
, path
=None):
188 if core
.exists(path
):
189 return read_json(path
)
190 # We couldn't find ~/.config/git-cola, try ~/.cola
192 path
= os
.path
.join(core
.expanduser('~'), '.cola')
193 if core
.exists(path
):
194 json_values
= read_json(path
)
195 for key
in self
.values
:
197 values
[key
] = json_values
[key
]
200 # Ensure that all stored bookmarks use normalized paths ("/" only).
201 normalize
= display
.normalize_path
202 for entry
in values
.get('bookmarks', []):
203 entry
['path'] = normalize(entry
['path'])
204 for entry
in values
.get('recent', []):
205 entry
['path'] = normalize(entry
['path'])
208 def save_gui_state(self
, gui
):
209 """Saves settings for a cola view"""
211 self
.gui_state
[name
] = mkdict(gui
.export_state())
214 def get_gui_state(self
, gui
):
215 """Returns the saved state for a gui"""
217 state
= mkdict(self
.gui_state
[gui
.name()])
219 state
= self
.gui_state
[gui
.name()] = {}
223 def rename_entry(entries
, path
, name
, new_name
):
224 normalize
= display
.normalize_path
225 path
= normalize(path
)
226 entry
= {'name': name
, 'path': path
}
228 index
= entries
.index(entry
)
232 if all([item
['name'] != new_name
for item
in entries
]):
233 entries
[index
]['name'] = new_name
240 class Session(Settings
):
241 """Store per-session settings
243 XDG sessions are created by the QApplication::commitData() callback.
244 These sessions are stored once, and loaded once. They are deleted once
245 loaded. The behavior of path() is such that it forgets its session path()
246 and behaves like a return Settings object after the session has been
249 Once the session is loaded, it is removed and further calls to save()
250 will save to the usual $XDG_CONFIG_HOME/git-cola/settings location.
254 _sessions_dir
= resources
.config_home('sessions')
256 repo
= property(lambda self
: self
.values
['repo'])
258 def __init__(self
, session_id
, repo
=None):
259 Settings
.__init
__(self
)
260 self
.session_id
= session_id
261 self
.values
.update({'repo': repo
})
264 def session_path(self
):
265 """The session-specific session file"""
266 return os
.path
.join(self
._sessions
_dir
, self
.session_id
)
269 base_path
= super(Session
, self
).path()
273 path
= self
.session_path()
274 if not os
.path
.exists(path
):
278 def load(self
, path
=None):
279 """Load the session and expire it for future loads
281 The session should be loaded only once. We remove the session file
282 when it's loaded, and set the session to be expired. This results in
283 future calls to load() and save() using the default Settings path
284 rather than the session-specific path.
286 The use case for sessions is when the user logs out with apps running.
287 We will restore their state, and if they then shutdown, it'll be just
288 like a normal shutdown and settings will be stored to
289 ~/.config/git-cola/settings instead of the session path.
291 This is accomplished by "expiring" the session after it has
292 been loaded initially.
295 result
= super(Session
, self
).load(path
=path
)
296 # This is the initial load, so expire the session and remove the
297 # session state file. Future calls will be equivalent to
301 path
= self
.session_path()
302 if core
.exists(path
):
305 except (OSError, ValueError):
313 """Reload settings from the base settings path"""
314 # This method does not expire the session.
315 path
= super(Session
, self
).path()
316 return super(Session
, self
).load(path
=path
)