/[gli]/branches/overhaul/src/fe/gtk/Partitioning.py
Gentoo

Contents of /branches/overhaul/src/fe/gtk/Partitioning.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1587 - (show annotations) (download) (as text)
Sat Jan 6 23:29:47 2007 UTC (7 years, 7 months ago) by agaffney
File MIME type: text/x-python
File size: 22830 byte(s)
gtkfe partititioning mods for new partitioning module
1 # Copyright 1999-2005 Gentoo Foundation
2 # This source code is distributed under the terms of version 2 of the GNU
3 # General Public License as published by the Free Software Foundation, a copy
4 # of which can be found in the main directory of this project.
5
6 import gtk
7 import GLIScreen
8 import Partitioning
9 from GLIException import *
10 import re
11 import PartitionButton
12 import PartProperties
13 from gettext import gettext as _
14
15 class Panel(GLIScreen.GLIScreen):
16
17 title = _("Partitioning")
18 part_buttons = {}
19 drives = []
20 devices = {}
21 active_device = ""
22 active_device_bytes_in_cylinder = 0
23 active_device_bytes_in_sector = 0
24 active_device_cylinders = 0
25 active_part_min_size = 0
26 active_part_max_size = 0
27 active_part_cur_size = 0
28 active_part_start_cyl = 0
29 active_part_minor = 0
30 colors = { 'ext2': '#0af2fe', 'ext3': '#0af2fe', 'unalloc': '#a2a2a2', 'unknown': '#ed03e0', 'free': '#ffffff', 'ntfs': '#f20600', 'fat16': '#3d07f9', 'fat32': '#3d07f9', 'reiserfs': '#f0ff00', 'linux-swap': '#12ff09', 'xfs': '#006600', 'jfs': '#ffb400', 'hfs': '#fdb0ff', 'hfs+': '#fdb0ff', 'apple_bootstrap': '#fdb0ff' }
31 supported_filesystems = ["ext2", "ext3", "linux-swap", "xfs", "jfs", "reiserfs", "fat16", "fat32", "ntfs", "hfs", "hfs+"]
32 _helptext = """
33 <b><u>Partitioning</u></b>
34
35 The choices you make on this screen are very important. There are 2 ways to do
36 your partitioning.
37
38 The first method is the Recommended Layout. If you really have no idea how you
39 should lay out your partitions, this is probably the best option. You will need
40 at least 4GB of concurrent disk space to use this option. If you have any
41 existing partitions, they will be kept intact. Three partitions will be created:
42 /boot (100MB), swap (calculated based on physical memory, up to 512MB), and
43 / (remaining concurrent space). The mountpoints and default mount options will
44 be automatically set for you. If you would like to mount any of your previously
45 existing partitions, you will need to edit those manually.
46
47 The second method is the old fashioned one: doing it yourself. The partition
48 edit is fairly straightforward (although, not <i>that</i> straightforward as
49 this help would not need to exist).
50
51 The currently active disk is represented by the bar near the top of the screen.
52 If you have more than one disk present in your system, you can change the
53 active disk by choosing another disk from the drop down box labeled 'Devices'.
54 Along the bottom of the screen, there is a color key for the disk representation
55 above.
56
57 To edit an existing partition, you must first select it by clicking it in the
58 bar above. You will get a brief overview of the partition below the bar. To edit
59 the partition's properties, click the button labeled 'Properties'. To delete it,
60 click the 'Delete' button. You will be asked for confirmation before the
61 partition is removed.
62
63 When you edit a partition's properties, you'll get a popup dialog with a few
64 options. If the installer is able to resize the partition, you will be able to
65 slide the bar at the top or enter a new size in the fields below the slider.
66 You can choose whether or not you want the partition to be formatted. If you
67 to keep the data, you should leave this as No. You can also choose a mountpoint
68 and mount options for the partition. If you don't enter a mountpoint, the
69 partition will not be mounted during the install and will not be added to your
70 /etc/fstab. Click 'OK' to save the changes you have made or 'Cancel' to leave it
71 as it was.
72
73 To create a new partition, select some unallocated space in the bar at the top.
74 The popup dialog that appears is the same one used for editing an existing
75 partition. The difference is that you will be able to select a partition type
76 (primary or logical for x86/amd64) and a filesystem type. If you select logical
77 for the type, an extended partition will be created automatically and take up
78 all of the remaining unallocated space. All partitions created after this will
79 automatically be logical. For a partition for use by Linux, you will want to use
80 ext2, ext3, reiserfs, jfs, or xfs. Ext3 is the recommended type.
81
82 Keep in mind that none of the changes to your partition table and filesystems
83 will be committed until you click the 'Install' button on the last screen of the
84 installer. Also, if your existing partitions are out of disk order, the
85 installer will reorder the partition numbers so that they match the physical
86 disk order.
87 """
88
89 def __init__(self, controller):
90 GLIScreen.GLIScreen.__init__(self, controller, show_title=True)
91
92 vert = gtk.VBox(False, 0)
93 vert.set_border_width(10)
94
95 content_str = _("""
96 On this screen, you will be presented with a list of detected partitionable devices. Selecting
97 a device will show you the current partitions on it (if any) and allow you to add, remove, and
98 resize partitions.""")
99
100 content_label = gtk.Label(content_str)
101 vert.pack_start(content_label, expand=False, fill=False, padding=0) # This was removed for screen space
102 container = gtk.HBox(False, 0)
103 detected_dev_label = gtk.Label(_("Devices:"))
104 container.pack_start(detected_dev_label, expand=False, fill=False, padding=0)
105 self.detected_dev_combo = gtk.combo_box_new_text()
106 self.detected_dev_combo.connect("changed", self.drive_changed)
107 container.pack_start(self.detected_dev_combo, expand=False, fill=False, padding=10)
108 self.part_button_recommended = gtk.Button(_(" Recommended layout "))
109 self.part_button_recommended.connect("clicked", self.part_button_recommended_clicked)
110 container.pack_end(self.part_button_recommended, expand=False, fill=False, padding=0)
111 self.part_button_clear = gtk.Button(_(" Clear partitions "))
112 self.part_button_clear.connect("clicked", self.part_button_clear_clicked)
113 container.pack_end(self.part_button_clear, expand=False, fill=False, padding=10)
114 self.part_button_dump_info = gtk.Button(_(" Dump to console (debug) "))
115 self.part_button_dump_info.connect("clicked", self.dump_part_info_to_console)
116 container.pack_end(self.part_button_dump_info, expand=False, fill=False, padding=0)
117
118 vert.pack_start(container, expand=False, fill=False, padding=10)
119
120 # This builds the container for the "whole disk" display at the top
121 part_table_frame = gtk.Frame()
122 part_table_frame.set_shadow_type(gtk.SHADOW_IN)
123 self.part_table = gtk.Table(1, 1, False)
124 self.part_table.set_size_request(-1, 40)
125 part_table_frame.add(self.part_table)
126 vert.pack_start(part_table_frame, expand=False, fill=False, padding=5)
127
128 # This builds the partition info box
129 self.part_info_box = gtk.HBox(False, 0)
130 part_info_table = gtk.Table(6, 2, False)
131 part_info_table.set_col_spacings(10)
132 info_partition_label = gtk.Label(_("Partition:"))
133 info_partition_label.set_alignment(0.0, 0.5)
134 self.info_partition = gtk.Label()
135 self.info_partition.set_alignment(0.0, 0.5)
136 info_type_label = gtk.Label(_("Type:"))
137 info_type_label.set_alignment(0.0, 0.5)
138 self.info_type = gtk.Label()
139 self.info_type.set_alignment(0.0, 0.5)
140 info_filesystem_label = gtk.Label(_("Filesystem:"))
141 info_filesystem_label.set_alignment(0.0, 0.5)
142 self.info_filesystem = gtk.Label()
143 self.info_filesystem.set_alignment(0.0, 0.5)
144 info_mountpoint_label = gtk.Label(_("Mountpoint:"))
145 info_mountpoint_label.set_alignment(0.0, 0.5)
146 self.info_mountpoint = gtk.Label()
147 self.info_mountpoint.set_alignment(0.0, 0.5)
148 info_size_label = gtk.Label(_("Size:"))
149 info_size_label.set_alignment(0.0, 0.5)
150 self.info_size = gtk.Label()
151 self.info_size.set_alignment(0.0, 0.5)
152 part_info_table.attach(info_partition_label, 0, 1, 0, 1)
153 part_info_table.attach(self.info_partition, 1, 2, 0, 1)
154 part_info_table.attach(info_type_label, 0, 1, 1, 2)
155 part_info_table.attach(self.info_type, 1, 2, 1, 2)
156 part_info_table.attach(info_filesystem_label, 0, 1, 2, 3)
157 part_info_table.attach(self.info_filesystem, 1, 2, 2, 3)
158 part_info_table.attach(info_mountpoint_label, 0, 1, 3, 4)
159 part_info_table.attach(self.info_mountpoint, 1, 2, 3, 4)
160
161 part_info_table.attach(info_size_label, 0, 1, 5, 6)
162 part_info_table.attach(self.info_size, 1, 2, 5, 6)
163 self.part_info_box.pack_start(part_info_table, expand=False, fill=False)
164 vert.pack_start(self.part_info_box, expand=False, fill=False, padding=10)
165
166 # This builds the row of buttons
167 self.part_button_box = gtk.HBox(False, 0)
168 self.part_button_delete = gtk.Button(_(" Delete "))
169 self.part_button_delete.connect("clicked", self.part_button_delete_clicked)
170 self.part_button_box.pack_start(self.part_button_delete, expand=False, fill=False, padding=0)
171 # self.part_button_recommended = gtk.Button(_(" Recommended "))
172 # self.part_button_recommended.connect("clicked", self.part_button_recommended_clicked)
173 # self.part_button_box.pack_start(self.part_button_recommended, expand=False, fill=False, padding=10)
174 self.part_button_properties = gtk.Button(_(" Properties "))
175 self.part_button_properties.connect("clicked", self.part_button_properties_clicked)
176 self.part_button_box.pack_start(self.part_button_properties, expand=False, fill=False, padding=10)
177 vert.pack_start(self.part_button_box, expand=False, fill=False, padding=10)
178
179 # This builds the color key at the bottom
180 color_codes = [ { 'label': "Swap", 'color': '#12ff09' },
181 { 'label': "Ext2/3", 'color': '#0af2fe' },
182 { 'label': "Reiserfs", 'color': '#f0ff00' },
183 { 'label': "JFS", 'color': '#ffb400' },
184 { 'label': "XFS", 'color': '#006600' },
185 { 'label': "FAT", 'color': '#3d07f9' },
186 { 'label': "NTFS", 'color': '#f20600' },
187 { 'label': "HFS", 'color': '#fdb0ff' },
188 { 'label': _("Other"), 'color': '#ed03e0' },
189 # { 'label': "Free space", 'color': '#ffffff' },
190 { 'label': _("Unallocated"), 'color': '#a2a2a2' }
191 ]
192 color_codes_box = gtk.HBox(False, 0)
193 vert.pack_end(color_codes_box, expand=False, fill=False, padding=2)
194 for color in color_codes:
195 temp_xpm = [ "12 12 2 1",
196 "B c #000000",
197 "C c " + color['color'],
198 "BBBBBBBBBBBB",
199 "BCCCCCCCCCCB",
200 "BCCCCCCCCCCB",
201 "BCCCCCCCCCCB",
202 "BCCCCCCCCCCB",
203 "BCCCCCCCCCCB",
204 "BCCCCCCCCCCB",
205 "BCCCCCCCCCCB",
206 "BCCCCCCCCCCB",
207 "BCCCCCCCCCCB",
208 "BCCCCCCCCCCB",
209 "BBBBBBBBBBBB"
210 ]
211 pixmap, mask = gtk.gdk.pixmap_create_from_xpm_d(self.controller.window.window, None, temp_xpm)
212 tmp_image = gtk.Image()
213 tmp_image.set_from_pixmap(pixmap, mask)
214 tmpbox = gtk.HBox(False, 0)
215 tmpbox.pack_start(tmp_image, expand=False, fill=False, padding=3)
216 tmpbox.pack_start(gtk.Label(color['label']), expand=False, fill=False, padding=3)
217 color_codes_box.pack_start(tmpbox, expand=False, fill=False, padding=6)
218
219 self.add_content(vert)
220 # self.detected_dev_combo.set_active(0)
221
222 def drive_changed(self, combobox, data=None):
223 self.active_device = self.drives[self.detected_dev_combo.get_active()]
224 self.draw_part_box()
225 self.active_device_cylinders = self.devices[self.active_device].get_num_cylinders()
226 self.active_device_bytes_in_cylinder = self.devices[self.active_device].get_cylinder_size()
227 self.active_device_bytes_in_sector = self.devices[self.active_device].get_sector_size()
228 self.info_partition.set_text("")
229 self.info_type.set_text("")
230 self.info_filesystem.set_text("")
231 self.info_mountpoint.set_text("")
232 # self.info_end.set_text("")
233 self.info_size.set_text("")
234 self.part_info_box.hide_all()
235 # self.resize_box.hide_all()
236 self.part_button_box.hide_all()
237 # self.part_mount_info_box.hide_all()
238
239 def part_selected(self, button, dev=None, minor=None):
240 minor = int(minor)
241 tmppart = self.devices[dev].get_partition(minor)
242 self.info_partition.set_text(tmppart['devnode'])
243 if self.devices[dev]._labelinfo['extended'] and minor > 4:
244 self.info_type.set_text(_("Logical"))
245 elif tmppart.get_type() == "extended":
246 self.info_type.set_text(_("Extended"))
247 else:
248 self.info_type.set_text(_("Primary"))
249 fstype = tmppart.get_type()
250 if fstype == "extended":
251 self.info_filesystem.set_text(_("N/A"))
252 else:
253 self.info_filesystem.set_text(fstype)
254 self.info_mountpoint.set_text(tmppart.get_mountpoint() or "none")
255 start = tmppart.get_start()
256 end = tmppart.get_end()
257 # self.info_start.set_text(str(start))
258 # self.info_end.set_text(str(end))
259 # part_size = int(round(float(self.devices[dev].get_sector_size()) * (end - start + 1) / 1024 / 1024))
260 part_size = int(tmppart.get_mb())
261 self.info_size.set_text(str(part_size) + _(" MB"))
262 self.active_part_minor = int(tmppart.get_minor())
263 self.part_button_delete.set_sensitive(True)
264 self.part_info_box.show_all()
265 self.part_button_box.show_all()
266
267 def unalloc_selected(self, button, dev=None, extended=False, mb=0, minor=0):
268 props = PartProperties.PartProperties(self, self.active_device, minor, 0, 0, mb, "free", self.active_device_bytes_in_sector)
269 props.run()
270
271 def part_button_delete_clicked(self, button, data=None):
272 msgdlg = gtk.MessageDialog(parent=self.controller.window, type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format=_("Are you sure you want to delete ") + self.devices[self.active_device][self.active_part_minor]['devnode'])
273 resp = msgdlg.run()
274 msgdlg.destroy()
275 if resp == gtk.RESPONSE_YES:
276 self.devices[self.active_device].remove_partition(self.active_part_minor)
277 if self.devices[self.active_device]._labelinfo['extended'] and self.active_part_minor > 4:
278 ext_part = self.devices[self.active_device].get_extended_partition()
279 if not len(self.devices[self.active_device].get_partition(ext_part).get_logicals()):
280 self.devices[self.active_device].remove_partition(ext_part)
281 self.drive_changed(None)
282
283 def part_button_recommended_clicked(self, button):
284 try:
285 self.devices[self.active_device].do_recommended()
286 except GLIException, error:
287 msgdlg = gtk.MessageDialog(parent=self.controller.window, type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_OK, message_format=error.get_error_msg())
288 msgdlg.run()
289 msgdlg.destroy()
290 self.draw_part_box()
291
292 def part_button_clear_clicked(self, button):
293 msgdlg = gtk.MessageDialog(parent=self.controller.window, type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format=_("Are you sure you wish to clear the partition table for " + self.active_device + "?"))
294 resp = msgdlg.run()
295 msgdlg.destroy()
296 if resp == gtk.RESPONSE_YES:
297 self.devices[self.active_device].clear_partitions()
298 self.drive_changed(self.detected_dev_combo)
299
300 def draw_part_box(self):
301 # partlist = self.devices[self.active_device].get_ordered_partition_list()
302 cylinders = self.devices[self.active_device].get_num_cylinders()
303 sectors = self.devices[self.active_device].get_num_sectors()
304 total_mb = self.devices[self.active_device].get_total_mb()
305 for button in self.part_buttons.keys():
306 self.part_table.remove(self.part_buttons[button])
307 self.part_table.resize(1, 100)
308 self.part_buttons = {}
309 last_percent = 0
310 last_log_percent = 0
311 extended_part = 0
312 extended_table = None
313 for part in self.devices[self.active_device]: #.get_partitions():
314 tmppart = self.devices[self.active_device][part]
315 if tmppart.get_type() == "free":
316 partsize = tmppart.get_mb()
317 percent = (float(partsize) / float(total_mb)) * 100
318 if percent < 1: percent = 1
319 percent = int(percent)
320 # print "minor: " + str(part) + ", mb: " + str(partsize) + ", percent: " + str(percent) + ", last_percent: " + str(last_percent)
321 if tmppart.is_logical():
322 tmpbutton = PartitionButton.Partition(color1=self.colors['unalloc'], color2=self.colors['unalloc'], label="", division=0)
323 tmpbutton.connect("clicked", self.unalloc_selected, self.active_device, False, partsize, tmppart.get_minor())
324 extended_table.attach(tmpbutton, last_log_percent, (last_log_percent + percent), 0, 1)
325 last_log_percent = last_log_percent + percent
326 else:
327 self.part_buttons['free_' + str(tmppart.get_minor())] = PartitionButton.Partition(color1=self.colors['unalloc'], color2=self.colors['unalloc'], label="", division=0)
328 if self.devices[self.active_device]._labelinfo['extended'] and self.devices[self.active_device].get_partition(1) and self.devices[self.active_device].get_partition(2) and self.devices[self.active_device].get_partition(3) and self.devices[self.active_device].get_partition(4):
329 self.part_buttons['free_' + str(tmppart.get_minor())].connect("clicked", self.show_no_more_primary_message)
330 else:
331 self.part_buttons['free_' + str(tmppart.get_minor())].connect("clicked", self.unalloc_selected, self.active_device, False, partsize, tmppart.get_minor())
332 self.part_table.attach(self.part_buttons['free_' + str(tmppart.get_minor())], last_percent, (last_percent + percent), 0, 1)
333 last_percent = last_percent + percent
334 else:
335 partsize = tmppart.get_mb()
336 percent = (float(partsize) / float(total_mb)) * 100
337 if percent < 1: percent = 1
338 percent = int(percent)
339 tmpminor = int(tmppart.get_minor())
340 tmpdevice = self.active_device
341 # print "minor: " + str(tmpminor) + ", mb: " + str(partsize) + ", percent: " + str(percent) + ", last_percent: " + str(last_percent)
342 if tmppart.is_extended():
343 extended_table = gtk.Table(1, percent)
344 extended_table.set_border_width(3)
345 extended_part = tmpminor
346 self.part_buttons[tmpminor] = extended_table
347 self.part_table.attach(self.part_buttons[tmpminor], last_percent, (last_percent + percent), 0, 1)
348 last_percent = last_percent + percent
349 elif tmppart.is_logical():
350 tmpbutton = PartitionButton.Partition(color1=self.colors[tmppart.get_type()], color2=self.colors[tmppart.get_type()], label="", division=0)
351 if percent >= 15:
352 tmpbutton.set_label(tmppart['devnode'])
353 extended_table.attach(tmpbutton, last_log_percent, (last_log_percent + percent), 0, 1)
354 last_log_percent = last_log_percent + percent
355 tmpbutton.connect("clicked", self.part_selected, tmpdevice, tmpminor)
356 else:
357 self.part_buttons[tmpminor] = PartitionButton.Partition(color1=self.colors[tmppart.get_type()], color2=self.colors[tmppart.get_type()], label="", division=0)
358 if percent >= 15:
359 self.part_buttons[tmpminor].set_label(tmppart['devnode'])
360 self.part_buttons[tmpminor].connect("clicked", self.part_selected, tmpdevice, tmpminor)
361 self.part_table.attach(self.part_buttons[tmpminor], last_percent, (last_percent + percent), 0, 1)
362 last_percent = last_percent + percent
363 self.part_table.show_all()
364
365 def show_no_more_primary_message(self, button, data=None):
366 msgdlg = gtk.MessageDialog(parent=self.controller.window, type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_OK, message_format=_("You cannot create more than 4 primary partitions. If you need more partitions, delete one or more and create logical partitions."))
367 msgdlg.run()
368 msgdlg.destroy()
369
370 def dump_part_info_to_console(self, button, data=None):
371 import pprint
372 pp = pprint.PrettyPrinter(indent=4)
373 pp.pprint(self.devices[self.active_device].get_install_profile_structure())
374
375 def part_button_properties_clicked(self, widget, data=None):
376 tmppart = self.devices[self.active_device].get_partition(self.active_part_minor)
377 props = PartProperties.PartProperties(self, self.active_device, self.active_part_minor, tmppart.get_mb(), tmppart.get_min_mb_for_resize(), tmppart.get_max_mb_for_resize(), tmppart.get_type(), self.active_device_bytes_in_sector, format=tmppart.get_format())
378 props.run()
379
380 def activate(self):
381 self.controller.SHOW_BUTTON_EXIT = True
382 self.controller.SHOW_BUTTON_HELP = True
383 self.controller.SHOW_BUTTON_BACK = False
384 self.controller.SHOW_BUTTON_FORWARD = True
385 self.controller.SHOW_BUTTON_FINISH = False
386
387 # self.devices = self.controller.install_profile.get_partition_tables()
388 # if not self.devices:
389 part_load_error = 0
390 tmp_drives = Partitioning.detect_devices()
391 tmp_drives.sort()
392 for drive in tmp_drives:
393 try:
394 self.devices[drive] = Partitioning.Device(drive, arch="x86")
395 # self.devices[drive].set_partitions_from_disk()
396 self.detected_dev_combo.append_text(drive)
397 self.drives.append(drive)
398 except:
399 print _("Exception received while loading partitions from device " + drive)
400 if self.devices.has_key(drive): del self.devices[drive]
401 part_load_error = 1
402 if part_load_error:
403 msgdlg = gtk.MessageDialog(parent=self.controller.window, type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_OK, message_format="There was an error loading one or more drives' partition table. Possible causes are running as a normal user or partitions being out of disk order.")
404 msgdlg.run()
405 msgdlg.destroy()
406 # return
407
408 if not self.drives:
409 tmp_drives = self.devices.keys()
410 tmp_drives.sort()
411 self.drives = tmp_drives
412 for drive in self.drives:
413 self.detected_dev_combo.append_text(drive)
414
415 if self.devices:
416 self.active_device = self.drives[0]
417 self.detected_dev_combo.set_active(0)
418 self.drive_changed(None)
419
420 # for device in self.devices:
421 # for tmppart in self.devices[device].get_partitions():
422 # if tmppart.get_type() == "unknown":
423 # msgdlg = gtk.MessageDialog(parent=self.controller.window, type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_OK, message_format="One of your disks (%s) contains a partition (minor %s) of a type that the installer cannot currently handle. If you continue, you risk damaging your partition table. You have been warned!" % (device, str(tmppart.get_minor())))
424 # msgdlg.run()
425 # msgdlg.destroy()
426 # return
427
428 def deactivate(self):
429 parts_tmp = {}
430 for device in self.devices.keys():
431 parts_tmp[device] = self.devices[device].get_install_profile_structure()
432 for device in parts_tmp:
433 for part in parts_tmp[device]:
434 if part['mountpoint'] == "/":
435 self.controller.install_profile.set_partition_tables(self.devices)
436 return True
437 msgdlg = gtk.MessageDialog(parent=self.controller.window, type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format=_("You have not specified a partition to mount as /. Do you want to continue?"))
438 resp = msgdlg.run()
439 msgdlg.destroy()
440 if resp == gtk.RESPONSE_YES:
441 self.controller.install_profile.set_partition_tables(self.devices)
442 return True
443 else:
444 return False

Properties

Name Value
svn:eol-style native

  ViewVC Help
Powered by ViewVC 1.1.20