Coverage for src/hods/tui/delta.py: 100.00%

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

80 statements  

1"""hods - home directory synchronization. 

2 

3Copyright (C) 2016-2020 Mathias Stelzer <knoppo@rolln.de> 

4 

5hods is free software: you can redistribute it and/or modify 

6it under the terms of the GNU General Public License as published by 

7the Free Software Foundation, either version 3 of the License, or 

8(at your option) any later version. 

9 

10hods is distributed in the hope that it will be useful, 

11but WITHOUT ANY WARRANTY; without even the implied warranty of 

12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

13GNU General Public License for more details. 

14 

15You should have received a copy of the GNU General Public License 

16along with this program. If not, see <http://www.gnu.org/licenses/>. 

17""" 

18import logging 

19import subprocess 

20 

21import urwid 

22 

23from hods.delta import ApplyError, Delta, DeltaBuilder, Template 

24from hods.delta import DeltaDirectory, Symlink 

25from hods.tui.base.list import AppItemMixin, button_list 

26from hods.tui.base.view import ErrorWindow, ProcessErrorWindow, SuccessWindow, Window 

27from hods.tui.config.tree import AppTreeDecoration, PathTree, PathTreeBox, PathTreeItem 

28 

29logger = logging.getLogger(__name__) 

30 

31 

32class BaseDeltaTreeItem(PathTreeItem): 

33 """A base tree item used in a home delta tree.""" 

34 

35 default_attr = 'body' 

36 

37 def get_attr(self): 

38 """Return an urwid attribute for the paths action.""" 

39 action = self.object.get_action() 

40 if action is None: 

41 return self.default_attr 

42 if action == self.object.create: 

43 return 'success' 

44 if action == self.object.replace: 

45 return 'warning' 

46 if action == self.object.delete: 

47 return 'error' 

48 return super().get_attr() # pragma: no cover 

49 

50 def get_label(self): 

51 """Return the path basename and its action as label.""" 

52 label = self.object.basename 

53 action = self.object.get_action_label() 

54 if action: 

55 label += ' ({})'.format(action) 

56 return label 

57 

58 

59class DeltaItem(PathTreeItem): 

60 """The tree item for the delta root node.""" 

61 

62 def get_attr(self): 

63 """Return the `urwid` attribute map to style the widget.""" 

64 return 'directory' 

65 

66 def get_label(self): 

67 """Return fixed ~/ as label for the home directory.""" 

68 return '~/' 

69 

70 

71class DirectoryItem(BaseDeltaTreeItem): 

72 """A delta tree item to display a directory.""" 

73 

74 default_attr = 'directory' 

75 

76 

77class SymlinkItem(AppItemMixin, BaseDeltaTreeItem): 

78 """A delta tree item to display a symlink.""" 

79 

80 default_attr = 'symlink' 

81 

82 

83class TemplateItem(AppItemMixin, BaseDeltaTreeItem): 

84 """A delta tree item to display a symlink.""" 

85 

86 default_attr = 'template' 

87 

88 

89class DeltaTreeDecoration(AppTreeDecoration): 

90 """A tree decoration to display a home delta tree.""" 

91 

92 def __getitem__(self, item): 

93 """Return the according tree widget for the given item.""" 

94 if isinstance(item, Delta): 

95 return DeltaItem(item) 

96 if isinstance(item, DeltaDirectory): 

97 return DirectoryItem(item) 

98 if isinstance(item, Symlink): 

99 return SymlinkItem(self.app, item) 

100 if isinstance(item, Template): 

101 return TemplateItem(self.app, item) 

102 return super().__getitem__(item) # pragma: no cover 

103 

104 

105class DeltaWindow(Window): 

106 """Window to show local changes.""" 

107 

108 def __init__(self, app, **kwargs): 

109 """Initialize window. 

110 

111 :param app: `hods.__main__.TUI` instance 

112 :param kwargs: passed to `hods.tui.base.windows.Window` 

113 """ 

114 self.delta = DeltaBuilder(app.config).build() 

115 tree = PathTree(app, self.delta) 

116 decorated_tree = DeltaTreeDecoration(app, tree) 

117 self.treebox = PathTreeBox(decorated_tree) 

118 

119 super().__init__(app, title='Update Summary', padding=0, **kwargs) 

120 

121 def get_body(self): 

122 """Get main widget to display.""" 

123 return urwid.Columns([ 

124 urwid.AttrMap(self.treebox, 'body'), 

125 button_list([ 

126 (urwid.Button('Update', on_press=self.update), 'success'), 

127 (urwid.Button('Cancel', on_press=lambda btn: self.close()), 'warning'), 

128 ], column=True), 

129 ], dividechars=1) 

130 

131 def update(self, btn): 

132 """Apply the delta to the home directory and close the window.""" 

133 for feature in self.app.config.tree.installed_features: 

134 try: 

135 with self.app.pause_loop(): 

136 feature.hooks.update.pre.run() 

137 except subprocess.CalledProcessError as e: 

138 ProcessErrorWindow(self.app, e, '{} pre-hook'.format(feature.name), parent=self).show() 

139 return 

140 

141 try: 

142 self.delta.apply() 

143 except ApplyError as e: 

144 self.treebox.refresh() 

145 msg = 'Failed to {} "{}":\n{}\nFix manually and update again'.format(e.action, e.node.path, e.message) 

146 ErrorWindow(self.app, msg, parent=self).show() 

147 return 

148 

149 for feature in self.app.config.tree.installed_features: 

150 try: 

151 with self.app.pause_loop(): 

152 feature.hooks.update.post.run() 

153 except subprocess.CalledProcessError as e: 

154 ProcessErrorWindow(self.app, e, '{} post-hook'.format(feature.name), parent=self).show() 

155 return 

156 

157 SuccessWindow(self.app, 'Successfully updated your home directory!').show()