2 # -*- coding: utf-8 -*-
6 # Copyright © 2013-2018 Antergos
8 # This file is part of Cnchi.
10 # Cnchi is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or
13 # (at your option) any later version.
15 # Cnchi is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # The following additional terms are in effect as per Section 7 of the license:
22 # The preservation of all legal notices and author attributions in
23 # the material or in the Appropriate Legal Notices displayed
24 # by works containing it is required.
26 # You should have received a copy of the GNU General Public License
27 # along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
30 """ Let advanced users manage mirrorlist files """
33 import multiprocessing
37 gi.require_version('Gtk', '3.0')
38 from gi.repository import Gtk, Gdk, GObject
41 gi.require_foreign("cairo")
43 print("No pycairo integration")
46 from pages.gtkbasebox import GtkBaseBox
47 from rank_mirrors import RankMirrors
49 if __name__ == '__main__':
51 sys.path.append('/usr/share/cnchi/src')
53 # When testing, no _() is available
56 except NameError as err:
61 class MirrorListBoxRow(Gtk.ListBoxRow):
62 """ Represents a mirror """
63 def __init__(self, url, active, switch_cb, drag_cbs):
64 super(Gtk.ListBoxRow, self).__init__()
66 # self.add(Gtk.Label(data))
70 box = Gtk.Box(spacing=20)
72 self.handle = Gtk.EventBox.new()
73 self.handle.add(Gtk.Image.new_from_icon_name("open-menu-symbolic", 1))
74 box.pack_start(self.handle, False, False, 0)
76 # Add mirror url label
77 self.label = Gtk.Label.new()
78 self.label.set_halign(Gtk.Align.START)
79 self.label.set_justify(Gtk.Justification.LEFT)
80 self.label.set_name(url)
81 # Only show site address
82 url_parts = url.split('/')
83 text_url = url_parts[0] + "//" + url_parts[2]
84 self.label.set_text(text_url)
85 box.pack_start(self.label, False, True, 0)
88 self.switch = Gtk.Switch.new()
89 self.switch.set_name("switch_" + url)
90 self.switch.set_property('margin_top', 2)
91 self.switch.set_property('margin_bottom', 2)
92 self.switch.set_property('margin_end', 10)
93 self.switch.connect("notify::active", switch_cb)
94 self.switch.set_active(active)
95 box.pack_end(self.switch, False, False, 0)
99 self.set_selectable(True)
103 self.handle.drag_source_set(
104 Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.MOVE)
105 self.handle.drag_source_add_text_targets()
106 self.handle.connect("drag-begin", drag_cbs['drag-begin'])
107 self.handle.connect("drag-data-get", drag_cbs['drag-data-get'])
108 #self.handle.connect("drag-data-delete", self.drag_data_delete)
109 #self.handle.connect("drag-end", self.drag_end)
112 self.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.MOVE)
113 self.drag_dest_add_text_targets()
114 self.connect("drag-data-received", drag_cbs['drag-data-received'])
115 #self.connect("drag-motion", self.drag_motion);
116 #self.connect("drag-crop", self.drag_crop);
119 """ Returns if the mirror is active """
120 return self.switch.get_active()
123 class MirrorListBox(Gtk.ListBox):
124 """ List that stores all mirrors """
126 'switch-activated': (GObject.SignalFlags.RUN_FIRST, None, ())
129 # 6 mirrors for Arch repos and 6 for Antergos repos
131 # DND_ID_LISTBOX_ROW = 6791
133 def __init__(self, mirrors_file_path, settings):
134 super(Gtk.ListBox, self).__init__()
135 self.mirrors_file_path = mirrors_file_path
136 self.set_selection_mode(Gtk.SelectionMode.NONE)
137 # self.set_selection_mode(Gtk.SelectionMode.BROWSE)
138 # self.connect("row-selected", self.on_listbox_row_selected)
139 # self.sort_func(self.listbox_sort_by_name, None)
141 self.settings = settings
143 # List. Each element is a tuple (url, active)
150 def uncommented_mirrors_first(mirrors):
151 """ Put uncommented mirrors first. Order is not guaranteed """
154 for mirror in mirrors:
155 if mirror.startswith("#"):
156 commented.append(mirror)
158 uncommented.append(mirror)
159 return uncommented + commented
161 def load_mirrors(self):
162 """ Load mirrors from text file """
165 # Load mirror file contents
166 with open(self.mirrors_file_path) as mfile:
167 lines = mfile.readlines()
169 # Discard lines that are not server lines
172 for line in tmp_lines:
174 if line.startswith("Server") or line.startswith("#Server"):
178 lines = self.uncommented_mirrors_first(lines)
180 # Use MAX_MIRRORS at max
181 if len(lines) > MirrorListBox.MAX_MIRRORS:
182 lines = lines[0:MirrorListBox.MAX_MIRRORS]
184 # Read mirror info and create mirrors list
186 if line.startswith("#Server"):
193 url = line.split("=")[1].strip()
195 self.mirrors.append((url, active))
200 """ Fill listbox with mirrors info """
201 for listboxrow in self.get_children():
205 'drag-begin': self.drag_begin,
206 'drag-data-get': self.drag_data_get,
207 'drag-data-received': self.drag_data_received
210 for (url, active) in self.mirrors:
211 box = Gtk.Box(spacing=20)
213 row = MirrorListBoxRow(url, active, self.switch_activated, drag_cbs)
216 def set_mirror_active(self, url, active):
217 """ Changes the active status in our mirrors list """
218 for index, item in enumerate(self.mirrors):
221 self.mirrors[index] = (url, active)
223 def get_active_mirrors(self):
224 """ Returns a list with all active mirrors """
226 for (url, active) in self.mirrors:
228 active_mirrors.append(url)
229 return active_mirrors
231 def switch_activated(self, switch, _gparam):
232 """ Mirror activated """
233 row = switch.get_ancestor(Gtk.ListBoxRow)
235 self.set_mirror_active(row.data, switch.get_active())
236 self.emit("switch-activated")
238 def drag_begin(self, widget, drag_context):
239 """ User starts a drag """
240 row = widget.get_ancestor(Gtk.ListBoxRow)
241 alloc = row.get_allocation()
242 surface = cairo.ImageSurface(
243 cairo.FORMAT_ARGB32, alloc.width, alloc.height)
244 ctx = cairo.Context(surface)
246 row.get_style_context().add_class("drag-icon")
248 row.get_style_context().remove_class("drag-icon")
250 pos_x, pos_y = widget.translate_coordinates(row, 0, 0)
252 surface.set_device_offset(-pos_x, -pos_y)
253 Gtk.drag_set_icon_surface(drag_context, surface)
255 hand_cursor = Gdk.Cursor(Gdk.CursorType.HAND1)
256 self.get_window().set_cursor(hand_cursor)
258 def drag_data_get(self, widget, _drag_context, selection_data, _info, _time):
259 """ When drag data is requested by the destination """
260 row = widget.get_ancestor(Gtk.ListBoxRow)
261 listbox_str = str(self)
262 row_index = row.get_index()
263 data = "{0}|{1}".format(listbox_str, row_index)
264 selection_data.set_text(data, len(data))
265 self.get_window().set_cursor(None)
267 def drag_data_received(
268 self, widget, _drag_context, _pos_x, _pos_y, selection_data, _info, _time):
269 """ When drag data is received by the destination """
270 data = selection_data.get_text()
272 listbox_str = data.split('|')[0]
273 if listbox_str == str(self):
274 old_index = int(data.split('|')[1])
275 new_index = widget.get_index()
276 self.mirrors.insert(new_index, self.mirrors.pop(old_index))
279 except (KeyError, ValueError) as err:
283 def trim_mirror_url(server_line):
284 """ Get url from full mirrorlist line """
286 if not server_line.startswith("Server") or '=' not in server_line:
290 url = server_line.split('=')[1].strip()
292 # Remove end part to get the FDQN only
293 url = url.replace("/$repo/os/$arch", "")
297 def save_changes(self, use_rankmirrors=False):
298 """ Save mirrors in mirrors list file """
299 # Save a backup if possible
300 src = self.mirrors_file_path
301 dst = src + ".cnchi-backup"
304 shutil.copy2(src, dst)
305 except (FileNotFoundError, FileExistsError, OSError) as err:
308 # ok, now save our changes
309 with open(src, 'w') as mfile:
310 line = "# Mirrorlist file modified by Cnchi\n\n"
312 for (url, active) in self.mirrors:
313 line = "Server = {}\n".format(url)
318 # If this is the Arch mirrorlist and we are not using rankmirrors,
319 # we need to save it to rankmirrors_result in settings
321 if not use_rankmirrors and self.mirrors_file_path == "/etc/pacman.d/mirrorlist":
322 for (url, active) in self.mirrors:
324 arch_mirrors.append(self.trim_mirror_url(url))
325 self.settings.set('rankmirrors_result', arch_mirrors)
328 class Mirrors(GtkBaseBox):
329 """ Page that shows mirrolists so the user can arrange them manually """
330 """ Remove antergos-mirrorlist, and add reborn-mirrorlist (Rafael) """
333 "/etc/pacman.d/mirrorlist",
334 "/etc/pacman.d/reborn-mirrorlist"]
336 def __init__(self, params, prev_page="cache", next_page="installation_ask"):
337 super().__init__(self, params, "mirrors", prev_page, next_page)
341 self.scrolledwindows = []
343 self.scrolledwindows.append(self.gui.get_object("scrolledwindow1"))
344 self.scrolledwindows.append(self.gui.get_object("scrolledwindow2"))
346 for mirror_file in Mirrors.MIRRORLISTS:
347 mirror_listbox = MirrorListBox(mirror_file, self.settings)
348 mirror_listbox.connect("switch-activated", self.switch_activated)
349 self.listboxes.append(mirror_listbox)
351 for index, scrolled_window in enumerate(self.scrolledwindows):
352 scrolled_window.add(self.listboxes[index])
354 self.listboxes_box = self.gui.get_object("listboxes_box")
356 self.use_rankmirrors = True
357 self.use_listboxes = False
359 # Boolean variable to check if rank_mirrors has already been run
360 self.rank_mirrors_launched = False
362 def switch_activated(self, _widget):
363 """ A mirror has been activated/deactivated. We must check if
364 at least there is one mirror active for each list """
365 self.check_active_mirrors()
367 def check_active_mirrors(self):
368 """ Checks if at least there is one mirror active for each list """
370 for listbox in self.listboxes:
371 if not listbox.get_active_mirrors():
372 ok_to_proceed = False
373 self.forward_button.set_sensitive(ok_to_proceed)
375 def rank_radiobutton_toggled(self, _widget):
376 """ Use rankmirrors """
377 self.use_rankmirrors = True
378 self.use_listboxes = False
379 self.forward_button.set_sensitive(True)
380 # self.listboxes_box.hide()
381 self.listboxes_box.set_sensitive(False)
383 def leave_radiobutton_toggled(self, _widget):
384 """ Do not modify mirror lists """
385 self.use_rankmirrors = False
386 self.use_listboxes = False
387 self.forward_button.set_sensitive(True)
388 # self.listboxes_box.hide()
389 self.listboxes_box.set_sensitive(False)
391 def user_radiobutton_toggled(self, _widget):
392 """ Let user choose mirrorlist ordering """
393 self.use_rankmirrors = False
394 self.use_listboxes = True
396 self.check_active_mirrors()
397 self.listboxes_box.set_sensitive(True)
399 def start_rank_mirrors(self):
400 """ Launch rank mirrors process to optimize Arch and Antergos mirrorlists
401 As user can come and go from/to this screen, we must get sure he/she
402 has not already run Rankmirrors before """
404 if not self.rank_mirrors_launched:
405 logging.debug("Cnchi is ranking your mirrors lists...")
406 parent_conn, child_conn = multiprocessing.Pipe(duplex=False)
407 # Store parent_conn for later use in summary.py (rankmirrors wait dialog)
408 #proc = RankMirrors(fraction_pipe=child_conn)
409 proc = RankMirrors(child_conn, self.settings)
411 proc.name = "rankmirrors"
413 self.rank_mirrors_launched = True
414 self.settings.set('rankmirrors_pipe', parent_conn)
416 def prepare(self, direction):
417 """ Prepares screen """
420 # self.listboxes_box.hide()
421 self.listboxes_box.set_sensitive(False)
422 self.forward_button.set_sensitive(True)
424 def translate_ui(self):
425 """ Translates screen before showing it """
426 self.header.set_subtitle(_("Mirrors Selection"))
428 self.forward_button.set_always_show_image(True)
429 self.forward_button.set_sensitive(True)
431 #bold_style = '<span weight="bold">{0}</span>'
433 radio = self.gui.get_object("rank_radiobutton")
434 txt = _("Let Cnchi sort the mirrors lists (recommended)")
436 radio.set_name('rank_radio_btn')
438 radio = self.gui.get_object("leave_radiobutton")
439 txt = _("Leave the mirrors lists as they are")
441 radio.set_name('leave_radio_btn')
443 radio = self.gui.get_object("user_radiobutton")
444 txt = _("Manage the mirrors lists manually (advanced)")
446 radio.set_name('user_radio_btn')
448 intro_txt = _("How would you like to proceed?")
449 intro_label = self.gui.get_object("introduction")
450 intro_label.set_text(intro_txt)
451 intro_label.set_name("intro_label")
452 intro_label.set_hexpand(False)
453 intro_label.set_line_wrap(True)
455 intro_label.set_max_width_chars(80)
457 lbl = self.gui.get_object("arch_mirrors_label")
458 lbl.set_text(_("Arch Mirrors"))
460 lbl = self.gui.get_object("antergos_mirrors_label")
461 lbl.set_text(_("RebornOS Mirrors"))
463 def store_values(self):
464 """ Store selected values """
465 if self.use_rankmirrors:
466 self.start_rank_mirrors()
467 if self.use_listboxes:
468 for listbox in self.listboxes:
469 listbox.save_changes(self.use_rankmirrors)
472 def get_next_page(self):
473 """ Returns next page """
474 return self.next_page