1 """Save settings, bookmarks, etc."""
2 from __future__
import absolute_import
, division
, print_function
, unicode_literals
10 from . import resources
14 """Transform None and non-dicts into dicts"""
15 if isinstance(obj
, dict):
23 """Transform None and non-lists into lists"""
24 if isinstance(obj
, list):
26 elif isinstance(obj
, tuple):
35 with core
.xopen(path
, 'rt') as fp
:
36 return mkdict(json
.load(fp
))
37 except (ValueError, TypeError, OSError, IOError): # bad path or json
41 def write_json(values
, path
):
43 parent
= os
.path
.dirname(path
)
44 if not core
.isdir(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"""
69 def remove_missing_bookmarks(self
):
70 """Remove "favorites" bookmarks that no longer exist"""
71 missing_bookmarks
= []
72 for bookmark
in self
.bookmarks
:
73 if not self
.verify(bookmark
['path']):
74 missing_bookmarks
.append(bookmark
)
76 for bookmark
in missing_bookmarks
:
78 self
.bookmarks
.remove(bookmark
)
82 def remove_missing_recent(self
):
83 """Remove "recent" repositories that no longer exist"""
85 for recent
in self
.recent
:
86 if not self
.verify(recent
['path']):
87 missing_recent
.append(recent
)
89 for recent
in missing_recent
:
91 self
.recent
.remove(recent
)
95 def add_bookmark(self
, path
, name
):
96 """Adds a bookmark to the saved settings"""
97 bookmark
= {'path': display
.normalize_path(path
), 'name': name
}
98 if bookmark
not in self
.bookmarks
:
99 self
.bookmarks
.append(bookmark
)
101 def remove_bookmark(self
, path
, name
):
102 """Remove a bookmark"""
103 bookmark
= {'path': display
.normalize_path(path
), 'name': name
}
105 self
.bookmarks
.remove(bookmark
)
109 def rename_bookmark(self
, path
, name
, new_name
):
110 return rename_entry(self
.bookmarks
, path
, name
, new_name
)
112 def add_recent(self
, path
, max_recent
):
113 normalize
= display
.normalize_path
114 path
= normalize(path
)
116 index
= [normalize(recent
['path']) for recent
in self
.recent
].index(path
)
117 entry
= self
.recent
.pop(index
)
118 except (IndexError, ValueError):
120 'name': os
.path
.basename(path
),
123 self
.recent
.insert(0, entry
)
124 if len(self
.recent
) > max_recent
:
127 def remove_recent(self
, path
):
128 """Removes an item from the recent items list"""
129 normalize
= display
.normalize_path
130 path
= normalize(path
)
132 index
= [normalize(recent
.get('path', '')) for recent
in self
.recent
].index(
138 self
.recent
.pop(index
)
142 def rename_recent(self
, path
, name
, new_name
):
143 return rename_entry(self
.recent
, path
, name
, new_name
)
146 return self
.config_path
149 write_json(self
.values
, self
.path())
151 def load(self
, path
=None):
152 self
.values
.update(self
.asdict(path
=path
))
153 self
.upgrade_settings()
157 def read(verify
=git
.is_git_worktree
):
158 """Load settings from disk"""
159 settings
= Settings(verify
=verify
)
163 def upgrade_settings(self
):
164 """Upgrade git-cola settings"""
165 # Upgrade bookmarks to the new dict-based bookmarks format.
166 normalize
= display
.normalize_path
167 if self
.bookmarks
and not isinstance(self
.bookmarks
[0], dict):
169 dict(name
=os
.path
.basename(path
), path
=normalize(path
))
170 for path
in self
.bookmarks
172 self
.values
['bookmarks'] = bookmarks
174 if self
.recent
and not isinstance(self
.recent
[0], dict):
176 dict(name
=os
.path
.basename(path
), path
=normalize(path
))
177 for path
in self
.recent
179 self
.values
['recent'] = recent
181 def asdict(self
, path
=None):
184 if core
.exists(path
):
185 return read_json(path
)
186 # We couldn't find ~/.config/git-cola, try ~/.cola
188 path
= os
.path
.join(core
.expanduser('~'), '.cola')
189 if core
.exists(path
):
190 json_values
= read_json(path
)
191 for key
in self
.values
:
193 values
[key
] = json_values
[key
]
196 # Ensure that all stored bookmarks use normalized paths ("/" only).
197 normalize
= display
.normalize_path
198 for entry
in values
.get('bookmarks', []):
199 entry
['path'] = normalize(entry
['path'])
200 for entry
in values
.get('recent', []):
201 entry
['path'] = normalize(entry
['path'])
204 def save_gui_state(self
, gui
):
205 """Saves settings for a cola view"""
207 self
.gui_state
[name
] = mkdict(gui
.export_state())
210 def get_gui_state(self
, gui
):
211 """Returns the saved state for a gui"""
213 state
= mkdict(self
.gui_state
[gui
.name()])
215 state
= self
.gui_state
[gui
.name()] = {}
219 def rename_entry(entries
, path
, name
, new_name
):
220 normalize
= display
.normalize_path
221 path
= normalize(path
)
222 entry
= {'name': name
, 'path': path
}
224 index
= entries
.index(entry
)
228 if all(item
['name'] != new_name
for item
in entries
):
229 entries
[index
]['name'] = new_name
236 class Session(Settings
):
237 """Store per-session settings
239 XDG sessions are created by the QApplication::commitData() callback.
240 These sessions are stored once, and loaded once. They are deleted once
241 loaded. The behavior of path() is such that it forgets its session path()
242 and behaves like a return Settings object after the session has been
245 Once the session is loaded, it is removed and further calls to save()
246 will save to the usual $XDG_CONFIG_HOME/git-cola/settings location.
250 _sessions_dir
= resources
.config_home('sessions')
252 repo
= property(lambda self
: self
.values
['repo'])
254 def __init__(self
, session_id
, repo
=None):
255 Settings
.__init
__(self
)
256 self
.session_id
= session_id
257 self
.values
.update({'repo': repo
})
260 def session_path(self
):
261 """The session-specific session file"""
262 return os
.path
.join(self
._sessions
_dir
, self
.session_id
)
265 base_path
= super(Session
, self
).path()
269 path
= self
.session_path()
270 if not os
.path
.exists(path
):
274 def load(self
, path
=None):
275 """Load the session and expire it for future loads
277 The session should be loaded only once. We remove the session file
278 when it's loaded, and set the session to be expired. This results in
279 future calls to load() and save() using the default Settings path
280 rather than the session-specific path.
282 The use case for sessions is when the user logs out with apps running.
283 We will restore their state, and if they then shutdown, it'll be just
284 like a normal shutdown and settings will be stored to
285 ~/.config/git-cola/settings instead of the session path.
287 This is accomplished by "expiring" the session after it has
288 been loaded initially.
291 result
= super(Session
, self
).load(path
=path
)
292 # This is the initial load, so expire the session and remove the
293 # session state file. Future calls will be equivalent to
297 path
= self
.session_path()
298 if core
.exists(path
):
301 except (OSError, ValueError):
309 """Reload settings from the base settings path"""
310 # This method does not expire the session.
311 path
= super(Session
, self
).path()
312 return super(Session
, self
).load(path
=path
)