Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
path: root/archinstall/default_profiles/custom.py
blob: f7e100ed65ee36bddb1a1dd19482484c3ef2884b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
from typing import List, Dict, Optional, TYPE_CHECKING, Any

from ..lib import menu
from archinstall.lib.output import log, FormattedOutput
from archinstall.lib.profile.profiles_handler import profile_handler
from archinstall.default_profiles.profile import Profile, ProfileType, SelectResult, ProfileInfo, TProfile

if TYPE_CHECKING:
	from archinstall.lib.installer import Installer
	_: Any


class CustomProfileList(menu.ListManager):
	def __init__(self, prompt: str, profiles: List[TProfile]):
		self._actions = [
			str(_('Add profile')),
			str(_('Edit profile')),
			str(_('Delete profile'))
		]
		super().__init__(prompt, profiles, [self._actions[0]], self._actions[1:])

	def reformat(self, data: List[TProfile]) -> Dict[str, Optional[TProfile]]:
		table = FormattedOutput.as_table(data)
		rows = table.split('\n')

		# these are the header rows of the table and do not map to any profile obviously
		# we're adding 2 spaces as prefix because the menu selector '> ' will be put before
		# the selectable rows so the header has to be aligned
		display_data: Dict[str, Optional[TProfile]] = {f'  {rows[0]}': None, f'  {rows[1]}': None}

		for row, profile in zip(rows[2:], data):
			row = row.replace('|', '\\|')
			display_data[row] = profile

		return display_data

	def selected_action_display(self, profile: TProfile) -> str:
		return profile.name

	def handle_action(
		self,
		action: str,
		entry: Optional['CustomTypeProfile'],
		data: List['CustomTypeProfile']
	) -> List['CustomTypeProfile']:
		if action == self._actions[0]:  # add
			new_profile = self._add_profile()
			if new_profile is not None:
				# in case a profile with the same name as an existing profile
				# was created we'll replace the existing one
				data = [d for d in data if d.name != new_profile.name]
				data += [new_profile]
		elif entry is not None:
			if action == self._actions[1]:  # edit
				new_profile = self._add_profile(entry)
				if new_profile is not None:
					# we'll remove the original profile and add the modified version
					data = [d for d in data if d.name != entry.name and d.name != new_profile.name]
					data += [new_profile]
			elif action == self._actions[2]:  # delete
				data = [d for d in data if d != entry]

		return data

	def _is_new_profile_name(self, name: str) -> bool:
		existing_profile = profile_handler.get_profile_by_name(name)
		if existing_profile is not None and existing_profile.profile_type != ProfileType.CustomType:
			return False
		return True

	def _add_profile(self, editing: Optional['CustomTypeProfile'] = None) -> Optional['CustomTypeProfile']:
		name_prompt = '\n\n' + str(_('Profile name: '))

		while True:
			profile_name = menu.TextInput(name_prompt, editing.name if editing else '').run().strip()

			if not profile_name:
				return None

			if not self._is_new_profile_name(profile_name):
				error_prompt = str(_("The profile name you entered is already in use. Try again"))
				print(error_prompt)
			else:
				break

		packages_prompt = str(_('Packages to be install with this profile (space separated, leave blank to skip): '))
		edit_packages = ' '.join(editing.packages) if editing else ''
		packages = menu.TextInput(packages_prompt, edit_packages).run().strip()

		services_prompt = str(_('Services to be enabled with this profile (space separated, leave blank to skip): '))
		edit_services = ' '.join(editing.services) if editing else ''
		services = menu.TextInput(services_prompt, edit_services).run().strip()

		choice = menu.Menu(
			str(_('Should this profile be enabled for installation?')),
			menu.Menu.yes_no(),
			skip=False,
			default_option=menu.Menu.no(),
			clear_screen=False,
			show_search_hint=False
		).run()

		enable_profile = True if choice.value == menu.Menu.yes() else False

		profile = CustomTypeProfile(
			profile_name,
			enabled=enable_profile,
			packages=packages.split(' '),
			services=services.split(' ')
		)

		return profile


# TODO
# Still needs some ironing out
class CustomProfile():
	def __init__(self):
		super().__init__(
			'Custom',
			ProfileType.Custom,
			description=str(_('Create your own'))
		)

	def json(self) -> Dict[str, Any]:
		data: Dict[str, Any] = {'main': self.name, 'gfx_driver': self.gfx_driver, 'custom': []}

		for profile in self._current_selection:
			data['custom'].append({
				'name': profile.name,
				'packages': profile.packages,
				'services': profile.services,
				'enabled': profile.custom_enabled
			})

		return data

	def do_on_select(self) -> SelectResult:
		custom_profile_list = CustomProfileList('', profile_handler.get_custom_profiles())
		custom_profiles = custom_profile_list.run()

		# we'll first remove existing custom default_profiles with
		# the same name and then add the new ones this
		# will avoid errors of default_profiles with duplicate naming
		profile_handler.remove_custom_profiles(custom_profiles)
		profile_handler.add_custom_profiles(custom_profiles)

		self.set_current_selection(custom_profiles)

		if custom_profile_list.is_last_choice_cancel():
			return SelectResult.SameSelection

		enabled_profiles = [p for p in self._current_selection if p.custom_enabled]
		# in  case we only created inactive default_profiles we wanna store them but
		# we want to reset the original setting
		if not enabled_profiles:
			return SelectResult.ResetCurrent

		return SelectResult.NewSelection

	def post_install(self, install_session: 'Installer'):
		for profile in self._current_selection:
			profile.post_install(install_session)

	def install(self, install_session: 'Installer'):
		driver_packages = self.gfx_driver_packages()
		install_session.add_additional_packages(driver_packages)

		for profile in self._current_selection:
			if profile.custom_enabled:
				log(f'Installing custom profile {profile.name}...')

				install_session.add_additional_packages(profile.packages)
				install_session.enable_service(profile.services)

				profile.install(install_session)

	def info(self) -> Optional[ProfileInfo]:
		enabled_profiles = [p for p in self._current_selection if p.custom_enabled]
		if enabled_profiles:
			details = ', '.join([p.name for p in enabled_profiles])
			gfx_driver = self.gfx_driver
			return ProfileInfo(self.name, details, gfx_driver)

		return None

	def reset(self):
		for profile in self._current_selection:
			profile.set_enabled(False)

		self.gfx_driver = None


class CustomTypeProfile(Profile):
	def __init__(
		self,
		name: str,
		enabled: bool = False,
		packages: List[str] = [],
		services: List[str] = []
	):
		super().__init__(
			name,
			ProfileType.CustomType,
			packages=packages,
			services=services,
			support_gfx_driver=True
		)

		self.custom_enabled = enabled

	def json(self) -> Dict[str, Any]:
		return {
			'name': self.name,
			'packages': self.packages,
			'services': self.services,
			'enabled': self.custom_enabled
		}