~devel-sumpfralle/virtualbrick/nitpicking

« back to all changes in this revision

Viewing changes to virtualbricks/gui/dialogs.py

  • Committer: Marco Giusti
  • Date: 2013-06-18 17:22:17 UTC
  • Revision ID: marco.giusti@gmail.com-20130618172217-17cuhx5e3pwmw2c3
Ignore all trial temp directories

Show diffs side-by-side

added added

removed removed

Lines of Context:
79
79
"""
80
80
 
81
81
import os
82
 
import sys
83
 
import errno
84
82
import tempfile
85
 
import functools
86
83
 
87
 
import pango
88
84
import gtk
89
 
from twisted.internet import utils, defer, task, error
90
 
from twisted.python import filepath
 
85
from twisted.internet import utils
91
86
 
92
 
from virtualbricks import (version, tools, log, console, settings,
93
 
                           virtualmachines, project)
 
87
from virtualbricks import version, tools, _compat, console
94
88
from virtualbricks.gui import graphics
95
89
 
96
90
 
97
 
if False:  # pyflakes
98
 
    _ = str
99
 
 
100
 
logger = log.Logger()
101
 
bug_send = log.Event("Sending report bug")
102
 
bug_sent = log.Event("Report bug sent succefully")
103
 
bug_error = log.Event("{err}\nstderr:\n{stderr}")
104
 
bug_report_fail = log.Event("Report bug failed with code "
105
 
                            "{code}\nstderr:\n{stderr}")
106
 
img_cannot_remove = log.Event("Cannot remove image {img}")
107
 
bug_err_unknown = log.Event("Error on bug reporting")
108
 
lsusb_out = log.Event("lsusb output:\n{out}")
109
 
dev_found = log.Event("found {dev}")
110
 
perm_error = log.Event("Cannot access /dev/bus/usb. Check user privileges.")
111
 
invalid_mac = log.Event("MAC address {mac} is not valid, generating "
112
 
                        "a random one")
113
 
not_implemented = log.Event("Not implemented")
114
 
event_in_use = log.Event("Cannot rename event: it is in use.")
115
 
brick_in_use = log.Event("Cannot rename brick: it is in use.")
116
 
event_created = log.Event("Event created successfully")
117
 
commit_failed = log.Event("Failed to commit image\n{err}")
118
 
img_invalid = log.Event("Invalid image")
119
 
base_not_found = log.Event("Base not found (invalid cow?)\nstderr:\n{err}")
120
 
img_combo = log.Event("Setting image for combobox")
121
 
img_create_err = log.Event("Error on creating image")
122
 
img_create = log.Event("Creating image...")
123
 
img_choose = log.Event("Choose a filename first!")
124
 
img_invalid_type = log.Event("Invalid value for format combo, assuming raw")
125
 
img_invalid_unit = log.Event("Invalid value for unit combo, assuming Mb")
126
 
import_cancelled = log.Event("Import cancelled")
127
 
import_prj_exists = log.Event("Cannot import project {name} a project with "
128
 
                              "the same name exists.")
129
 
import_invalid_name = log.Event("Invalid project name {name}")
130
 
extract_err = log.Event("Error on import project")
131
 
remap_canceled = log.Event("Remap canceled by the user.")
132
 
remap_completed = log.Event("Remap finished.")
133
 
log_rebase = log.Event("Rebasing {cow} to {basefile}")
134
 
rebase_error = log.Event("Error on rebase")
135
 
image_not_exists = log.Event("Cannot save image to {destination}, file does "
136
 
                             "not exists: {source}")
137
 
invalid_step_assitant = log.Event("Assistant cannot handle step {num}")
138
 
project_extracted = log.Event("Project has beed extracted in {path}")
139
 
removing_temporary_project = log.Event("Remove temporary files in {path}")
140
 
error_on_import_project = log.Event("An error occurred while import project")
141
 
 
 
91
log = _compat.getLogger(__name__)
142
92
NUMERIC = set(map(str, range(10)))
143
93
NUMPAD = set(map(lambda i: "KP_%d" % i, range(10)))
144
94
EXTRA = set(["BackSpace", "Delete", "Left", "Right", "Home", "End", "Tab"])
145
95
VALIDKEY = NUMERIC | NUMPAD | EXTRA
146
96
 
 
97
 
 
98
if False:  # pyflakes
 
99
    _ = str
 
100
 
 
101
 
147
102
BUG_REPORT_ERRORS = {
148
103
    1: "Error in command line syntax.",
149
104
    2: "One of the files passed on the command line did not exist.",
158
113
"""
159
114
 
160
115
 
161
 
def destroy_on_exit(func):
162
 
    @functools.wraps(func)
163
 
    def on_response(self, dialog, response_id):
164
 
        try:
165
 
            func(self, dialog, response_id)
166
 
        finally:
167
 
            dialog.destroy()
168
 
    return on_response
169
 
 
170
 
 
171
116
class Base(object):
172
117
    """Base class to work with gtkbuilder files.
173
118
 
209
154
class Window(Base):
210
155
    """Base class for all dialogs."""
211
156
 
212
 
    on_destroy = None
213
 
 
214
157
    @property
215
158
    def window(self):
216
159
        return self.widget
217
160
 
218
 
    def set_transient_for(self, parent):
219
 
        self.window.set_transient_for(parent)
220
 
 
221
 
    def show(self, parent=None):
222
 
        if parent is not None:
223
 
            self.window.set_transient_for(parent)
224
 
        if self.on_destroy is not None:
225
 
            self.window.connect("destroy", lambda w: self.on_destroy())
226
 
        self.window.show()
 
161
    def show(self):
 
162
        self.widget.show()
227
163
 
228
164
 
229
165
class AboutDialog(Window):
245
181
        Window.__init__(self)
246
182
        self.textbuffer = textbuffer
247
183
        self.__bottom = True
248
 
        textview = self.get_object("textview")
 
184
        textview = self.get_object("textview1")
249
185
        textview.set_buffer(textbuffer)
250
186
        self.__insert_text_h = textbuffer.connect("changed",
251
187
                self.on_textbuffer_changed, textview)
286
222
        try:
287
223
            if response_id == gtk.RESPONSE_OK:
288
224
                with open(dialog.get_filename(), "w") as fp:
289
 
                    fp.write(self.textbuffer.get_property("text"))
 
225
                    self.save_to(fp)
290
226
        finally:
291
227
            dialog.destroy()
292
228
 
293
229
    def on_reportbugbutton_clicked(self, button):
294
 
        logger.info(bug_send)
 
230
        log.msg("Sending report bug")
295
231
        fd, filename = tempfile.mkstemp()
296
232
        os.write(fd, self.textbuffer.get_property("text"))
297
233
        gtk.link_button_set_uri_hook(None)
302
238
 
303
239
        def success((out, err, code)):
304
240
            if code == 0:
305
 
                logger.info(bug_sent)
 
241
                log.msg("Report bug sent succefully")
306
242
            elif code in BUG_REPORT_ERRORS:
307
 
                logger.error(bug_error, err=BUG_REPORT_ERRORS[code],
308
 
                             stderr=err, hide_to_user=True)
 
243
                log.err(BUG_REPORT_ERRORS[code])
 
244
                log.err(err, show_to_user=False)
309
245
            else:
310
 
                logger.error(bug_report_fail, code=code, stderr=err,
311
 
                             hide_to_user=True)
 
246
                log.err("Report bug failed with exit code %s" % code)
 
247
                log.err(err, show_to_user=False)
312
248
 
313
 
        exit_d.addCallback(success)
314
 
        exit_d.addErrback(logger.failure_eb, bug_err_unknown)
315
 
        exit_d.addBoth(lambda _: os.close(fd))
 
249
        exit_d.addCallbacks(success, log.err).addBoth(lambda _: os.close(fd))
316
250
 
317
251
 
318
252
class DisksLibraryDialog(Window):
321
255
    image = None
322
256
    cols_cell = (
323
257
        ("treeviewcolumn1", "cellrenderertext1", lambda i: i.name),
324
 
        ("treeviewcolumn6", "cellrenderertext6", lambda i: i.path),
325
 
        # ("treeviewcolumn2", "cellrenderertext2", lambda i: i.get_users()),
326
 
        ("treeviewcolumn3", "cellrenderertext3", lambda i: i.repr_master()),
327
 
        # ("treeviewcolumn4", "cellrenderertext4", lambda i: i.get_cows()),
 
258
        ("treeviewcolumn2", "cellrenderertext2", lambda i: i.get_users()),
 
259
        ("treeviewcolumn3", "cellrenderertext3",
 
260
         lambda i: i.get_master_name()),
 
261
        ("treeviewcolumn4", "cellrenderertext4", lambda i: i.get_cows()),
328
262
        ("treeviewcolumn5", "cellrenderertext5", lambda i: i.get_size())
329
263
    )
330
264
 
338
272
            cell_renderer = self.get_object(cell_renderer_name)
339
273
            column.set_cell_data_func(cell_renderer, self._set_cell_data,
340
274
                                      getter)
341
 
        column = self.get_object("treeviewcolumn2")
342
 
        cell_renderer = self.get_object("cellrenderertext2")
343
 
        column.set_cell_data_func(cell_renderer, self._set_users)
344
 
        column = self.get_object("treeviewcolumn4")
345
 
        cell_renderer = self.get_object("cellrenderertext4")
346
 
        column.set_cell_data_func(cell_renderer, self._set_cows)
347
275
        self.get_object("treeview_diskimages").set_model(factory.disk_images)
348
276
 
349
277
    def _set_cell_data(self, column, cell_renderer, model, iter, getter):
350
278
        image = model.get_value(iter, 0)
351
 
        self._set_text(cell_renderer, getter(image), image.exists())
352
 
 
353
 
    def _set_text(self, cell_renderer, text, exists):
354
 
        cell_renderer.set_property("text", text)
355
 
        cell_renderer.set_property("foreground", "black" if exists else "grey")
356
 
 
357
 
    def _set_users(self, column, cell_renderer, model, itr):
358
 
        def is_user(disk, image):
359
 
            return disk.image is image
360
 
        self._set_something(cell_renderer, model, itr, is_user)
361
 
 
362
 
    def _set_cows(self, column, cell_renderer, model, itr):
363
 
        def is_cow(disk, image):
364
 
            return disk.image is image and disk.cow
365
 
        self._set_something(cell_renderer, model, itr, is_cow)
366
 
 
367
 
    def _set_something(self, cell_renderer, model, itr, condition):
368
 
        image = model.get_value(itr, 0)
369
 
        c = 0
370
 
        for brick in self.factory.bricks:
371
 
            if brick.get_type() == "Qemu":
372
 
                for disk in brick.disks():
373
 
                    if condition(disk, image):
374
 
                        c += 1
375
 
        self._set_text(cell_renderer, str(c), image.exists())
 
279
        cell_renderer.set_property("text", getter(image))
 
280
        color = "black" if image.exists() else "grey"
 
281
        cell_renderer.set_property("foreground", color)
376
282
 
377
283
    def on_close_button_clicked(self, button):
378
284
        self.window.destroy()
392
298
        try:
393
299
            self.factory.remove_disk_image(self.image)
394
300
        except Exception:
395
 
            logger.failure(img_cannot_remove, img=self.image)
 
301
            log.exception("Cannot remove image %s", self.image)
396
302
        self.tree_panel.show()
397
303
        self.config_panel.hide()
398
304
 
400
306
        assert self.image is not None, \
401
307
                "Called on_save_button_clicked but no image is selected"
402
308
        name = self.get_object("name_entry").get_text()
403
 
        self.image.name = name
404
 
        # host = self.get_object("host_entry").get_text()
405
 
        # if host != self.image.host:
406
 
        #     self.image.host = host
407
 
        # ro = self.get_object("readonly_checkbutton").get_active()
408
 
        # self.image.set_readonly(ro)
 
309
        if self.image.name != name:
 
310
            self.image.rename(name)
 
311
        host = self.get_object("host_entry").get_text()
 
312
        if host != self.image.host:
 
313
            self.image.host = host
 
314
        ro = self.get_object("readonly_checkbutton").get_active()
 
315
        self.image.set_readonly(ro)
409
316
        desc = self.get_object("description_entry").get_text()
410
 
        if desc and self.image.description != desc:
411
 
            self.image.description = desc
 
317
        self.image.set_description(desc)
412
318
        self.image = None
413
319
        self.tree_panel.show()
414
320
        self.config_panel.hide()
419
325
        i, w = self.image, self.get_object
420
326
        w("name_entry").set_text(i.name)
421
327
        w("path_entry").set_text(i.path)
422
 
        w("description_entry").set_text(i.description)
423
 
        # w("readonly_checkbutton").set_active(i.is_readonly())
424
 
        # w("host_entry").set_text(i.host or "")
 
328
        w("description_entry").set_text(i.get_description())
 
329
        w("readonly_checkbutton").set_active(i.is_readonly())
 
330
        w("host_entry").set_text(i.host or "")
425
331
 
426
332
 
427
333
class UsbDevWindow(Window):
432
338
        Window.__init__(self)
433
339
        self.gui = gui
434
340
        self.vm = vm
435
 
        logger.info(lsusb_out, out=output)
 
341
        log.msg("lsusb output:\n%s" % output)
436
342
        model = self.get_object("liststore1")
437
343
        self._populate_model(model, output)
438
344
 
453
359
                ndev = model.get_value(iter, 0)
454
360
                if ndev == dev:
455
361
                    selection.select_iter(iter)
456
 
                    logger.debug(dev_found, dev=dev)
 
362
                    log.debug("found %s", dev)
457
363
                    break
458
364
            iter = model.iter_next(iter)
459
365
 
465
371
            devs = [model[p[0]][0] for p in paths]
466
372
 
467
373
            if devs and not os.access("/dev/bus/usb", os.W_OK):
468
 
                logger.error(perm_error)
 
374
                log.error(_("Cannot access /dev/bus/usb. "
 
375
                            "Check user privileges."))
469
376
                self.gui.gladefile.get_widget("cfg_Qemu_usbmode_check"
470
377
                                             ).set_active(False)
471
378
 
493
400
        self.window.response(gtk.RESPONSE_OK)
494
401
 
495
402
 
496
 
class BaseEthernetDialog(Window):
 
403
class EthernetDialog(Window):
497
404
 
498
405
    resource = "data/ethernetdialog.ui"
499
 
    name = "EthernetDialog"
500
406
 
501
 
    def __init__(self, factory, brick):
 
407
    def __init__(self, gui, brick, plug=None):
502
408
        Window.__init__(self)
503
 
        self.factory = factory
 
409
        self.gui = gui
504
410
        self.brick = brick
 
411
        self.plug = plug
 
412
        socks = self.get_object("sock_model")
 
413
        socks.append(("Host-only ad hoc network", "_hostonly"))
 
414
        if gui.config.femaleplugs:
 
415
            socks.append(("Vde socket", "_sock"))
 
416
        # TODO: can this operation made only once?
 
417
        for sock in gui.brickfactory.socks:
 
418
            if (sock.brick.get_type().startswith('Switch') or
 
419
                    gui.config.femaleplugs):
 
420
                socks.append((sock.nickname, sock.nickname))
 
421
 
 
422
        if plug:
 
423
            self.get_object("title_label").set_label(
 
424
                "<b>Edit ethernet interface</b>")
 
425
            self.get_object("ok_button").set_property("label", gtk.STOCK_OK)
 
426
            self.get_object("mac_entry").set_text(plug.mac)
 
427
            model = self.get_object("netmodel_model")
 
428
            i = model.get_iter_first()
 
429
            while i:
 
430
                if model.get_value(i, 0) == plug.model:
 
431
                    self.get_object("model_combo").set_active_iter(i)
 
432
                    break
 
433
                i = model.iter_next(i)
 
434
 
 
435
            i = socks.get_iter_first()
 
436
            while i:
 
437
                v = socks.get_value(i, 1)
 
438
                if ((plug.mode == "sock" and v == "_sock") or
 
439
                        (plug.mode == "hostonly" and v == "_hostonly") or
 
440
                        (plug.sock and plug.sock.nickname == v)):
 
441
                    self.get_object("sock_combo").set_active_iter(i)
 
442
                    break
 
443
                i = socks.iter_next(i)
 
444
        else:
 
445
            self.get_object("sock_combo").set_active(0)
505
446
 
506
447
    def is_valid(self, mac):
507
448
        return tools.mac_is_valid(mac)
508
449
 
509
 
    def setup(self):
510
 
        socks = self.get_object("sock_model")
511
 
        socks.append(("Host-only ad hoc network",
512
 
                      virtualmachines.hostonly_sock))
513
 
        if settings.femaleplugs:
514
 
            socks.append(("Vde socket", "_sock"))
515
 
            for sock in self.factory.socks:
516
 
                socks.append((sock.nickname, sock))
 
450
    def add_plug(self, vlan=None):
 
451
        combo = self.get_object("sock_combo")
 
452
        sockname = combo.get_model().get_value(combo.get_active_iter(), 1)
 
453
        if sockname == "_sock":
 
454
            plug = self.brick.add_sock()
 
455
        elif sockname == "_hostonly":
 
456
            plug = self.brick.add_plug(sockname)
517
457
        else:
518
 
            for sock in self.factory.socks:
519
 
                if sock.brick.get_type().startswith("Switch"):
520
 
                    socks.append((sock.nickname, sock))
 
458
            plug = self.brick.add_plug()
 
459
            for sock in self.gui.brickfactory.socks:
 
460
                if sock.nickname == sockname:
 
461
                    plug.connect(sock)
 
462
                    break
 
463
        combo = self.get_object("model_combo")
 
464
        plug.model = combo.get_model().get_value(combo.get_active_iter(), 0)
 
465
        mac = self.get_object("mac_entry").get_text()
 
466
        if not self.is_valid(mac):
 
467
            log.error("MAC address %s is not valid, generating a random one",
 
468
                      mac)
 
469
            mac = tools.random_mac()
 
470
        plug.mac = mac
 
471
        if vlan is not None:
 
472
            plug.vlan = vlan
 
473
 
 
474
        self.gui.vmplugs.append((plug, ))
521
475
 
522
476
    def on_randomize_button_clicked(self, button):
523
477
        self.get_object("mac_entry").set_text(tools.random_mac())
524
478
 
525
479
    def on_EthernetDialog_response(self, dialog, response_id):
526
480
        if response_id == gtk.RESPONSE_OK:
527
 
            combo = self.get_object("sock_combo")
528
 
            sock = combo.get_model().get_value(combo.get_active_iter(), 1)
529
 
            combo = self.get_object("model_combo")
530
 
            model = combo.get_model().get_value(combo.get_active_iter(), 0)
531
 
            mac = self.get_object("mac_entry").get_text()
532
 
            if not self.is_valid(mac):
533
 
                logger.error(invalid_mac, mac=mac)
534
 
                mac = tools.random_mac()
535
 
            self.do(sock, mac, model)
 
481
            vlan = None
 
482
            plug = self.plug
 
483
            if plug:
 
484
                vlan = plug.vlan
 
485
                if plug.mode == "sock":
 
486
                    self.brick.socks.remove(plug)
 
487
                else:
 
488
                    self.brick.plugs.remove(plug)
 
489
 
 
490
                get_value = self.gui.vmplugs.get_value
 
491
                iter_next = self.gui.vmplugs.iter_next
 
492
                i = self.gui.vmplugs.get_iter_first()
 
493
                while i:
 
494
                    l = get_value(i, 0)
 
495
                    if plug is l:
 
496
                        self.gui.vmplugs.remove(i)
 
497
                        break
 
498
                    i = iter_next(i)
 
499
 
 
500
            self.add_plug(vlan)
536
501
        dialog.destroy()
537
502
 
538
503
 
539
 
class AddEthernetDialog(BaseEthernetDialog):
540
 
 
541
 
    def __init__(self, factory, brick, model):
542
 
        BaseEthernetDialog.__init__(self, factory, brick)
543
 
        self.model = model
544
 
 
545
 
    def show(self, parent=None):
546
 
        self.setup()
547
 
        self.get_object("sock_combo").set_active(0)
548
 
        BaseEthernetDialog.show(self, parent)
549
 
 
550
 
    def do(self, sock, mac, model):
551
 
        if sock == "_sock":
552
 
            link = self.brick.add_sock(mac, model)
553
 
        else:
554
 
            link = self.brick.add_plug(sock, mac, model)
555
 
        self.model.append((link, ))
556
 
 
557
 
 
558
 
class EditEthernetDialog(BaseEthernetDialog):
559
 
 
560
 
    def __init__(self, factory, brick, plug):
561
 
        BaseEthernetDialog.__init__(self, factory, brick)
562
 
        self.plug = plug
563
 
 
564
 
    def show(self, parent=None):
565
 
        self.setup()
566
 
        self.get_object("title_label").set_label(
567
 
            "<b>Edit ethernet interface</b>")
568
 
        self.get_object("ok_button").set_property("label", gtk.STOCK_OK)
569
 
        self.get_object("mac_entry").set_text(self.plug.mac)
570
 
        model = self.get_object("netmodel_model")
571
 
        itr = model.get_iter_first()
572
 
        while itr:
573
 
            if model.get_value(itr, 0) == self.plug.model:
574
 
                self.get_object("model_combo").set_active_iter(itr)
575
 
                break
576
 
            itr = model.iter_next(itr)
577
 
 
578
 
        socks = self.get_object("sock_model")
579
 
        if self.plug.mode == "sock" and settings.femaleplugs:
580
 
            self.get_object("sock_combo").set_active(1)
581
 
        else:
582
 
            itr = socks.get_iter_first()
583
 
            while itr:
584
 
                if self.plug.sock is socks.get_value(itr, 1):
585
 
                    self.get_object("sock_combo").set_active_iter(itr)
586
 
                    break
587
 
                itr = socks.iter_next(itr)
588
 
        BaseEthernetDialog.show(self, parent)
589
 
 
590
 
    def do(self, sock, mac, model):
591
 
        if sock == "_sock":
592
 
            logger.error(not_implemented)
593
 
        else:
594
 
            if self.plug.configured():
595
 
                self.plug.disconnect()
596
 
            self.plug.connect(sock)
597
 
            if mac:
598
 
                self.plug.mac = mac
599
 
            if model:
600
 
                self.plug.model = model
601
 
 
602
 
 
603
504
class ConfirmDialog(Window):
604
505
 
605
506
    resource = "data/confirmdialog.ui"
642
543
        try:
643
544
            if response_id == gtk.RESPONSE_OK:
644
545
                if self.brick.scheduled:
645
 
                    logger.error(event_in_use)
 
546
                    log.err(_("Cannot rename event: it is in use."))
646
547
                else:
647
548
                    new = self.get_object("entry").get_text()
648
549
                    self.factory.rename_event(self.brick, new)
657
558
            return
658
559
            if response_id == gtk.RESPONSE_OK:
659
560
                if self.event.scheduled:
660
 
                    logger.error(brick_in_use)
 
561
                    log.err(_("Cannot rename event: it is in use."))
661
562
                else:
662
563
                    new = self.get_object("entry").get_text()
663
564
                    self.factory.rename_event(self.event, new)
788
689
            actions = [console.VbShellCommand("%s %s" % (b.name, self.action))
789
690
                       for b in self.added]
790
691
            self.event.set({"actions": actions})
791
 
            logger.info(event_created)
 
692
            log.msg("Event created successfully")
792
693
        dialog.destroy()
793
694
 
794
695
 
843
744
        if response_id == gtk.RESPONSE_OK:
844
745
            self.configure_event(self.event, {})
845
746
        dialog.destroy()
846
 
 
847
 
 
848
 
def disks_of(brick):
849
 
    if brick.get_type() == "Qemu":
850
 
        for dev in "hda", "hdb", "hdc", "hdd", "fda", "fdb", "mtdblock":
851
 
            yield brick.config[dev]
852
 
 
853
 
 
854
 
class CommitImageDialog(Window):
855
 
 
856
 
    resource = "data/commitdialog.ui"
857
 
    parent = None
858
 
    _set_label_d = None
859
 
 
860
 
    def __init__(self, progessbar, factory):
861
 
        Window.__init__(self)
862
 
        self.progessbar = progessbar
863
 
        model = self.get_object("model1")
864
 
        for brick in factory.bricks:
865
 
            for disk in (disk for disk in disks_of(brick) if disk.cow):
866
 
                model.append((disk.device + " on " + brick.name, disk))
867
 
 
868
 
    def show(self, parent=None):
869
 
        self.parent = parent
870
 
        Window.show(self, parent)
871
 
 
872
 
    def _do_image_commit(self, path):
873
 
 
874
 
        def log_err((out, err, exit_status)):
875
 
            if exit_status != 0:
876
 
                logger.error(commit_failed, err=err)
877
 
 
878
 
        d = utils.getProcessOutputAndValue("qemu-img", ["commit", path],
879
 
                                           os.environ)
880
 
        d.addCallback(log_err)
881
 
        return d
882
 
 
883
 
    def do_image_commit(self, path):
884
 
        self.window.destroy()
885
 
        self.progessbar.wait_for(self._do_image_commit, path)
886
 
 
887
 
    def commit_file(self, pathname):
888
 
        question = ("Warning: the base image will be updated to the\n"
889
 
                    "changes contained in the COW. This operation\n"
890
 
                    "cannot be undone. Are you sure?")
891
 
        ConfirmDialog(question, on_yes=self.do_image_commit,
892
 
                      on_yes_arg=pathname).show(self.parent)
893
 
 
894
 
    def _commit_vm(self, img):
895
 
        logger.warning(not_implemented)
896
 
        # img.VM.commit_disks()
897
 
        self.window.destroy()
898
 
 
899
 
    def commit_vm(self):
900
 
        combobox = self.get_object("disk_combo")
901
 
        model = combobox.get_model()
902
 
        itr = combobox.get_active_iter()
903
 
        if itr:
904
 
            img = model[itr][1]
905
 
            if not self.get_object("cow_checkbutton").get_active():
906
 
                question = ("Warning: the private COW image will be "
907
 
                            "updated.\nThis operation cannot be undone.\n"
908
 
                            "Are you sure?")
909
 
                ConfirmDialog(question, on_yes=self._commit_vm,
910
 
                              on_yes_arg=img).show(self.parent)
911
 
            else:
912
 
                pathname = os.path.join(img.basefolder,
913
 
                        "{0.vm_name}_{0.device}.cow".format(img))
914
 
                self.commit_file(pathname)
915
 
        else:
916
 
            logger.error(img_invalid)
917
 
 
918
 
    def on_CommitImageDialog_response(self, dialog, response_id):
919
 
        if response_id == gtk.RESPONSE_OK:
920
 
            if self.get_object("file_radiobutton").get_active():
921
 
                pathname = self.get_object(
922
 
                    "cowpath_filechooser").get_filename()
923
 
                self.commit_file(pathname)
924
 
            else:
925
 
                self.commit_vm()
926
 
        else:
927
 
            dialog.destroy()
928
 
 
929
 
    def on_file_radiobutton_toggled(self, button):
930
 
        active = button.get_active()
931
 
        filechooser = self.get_object("cowpath_filechooser")
932
 
        filechooser.set_visible(active)
933
 
        filechooser.unselect_all()
934
 
        combo = self.get_object("disk_combo")
935
 
        combo.set_visible(not active)
936
 
        combo.set_active(-1)
937
 
        self.get_object("cow_checkbutton").set_visible(not active)
938
 
        self.get_object("msg_label").set_visible(False)
939
 
 
940
 
    def _commit_image_show_result(self, (out, err, code)):
941
 
        if code != 0:
942
 
            logger.error(base_not_found, err=err)
943
 
        else:
944
 
            label = self.get_object("msg_label")
945
 
            for line in out.splitlines():
946
 
                if line.startswith("backing file: "):
947
 
                    label.set_text(line)
948
 
                    break
949
 
            else:
950
 
                label.set_text(_("Base not found (invalid cow?)"))
951
 
            label.set_visible(True)
952
 
 
953
 
    def on_cowpath_filechooser_file_set(self, filechooser):
954
 
        if self._set_label_d is not None:
955
 
            self._set_label_d.cancel()
956
 
        filename = filechooser.get_filename()
957
 
        if os.access(filename, os.R_OK):
958
 
            code = utils.getProcessOutputAndValue("qemu-img",
959
 
                    ["info", filename], os.environ)
960
 
            code.addCallback(self._commit_image_show_result)
961
 
            self._set_label_d = code
962
 
            return code
963
 
 
964
 
    def set_label(self, combobox=None, button=None):
965
 
        if self._set_label_d is not None:
966
 
            self._set_label_d.cancel()
967
 
        if combobox is None:
968
 
            combobox = self.get_object("disk_combo")
969
 
        if button is None:
970
 
            button = self.get_object("cow_checkbutton")
971
 
        label = self.get_object("msg_label")
972
 
        label.set_visible(False)
973
 
        model = combobox.get_model()
974
 
        itr = combobox.get_active_iter()
975
 
        if itr is not None:
976
 
            disk = model[itr][1]
977
 
            base = disk.image and disk.image.path or None
978
 
            if base and button.get_active():
979
 
                # XXX: make disk.get_real_disk_name's deferred cancellable
980
 
                deferred = disk.get_real_disk_name()
981
 
                deferred.addCallback(label.set_text)
982
 
                deferred.addCallback(lambda _: label.set_visible(True))
983
 
                deferred.addErrback(logger.failure_eb, img_combo)
984
 
                self._set_label_d = deferred
985
 
            elif base:
986
 
                label.set_visible(True)
987
 
                label.set_text(base)
988
 
            else:
989
 
                label.set_visible(True)
990
 
                label.set_text("base not found")
991
 
        else:
992
 
            label.set_visible(True)
993
 
            label.set_text("base not found")
994
 
 
995
 
    def on_disk_combo_changed(self, combobox):
996
 
        self.set_label(combobox=combobox)
997
 
 
998
 
    def on_cow_checkbutton_toggled(self, button):
999
 
        self.set_label(button=button)
1000
 
 
1001
 
 
1002
 
def choose_new_image(gui, factory):
1003
 
    main = gui.get_object("main_win")
1004
 
    dialog = gtk.FileChooserDialog(_("Open a disk image"), main,
1005
 
        gtk.FILE_CHOOSER_ACTION_OPEN,
1006
 
        (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
1007
 
         gtk.STOCK_OPEN, gtk.RESPONSE_OK))
1008
 
    if dialog.run() == gtk.RESPONSE_OK:
1009
 
        pathname = dialog.get_filename()
1010
 
        LoadImageDialog(factory, pathname).show(main)
1011
 
    dialog.destroy()
1012
 
 
1013
 
 
1014
 
class LoadImageDialog(Window):
1015
 
 
1016
 
    resource = "data/loadimagedialog.ui"
1017
 
 
1018
 
    def __init__(self, factory, pathname):
1019
 
        Window.__init__(self)
1020
 
        self.pathname = pathname
1021
 
        self.factory = factory
1022
 
 
1023
 
    def show(self, parent=None):
1024
 
        name = os.path.basename(self.pathname)
1025
 
        self.get_object("name_entry").set_text(name)
1026
 
        buf = self.get_object("description_textview").get_buffer()
1027
 
        buf.set_text(self.load_desc())
1028
 
        Window.show(self, parent)
1029
 
 
1030
 
    def load_desc(self):
1031
 
        try:
1032
 
            with open(self.pathname + ".vbdescr") as fd:
1033
 
                return fd.read()
1034
 
        except IOError:
1035
 
            return ""
1036
 
 
1037
 
    def on_LoadImageDialog_response(self, dialog, response_id):
1038
 
        if response_id == gtk.RESPONSE_OK:
1039
 
            name = self.get_object("name_entry").get_text()
1040
 
            buf = self.get_object("description_textview").get_buffer()
1041
 
            desc = buf.get_text(buf.get_start_iter(), buf.get_end_iter())
1042
 
            try:
1043
 
                self.factory.new_disk_image(name, self.pathname, desc)
1044
 
            except:
1045
 
                dialog.destroy()
1046
 
                raise
1047
 
        dialog.destroy()
1048
 
 
1049
 
 
1050
 
class CreateImageDialog(Window):
1051
 
 
1052
 
    resource = "data/createimagedialog.ui"
1053
 
 
1054
 
    def __init__(self, gui, factory):
1055
 
        self.gui = gui
1056
 
        self.factory = factory
1057
 
        Window.__init__(self)
1058
 
 
1059
 
    def create_image(self, name, pathname, fmt, size, unit):
1060
 
 
1061
 
        def _create_disk(result):
1062
 
            out, err, code = result
1063
 
            if code:
1064
 
                logger.error(err)
1065
 
            else:
1066
 
                return self.factory.new_disk_image(name, pathname)
1067
 
 
1068
 
        exit = utils.getProcessOutputAndValue("qemu-img",
1069
 
            ["create", "-f", fmt, pathname, size + unit], os.environ)
1070
 
        exit.addCallback(_create_disk)
1071
 
        exit.addErrback(logger.failure_eb, img_create_err)
1072
 
        return exit
1073
 
 
1074
 
    def on_CreateImageDialog_response(self, dialog, response_id):
1075
 
        if response_id == gtk.RESPONSE_OK:
1076
 
            logger.info(img_create)
1077
 
            name = self.get_object("name_entry").get_text()
1078
 
            if not name:
1079
 
                logger.error(img_choose)
1080
 
                return
1081
 
            folder = self.get_object("folder_filechooserbutton").get_filename()
1082
 
            fmt_cmb = self.get_object("format_combobox")
1083
 
            itr = fmt_cmb.get_active_iter()
1084
 
            if itr:
1085
 
                fmt = fmt_cmb.get_model()[itr][0]
1086
 
                if fmt == "Auto":
1087
 
                    fmt = "raw"
1088
 
            else:
1089
 
                logger.info(img_invalid_type)
1090
 
                fmt = "raw"
1091
 
            size = str(self.get_object("size_spinbutton").get_value_as_int())
1092
 
            # Get size unit and remove the last character "B"
1093
 
            # because qemu-img want k, M, G or T suffixes.
1094
 
            unit_cmb = self.get_object("unit_combobox")
1095
 
            itr = unit_cmb.get_active_iter()
1096
 
            if itr:
1097
 
                unit = unit_cmb.get_model()[itr][0][0]
1098
 
            else:
1099
 
                logger.info(img_invalid_unit)
1100
 
                unit = "M"
1101
 
            pathname = os.path.join(folder, name, os.path.extsep, fmt)
1102
 
            self.gui.user_wait_action(self.create_image(name, pathname, fmt,
1103
 
                                                        size, unit))
1104
 
        dialog.destroy()
1105
 
 
1106
 
 
1107
 
class NewProjectDialog(Window):
1108
 
 
1109
 
    resource = "data/newproject.ui"
1110
 
 
1111
 
    def __init__(self, factory):
1112
 
        self.factory = factory
1113
 
        Window.__init__(self)
1114
 
 
1115
 
    def on_NewProjectDialog_response(self, dialog, response_id):
1116
 
        try:
1117
 
            if response_id == gtk.RESPONSE_OK:
1118
 
                name = self.get_object("name_entry").get_text()
1119
 
                project.manager.create(name, self.factory)
1120
 
        finally:
1121
 
            dialog.destroy()
1122
 
 
1123
 
 
1124
 
class OpenProjectDialog(Window):
1125
 
 
1126
 
    resource = "data/openproject.ui"
1127
 
 
1128
 
    def __init__(self, gui):
1129
 
        self.gui = gui
1130
 
        Window.__init__(self)
1131
 
 
1132
 
    def show(self, parent=None):
1133
 
        model = self.get_object("liststore1")
1134
 
        for prj in project.manager:
1135
 
            model.append((prj, ))
1136
 
        Window.show(self, parent)
1137
 
 
1138
 
    def on_treeview1_row_activated(self, treeview, path, column):
1139
 
        model = treeview.get_model()
1140
 
        name = model[path][0]
1141
 
        self.on_OpenProjectDialog_response(self.get_object(
1142
 
            "OpenProjectDialog"), gtk.RESPONSE_OK, name)
1143
 
 
1144
 
    def on_OpenProjectDialog_response(self, dialog, response_id, name=""):
1145
 
        if response_id == gtk.RESPONSE_OK:
1146
 
            if not name:
1147
 
                treeview = self.get_object("treeview1")
1148
 
                model, itr = treeview.get_selection().get_selected()
1149
 
                if itr:
1150
 
                    name = model.get_value(itr, 0)
1151
 
                else:
1152
 
                    return
1153
 
            try:
1154
 
                self.gui.on_open(name)
1155
 
            finally:
1156
 
                dialog.destroy()
1157
 
        dialog.destroy()
1158
 
 
1159
 
 
1160
 
def is_vm(brick):
1161
 
    return brick.get_type() == "Qemu"
1162
 
 
1163
 
 
1164
 
def has_cow(disk):
1165
 
    return disk.image and disk.cow
1166
 
 
1167
 
 
1168
 
def cowname(brick, disk):
1169
 
    return os.path.join(project.current.path,
1170
 
                        "{0.name}_{1.device}.cow".format(brick, disk))
1171
 
 
1172
 
 
1173
 
def gather_selected(model, parent, workspace, lst):
1174
 
    itr = model.iter_children(parent)
1175
 
    while itr:
1176
 
        fp = model[itr][FILEPATH]
1177
 
        if model[itr][SELECTED] and fp.isfile():
1178
 
            lst.append(os.path.join(*fp.segmentsFrom(workspace)))
1179
 
        else:
1180
 
            gather_selected(model, itr, workspace, lst)
1181
 
        itr = model.iter_next(itr)
1182
 
 
1183
 
 
1184
 
class ImportCanceled(Exception):
1185
 
    pass
1186
 
 
1187
 
 
1188
 
SELECTED, ACTIVABLE, TYPE, NAME, FILEPATH = range(5)
1189
 
 
1190
 
 
1191
 
class ExportProjectDialog(Window):
1192
 
 
1193
 
    resource = "data/exportproject.ui"
1194
 
    include_images = False
1195
 
 
1196
 
    def __init__(self, progressbar, prjpath, disk_images):
1197
 
        super(Window, self).__init__()
1198
 
        self.progressbar = progressbar
1199
 
        self.prjpath = prjpath
1200
 
        self.image_files = [(image.name, filepath.FilePath(image.path))
1201
 
                            for image in disk_images]
1202
 
        # self.image_files = set(filepath.FilePath(image.path) for image in
1203
 
        #                         disk_images)
1204
 
        self.required_files = set([prjpath.child(".project"),
1205
 
                                   prjpath.child("README")])
1206
 
        self.internal_files = set([prjpath.child("vde.dot"),
1207
 
                                   prjpath.child("vde_topology.plain"),
1208
 
                                   prjpath.child(".images")])
1209
 
 
1210
 
    def append_dirs(self, dirpath, dirnames, model, parent, nodes):
1211
 
        for dirname in sorted(dirnames):
1212
 
            child = dirpath.child(dirname)
1213
 
            if child in self.required_files | self.internal_files:
1214
 
                dirnames.remove(dirname)
1215
 
            else:
1216
 
                row = (True, True, gtk.STOCK_DIRECTORY, dirname, child)
1217
 
                nodes[child.path] = model.append(parent, row)
1218
 
 
1219
 
    def append_files(self, dirpath, filenames, model, parent):
1220
 
        for filename in sorted(filenames):
1221
 
            child = dirpath.child(filename)
1222
 
            if (child not in self.required_files | self.internal_files and
1223
 
                    child.isfile() and not child.islink()):
1224
 
                row = (True, True, gtk.STOCK_FILE, filename, child)
1225
 
                model.append(parent, row)
1226
 
 
1227
 
    def build_path_tree(self, model, prjpath):
1228
 
        row = (True, True, gtk.STOCK_DIRECTORY, prjpath.basename(), prjpath)
1229
 
        root = model.append(None, row)
1230
 
        nodes = {prjpath.path: root}
1231
 
        for dirpath, dirnames, filenames in os.walk(prjpath.path):
1232
 
            parent = nodes[dirpath]
1233
 
            dp = filepath.FilePath(dirpath)
1234
 
            self.append_dirs(dp, dirnames, model, parent, nodes)
1235
 
            self.append_files(dp, filenames, model, parent)
1236
 
 
1237
 
    def show(self, parent_w=None):
1238
 
        model = self.get_object("treestore1")
1239
 
        self.build_path_tree(model, self.prjpath)
1240
 
        pixbuf_cr = self.get_object("icon_cellrenderer")
1241
 
        pixbuf_cr.set_property("stock-size", gtk.ICON_SIZE_MENU)
1242
 
        size_c = self.get_object("treeviewcolumn2")
1243
 
        size_cr = self.get_object("size_cellrenderer")
1244
 
        size_c.set_cell_data_func(size_cr, self._set_size)
1245
 
        self.get_object("selected_cellrenderer").connect(
1246
 
            "toggled", self.on_selected_cellrenderer_toggled, model)
1247
 
        self.get_object("treeview1").expand_row(0, False)
1248
 
        Window.show(self, parent_w)
1249
 
 
1250
 
    def _set_size(self, column, cellrenderer, model, itr):
1251
 
        fp = model.get_value(itr, FILEPATH)
1252
 
        if fp.isfile():
1253
 
            cellrenderer.set_property("text", tools.fmtsize(fp.getsize()))
1254
 
        else:
1255
 
            size = self._calc_size(model, itr)
1256
 
            if model.get_path(itr) == (0,):
1257
 
                size += sum(fp.getsize() for fp in self.required_files if
1258
 
                            fp.exists())
1259
 
                if self.include_images:
1260
 
                    size += sum(fp.getsize() for n, fp in self.image_files)
1261
 
            cellrenderer.set_property("text", tools.fmtsize(size))
1262
 
 
1263
 
    def _calc_size(self, model, parent):
1264
 
        size = 0
1265
 
        fp = model[parent][FILEPATH]
1266
 
        if fp.isdir():
1267
 
            itr = model.iter_children(parent)
1268
 
            while itr:
1269
 
                size += self._calc_size(model, itr)
1270
 
                itr = model.iter_next(itr)
1271
 
        elif model[parent][SELECTED]:
1272
 
            size += fp.getsize()
1273
 
        return size
1274
 
 
1275
 
    def _normalize_filename(self, filename):
1276
 
        if filename[-4:] != ".vbp":
1277
 
            return filename + ".vbp"
1278
 
        return filename
1279
 
 
1280
 
    def on_selected_cellrenderer_toggled(self, cellrenderer, path, model):
1281
 
        itr = model.get_iter(path)
1282
 
        model[itr][SELECTED] = not model[itr][SELECTED]
1283
 
        self._select_children(model, itr, model[itr][SELECTED])
1284
 
        parent = model.iter_parent(itr)
1285
 
        while parent:
1286
 
            child = model.iter_children(parent)
1287
 
            while child:
1288
 
                if not model[child][SELECTED]:
1289
 
                    model[parent][SELECTED] = False
1290
 
                    break
1291
 
                child = model.iter_next(child)
1292
 
            else:
1293
 
                model[parent][SELECTED] = True
1294
 
            parent = model.iter_parent(parent)
1295
 
 
1296
 
    def _select_children(self, model, parent, selected):
1297
 
        itr = model.iter_children(parent)
1298
 
        while itr:
1299
 
            self._select_children(model, itr, selected)
1300
 
            model[itr][SELECTED] = selected
1301
 
            itr = model.iter_next(itr)
1302
 
 
1303
 
    def on_filechooser_response(self, dialog, response_id):
1304
 
        if response_id == gtk.RESPONSE_OK:
1305
 
            filename = dialog.get_filename()
1306
 
            if filename is None:
1307
 
                self.get_object("export_button").set_sensitive(False)
1308
 
            elif os.path.exists(filename) and not os.path.isfile(filename):
1309
 
                dialog.unselect_all()
1310
 
                self.get_object("export_button").set_sensitive(False)
1311
 
            else:
1312
 
                filename = self._normalize_filename(filename)
1313
 
                txt = filename.decode(sys.getfilesystemencoding()).encode(
1314
 
                    "utf8")
1315
 
                self.get_object("filename_entry").set_text(txt)
1316
 
                self.get_object("export_button").set_sensitive(True)
1317
 
        dialog.destroy()
1318
 
 
1319
 
    def on_open_button_clicked(self, button):
1320
 
        chooser = gtk.FileChooserDialog(title=_("Export project"),
1321
 
                action=gtk.FILE_CHOOSER_ACTION_SAVE,
1322
 
                buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
1323
 
                         gtk.STOCK_SAVE, gtk.RESPONSE_OK))
1324
 
        vbp = gtk.FileFilter()
1325
 
        vbp.add_pattern("*.vbp")
1326
 
        chooser.set_do_overwrite_confirmation(True)
1327
 
        chooser.set_filter(vbp)
1328
 
        chooser.connect("response", self.on_filechooser_response)
1329
 
        chooser.set_transient_for(self.window)
1330
 
        chooser.show()
1331
 
 
1332
 
    def on_filename_entry_changed(self, entry):
1333
 
        self.get_object("export_button").set_sensitive(bool(entry.get_text()))
1334
 
 
1335
 
    def on_include_images_checkbutton_toggled(self, checkbutton):
1336
 
        self.include_images = checkbutton.get_active()
1337
 
        model = self.get_object("treestore1")
1338
 
        model.row_changed((0,), model.get_iter((0,)))
1339
 
 
1340
 
    def export(self, model, ancestor, filename):
1341
 
        files = []
1342
 
        gather_selected(model, model.get_iter_first(), ancestor, files)
1343
 
        for fp in self.required_files:
1344
 
            if fp.exists():
1345
 
                files.append(os.path.join(*fp.segmentsFrom(ancestor)))
1346
 
        images = []
1347
 
        if self.include_images:
1348
 
            images = [(name, fp.path) for name, fp in self.image_files]
1349
 
        return project.manager.export(filename, files, images)
1350
 
 
1351
 
    def on_ExportProjectDialog_response(self, dialog, response_id):
1352
 
        if response_id == gtk.RESPONSE_OK:
1353
 
            model = self.get_object("treestore1")
1354
 
            ancestor = filepath.FilePath(settings.VIRTUALBRICKS_HOME)
1355
 
            filename = self._normalize_filename(self.get_object(
1356
 
                "filename_entry").get_text())
1357
 
            self.progressbar.wait_for(self.export(model, ancestor, filename))
1358
 
        dialog.destroy()
1359
 
 
1360
 
 
1361
 
def retrieve_data(widget, data):
1362
 
    lst, name = data
1363
 
    attr = widget.get_data(name)
1364
 
    if attr is not None:
1365
 
        lst.append((attr, widget))
1366
 
 
1367
 
 
1368
 
def accumulate_data(container, name):
1369
 
    lst = []
1370
 
    container.foreach(retrieve_data, (lst, name))
1371
 
    return lst
1372
 
 
1373
 
 
1374
 
def pass_through(function, *args, **kwds):
1375
 
    def wrapper(arg):
1376
 
        function(*args, **kwds)
1377
 
        return arg
1378
 
    return wrapper
1379
 
 
1380
 
 
1381
 
def iter_model(model, *columns):
1382
 
    itr = model.get_iter_root()
1383
 
    if not columns:
1384
 
        columns = range(model.get_n_columns())
1385
 
    while itr:
1386
 
        yield model.get(itr, *columns)
1387
 
        itr = model.iter_next(itr)
1388
 
 
1389
 
 
1390
 
def complain_on_error(result):
1391
 
    out, err, code = result
1392
 
    if code != 0:
1393
 
        logger.warn(err)
1394
 
        raise error.ProcessTerminated(code)
1395
 
    logger.info(err)
1396
 
    return result
1397
 
 
1398
 
 
1399
 
def set_path(column, cell_renderer, model, iter, colid):
1400
 
    path = model.get_value(iter, colid)
1401
 
    cell_renderer.set_property("text",  path.path if path else "")
1402
 
 
1403
 
 
1404
 
class Freezer:
1405
 
 
1406
 
    def __init__(self, freeze, unfreeze, parent):
1407
 
        self.freeze = freeze
1408
 
        self.unfreeze = unfreeze
1409
 
        builder = gtk.Builder()
1410
 
        res = graphics.get_filename("virtualbricks.gui", "data/userwait.ui")
1411
 
        builder.add_from_file(res)
1412
 
        self.progressbar = builder.get_object("progressbar")
1413
 
        self.window = builder.get_object("UserWaitWindow")
1414
 
        self.window.set_transient_for(parent)
1415
 
        self.window.set_modal(True)
1416
 
 
1417
 
    def wait_for(self, something, *args):
1418
 
        if isinstance(something, defer.Deferred):
1419
 
            return self.wait_for_deferred(something)
1420
 
        elif hasattr(something, "__call__"):
1421
 
            return self.wait_for_action(something, *args)
1422
 
        raise RuntimeError("Invalid argument")
1423
 
 
1424
 
    def wait_for_action(self, action, *args):
1425
 
        done = defer.maybeDeferred(action, *args)
1426
 
        return self.wait_for_deferred(done)
1427
 
 
1428
 
    def wait_for_deferred(self, deferred):
1429
 
        deferred.addBoth(self.stop, self.start())
1430
 
        return deferred
1431
 
 
1432
 
    def start(self):
1433
 
        self.freeze()
1434
 
        self.window.show_all()
1435
 
        lc = task.LoopingCall(self.progressbar.pulse)
1436
 
        lc.start(0.2, False)
1437
 
        return lc
1438
 
 
1439
 
    def stop(self, passthru, lc):
1440
 
        self.window.destroy()
1441
 
        self.unfreeze()
1442
 
        lc.stop()
1443
 
        return passthru
1444
 
 
1445
 
 
1446
 
class ProgressBar:
1447
 
 
1448
 
    def __init__(self, dialog):
1449
 
        self.freezer = Freezer(lambda: None, lambda: None, dialog)
1450
 
 
1451
 
    def wait_for(self, something, *args):
1452
 
        return self.freezer.wait_for(something, *args)
1453
 
 
1454
 
 
1455
 
class _HumbleImport:
1456
 
 
1457
 
    def step_1(self, dialog, model, path, extract=project.manager.extract):
1458
 
        archive_path = dialog.get_archive_path()
1459
 
        if archive_path != dialog.archive_path:
1460
 
            if dialog.project:
1461
 
                dialog.project.delete()
1462
 
            dialog.archive_path = archive_path
1463
 
            d = extract(filepath._secureEnoughString(), archive_path)
1464
 
            d.addCallback(self.extract_cb, dialog)
1465
 
            d.addCallback(self.fill_model_cb, dialog, model, path)
1466
 
            d.addErrback(self.extract_eb, dialog)
1467
 
            return d
1468
 
 
1469
 
    def extract_cb(self, project, dialog):
1470
 
        logger.debug(project_extracted, path=project.path)
1471
 
        dialog.project = project
1472
 
        dialog.images = dict((name, section["path"]) for (_, name), section in
1473
 
                              project.get_descriptor().get_images())
1474
 
        return project
1475
 
 
1476
 
    def extract_eb(self, fail, dialog):
1477
 
        logger.failure(extract_err, fail)
1478
 
        dialog.destroy()
1479
 
        return fail
1480
 
 
1481
 
    def fill_model_cb(self, project, dialog, model, vipath):
1482
 
        model.clear()
1483
 
        for name in (fp.basename() for fp in project.imported_images()):
1484
 
            if name in dialog.images:
1485
 
                fp = vipath.child(os.path.basename(dialog.images[name]))
1486
 
            else:
1487
 
                fp = vipath.child(name)
1488
 
            fp2 = filepath.FilePath(fp.path)
1489
 
            c = 1
1490
 
            while fp2.exists():
1491
 
                fp2 = fp.siblingExtension(".{0}".format(c))
1492
 
                c += 1
1493
 
            model.append((name, fp2, True))
1494
 
        return project
1495
 
 
1496
 
    def step_2(self, dialog, store1, store2):
1497
 
        """Step 2: map images."""
1498
 
 
1499
 
        imgs = dict((name, path) for name, path, save in
1500
 
                    iter_model(store1) if save)
1501
 
        store2.clear()
1502
 
        for name in dialog.images:
1503
 
            store2.append((name, imgs.get(name)))
1504
 
        if len(store2) == 0 or all(p for (p,) in iter_model(store2, 1)):
1505
 
            dialog.set_page_complete()
1506
 
 
1507
 
    def step_3(self, dialog):
1508
 
        w = dialog.get_object
1509
 
        w("projectname_label").set_text(dialog.get_project_name())
1510
 
        path_label = w("projectpath_label")
1511
 
        path = dialog.project.filepath.sibling(dialog.get_project_name()).path
1512
 
        path_label.set_text(path)
1513
 
        path_label.set_tooltip_text(path)
1514
 
        w("open_label").set_text(str(dialog.get_open()))
1515
 
        w("overwrite_label").set_text(str(dialog.get_overwrite()))
1516
 
        iimgs = (name for name, s in iter_model(w("liststore1"), 0, 2) if s)
1517
 
        w("imported_label").set_text("\n".join(iimgs))
1518
 
        store = w("liststore2")
1519
 
        vbox = w("vbox1")
1520
 
        vbox.foreach(vbox.remove)
1521
 
        for i, (name, dest) in enumerate(iter_model(store)):
1522
 
            nlabel = gtk.Label(name + ":")
1523
 
            nlabel.set_alignment(0.0, 0.5)
1524
 
            dlabel = gtk.Label(dest.path)
1525
 
            dlabel.set_tooltip_text(dest.path)
1526
 
            dlabel.set_alignment(0.0, 0.5)
1527
 
            dlabel.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
1528
 
            box = gtk.HBox(spacing=5)
1529
 
            box.pack_start(nlabel, False, True, 0)
1530
 
            box.pack_start(dlabel, True, True, 0)
1531
 
            vbox.pack_start(box, False, True, 3)
1532
 
            box.show_all()
1533
 
 
1534
 
    def apply(self, project, name, factory, overwrite, open, store1, store2):
1535
 
        entry = project.get_descriptor()
1536
 
        imgs = self.get_images(project, entry, store1, store2)
1537
 
        deferred = self.rebase_all(project, imgs, entry)
1538
 
        deferred.addCallback(self.check_rebase)
1539
 
        deferred.addCallback(lambda a: project.rename(name, overwrite))
1540
 
        if open:
1541
 
            deferred.addCallback(pass_through(project.restore, factory))
1542
 
        deferred.addErrback(pass_through(project.delete))
1543
 
        deferred.addErrback(logger.failure_eb, error_on_import_project)
1544
 
        return deferred
1545
 
 
1546
 
    def get_images(self, project, entry, store1, store2):
1547
 
        imagesfp = project.filepath.child(".images")
1548
 
        imgs = self.save_images(store1, imagesfp)
1549
 
        self.remap_images(entry, store2, imgs)
1550
 
        with project.dot_project().open("w") as fp:
1551
 
            entry.dump(fp)
1552
 
        return imgs
1553
 
 
1554
 
    def save_images(self, model, source):
1555
 
        saved = {}
1556
 
        for name, destination, save in iter_model(model):
1557
 
            if save:
1558
 
                fp = source.child(name)
1559
 
                try:
1560
 
                    fp.moveTo(destination)
1561
 
                except OSError as e:
1562
 
                    if e.errno == errno.ENOENT:
1563
 
                        logger.error(image_not_exists, source=fp.path,
1564
 
                                     destination=destination.path)
1565
 
                        continue
1566
 
                    else:
1567
 
                        raise
1568
 
                else:
1569
 
                    saved[name] = destination
1570
 
        return saved
1571
 
 
1572
 
    def remap_images(self, entry, store, saved):
1573
 
        for name, destination in saved.iteritems():
1574
 
            entry.remap_image(name, destination.path)
1575
 
        for name, path in iter_model(store):
1576
 
            entry.remap_image(name, path.path)
1577
 
            saved[name] = path
1578
 
 
1579
 
    def rebase_all(self, project, images, entry):
1580
 
        lst = []
1581
 
        for name, path in images.iteritems():
1582
 
            for vmname, dev in entry.device_for_image(name):
1583
 
                cow_name = "{0}_{1}.cow".format(vmname, dev)
1584
 
                cow = project.filepath.child(cow_name)
1585
 
                if cow.exists():
1586
 
                    logger.debug(log_rebase, cow=cow.path, basefile=path.path)
1587
 
                    lst.append(self.rebase(path.path, cow.path))
1588
 
        return defer.DeferredList(lst)
1589
 
 
1590
 
    def rebase(self, backing_file, cow, run=utils.getProcessOutputAndValue):
1591
 
        args = ["rebase", "-u", "-b", backing_file, cow]
1592
 
        d = run("qemu-img", args, os.environ)
1593
 
        return d.addCallback(complain_on_error)
1594
 
 
1595
 
    def check_rebase(self, result):
1596
 
        for success, status in result:
1597
 
            if not success:
1598
 
                logger.error(rebase_error, log_failure=status)
1599
 
 
1600
 
 
1601
 
class ImportDialog(Window):
1602
 
 
1603
 
    resource = "data/importdialog.ui"
1604
 
    NAME, PATH, SELECTED = range(3)
1605
 
    archive_path = None
1606
 
    project = None
1607
 
    images = None
1608
 
    humble = _HumbleImport()
1609
 
 
1610
 
    def __init__(self, factory):
1611
 
        Window.__init__(self)
1612
 
        self.factory = factory
1613
 
 
1614
 
    @property
1615
 
    def assistant(self):
1616
 
        return self.builder.get_object("ImportDialog")
1617
 
 
1618
 
    def show(self, parent=None):
1619
 
        col1 = self.get_object("pathcolumn1")
1620
 
        cell1 = self.get_object("cellrenderertext2")
1621
 
        col1.set_cell_data_func(cell1, set_path, 1)
1622
 
        col2 = self.get_object("pathcolumn2")
1623
 
        cell2 = self.get_object("cellrenderertext4")
1624
 
        col2.set_cell_data_func(cell2, set_path, 1)
1625
 
        view1 = self.get_object("treeview1")
1626
 
        view1.connect("button_press_event", self.on_button_press_event, col1,
1627
 
                      self.get_save_filechooserdialog)
1628
 
        view2 = self.get_object("treeview2")
1629
 
        view2.connect("button_press_event", self.on_button_press_event, col2,
1630
 
                      self.get_map_filechooserdialog)
1631
 
        Window.show(self, parent)
1632
 
 
1633
 
    def destroy(self):
1634
 
        self.assistant.destroy()
1635
 
 
1636
 
    # assistant method helpers
1637
 
 
1638
 
    def set_page_complete(self, page=None, complete=True):
1639
 
        if page is None:
1640
 
            page = self.assistant.get_nth_page(
1641
 
                self.assistant.get_current_page())
1642
 
        self.assistant.set_page_complete(page, complete)
1643
 
 
1644
 
    ####
1645
 
 
1646
 
    def get_project_name(self):
1647
 
        return self.get_object("prjname_entry").get_text()
1648
 
 
1649
 
    def set_project_name(self, name):
1650
 
        self.get_object("prjname_entry").set_text(name)
1651
 
 
1652
 
    def get_archive_path(self):
1653
 
        return self.get_object("filechooserbutton").get_filename()
1654
 
 
1655
 
    def get_open(self):
1656
 
        return self.get_object("opencheckbutton").get_active()
1657
 
 
1658
 
    def get_overwrite(self):
1659
 
        return self.get_object("overwritecheckbutton").get_active()
1660
 
 
1661
 
    def get_filechooserdialog(self, model, path, title, action, stock_id):
1662
 
        chooser = gtk.FileChooserDialog(title, self.window, action,
1663
 
                (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
1664
 
                 stock_id, gtk.RESPONSE_OK))
1665
 
        chooser.set_modal(True)
1666
 
        chooser.set_select_multiple(False)
1667
 
        chooser.set_transient_for(self.window)
1668
 
        chooser.set_destroy_with_parent(True)
1669
 
        chooser.set_position(gtk.WIN_POS_CENTER)
1670
 
        chooser.set_do_overwrite_confirmation(True)
1671
 
        chooser.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
1672
 
        chooser.connect("response", self.on_filechooserdialog_response, model,
1673
 
                        path)
1674
 
        return chooser
1675
 
 
1676
 
    def get_save_filechooserdialog(self, model, path):
1677
 
        return self.get_filechooserdialog(model, path, _("Save image as..."),
1678
 
                                           gtk.FILE_CHOOSER_ACTION_SAVE,
1679
 
                                           gtk.STOCK_SAVE)
1680
 
 
1681
 
    def get_map_filechooserdialog(self, model, path):
1682
 
        return self.get_filechooserdialog(model, path, _("Map image as..."),
1683
 
                                           gtk.FILE_CHOOSER_ACTION_OPEN,
1684
 
                                           gtk.STOCK_OPEN)
1685
 
 
1686
 
    # callbacks
1687
 
 
1688
 
    def on_liststore2_row_changed(self, model, path, iter):
1689
 
        self.assistant.set_page_complete(self.assistant.get_nth_page(3),
1690
 
            all(path for name, path in iter_model(model)))
1691
 
 
1692
 
    def on_ImportDialog_prepare(self, assistant, page):
1693
 
        page_num = assistant.get_current_page()
1694
 
        if page_num == 0:
1695
 
            pass
1696
 
        elif page_num == 1:
1697
 
            ws = settings.get("workspace")
1698
 
            deferred = self.humble.step_1(self, self.get_object("liststore1"),
1699
 
                    filepath.FilePath(ws).child("vimages"))
1700
 
            if deferred:
1701
 
                ProgressBar(self.assistant).wait_for(deferred)
1702
 
        elif page_num == 2:
1703
 
            self.humble.step_2(self, self.get_object("liststore1"),
1704
 
                               self.get_object("liststore2"))
1705
 
        elif page_num == 3:
1706
 
            self.humble.step_3(self)
1707
 
        else:
1708
 
            logger.error(invalid_step_assitant, num=page_num)
1709
 
        return True
1710
 
 
1711
 
    def on_ImportDialog_cancel(self, assistant):
1712
 
        if self.project:
1713
 
            logger.info(removing_temporary_project, path=self.project.path)
1714
 
            self.project.delete()
1715
 
        assistant.destroy()
1716
 
        return True
1717
 
 
1718
 
    def on_ImportDialog_apply(self, assistant):
1719
 
        deferred = self.humble.apply(self.project, self.get_project_name(),
1720
 
                                     self.factory, self.get_overwrite(),
1721
 
                                     self.get_open(),
1722
 
                                     self.get_object("liststore1"),
1723
 
                                     self.get_object("liststore2"))
1724
 
        ProgressBar(self.assistant).wait_for(deferred)
1725
 
        deferred.addBoth(pass_through(assistant.destroy))
1726
 
        return True
1727
 
 
1728
 
    def on_filechooserbutton_file_set(self, filechooser):
1729
 
        filename = filechooser.get_filename()
1730
 
        name = os.path.splitext(os.path.basename(filename))[0]
1731
 
        if not self.get_project_name():
1732
 
            self.set_project_name(name)
1733
 
        return True
1734
 
 
1735
 
    def on_prjname_entry_changed(self, entry):
1736
 
        self.set_import_sensitive(self.get_archive_path(), entry.get_text(),
1737
 
                                  self.get_object("overwritecheckbutton"))
1738
 
        return True
1739
 
 
1740
 
    def on_overwritecheckbutton_toggled(self, checkbutton):
1741
 
        self.set_import_sensitive(self.get_archive_path(),
1742
 
                                  self.get_object("prjname_entry").get_text(),
1743
 
                                  checkbutton)
1744
 
        return True
1745
 
 
1746
 
    def set_import_sensitive(self, filename, name, overwrite_btn):
1747
 
        page = self.get_object("intro_page")
1748
 
        label = self.get_object("warn_label")
1749
 
        if name in set(project.manager):
1750
 
            overwrite_btn.set_visible(True)
1751
 
            overwrite = overwrite_btn.get_active()
1752
 
            label.set_visible(not overwrite)
1753
 
            self.set_page_complete(page, overwrite)
1754
 
        else:
1755
 
            overwrite_btn.set_active(False)
1756
 
            overwrite_btn.set_visible(False)
1757
 
            label.set_visible(False)
1758
 
            if filename and name:
1759
 
                self.set_page_complete(page, True)
1760
 
            else:
1761
 
                self.set_page_complete(page, False)
1762
 
 
1763
 
    def on_cellrenderertoggle1_toggled(self, renderer, path):
1764
 
        model = self.get_object("liststore1")
1765
 
        active = renderer.get_active()
1766
 
        model.set(model.get_iter(path), self.SELECTED, not active)
1767
 
        return True
1768
 
 
1769
 
    def on_button_press_event(self, treeview, event, column, dialog_factory):
1770
 
        if event.button == 1:
1771
 
            x = int(event.x)
1772
 
            y = int(event.y)
1773
 
            pthinfo = treeview.get_path_at_pos(x, y)
1774
 
            if pthinfo is not None and pthinfo[1] is column:
1775
 
                path, col = pthinfo[:2]
1776
 
                treeview.grab_focus()
1777
 
                treeview.set_cursor(path, col, 0)
1778
 
                model = treeview.get_model()
1779
 
                chooser = dialog_factory(model, path)
1780
 
                itr = model.get_iter(path)
1781
 
                filename = model.get_value(itr, self.PATH)
1782
 
                if filename is not None:
1783
 
                    if not chooser.set_filename(filename.path):
1784
 
                        chooser.set_current_name(filename.basename())
1785
 
                chooser.show()
1786
 
                return True
1787
 
 
1788
 
    def on_filechooserdialog_response(self, dialog, response_id, model, path):
1789
 
        if response_id == gtk.RESPONSE_OK:
1790
 
            filename = dialog.get_filename()
1791
 
            if filename is not None:
1792
 
                model.set_value(model.get_iter(path), self.PATH,
1793
 
                                filepath.FilePath(filename))
1794
 
        dialog.destroy()
1795
 
        return True
1796
 
 
1797
 
 
1798
 
class SaveAsDialog(Window):
1799
 
 
1800
 
    resource = "data/saveas.ui"
1801
 
    home = filepath.FilePath(settings.DEFAULT_HOME)
1802
 
 
1803
 
    def __init__(self, factory, projects):
1804
 
        Window.__init__(self)
1805
 
        self.factory = factory
1806
 
        self.model = model = self.get_object("liststore1")
1807
 
        for prj in projects:
1808
 
            model.append((prj, ))
1809
 
 
1810
 
    def get_project_name(self):
1811
 
        return self.get_object("name_entry").get_text()
1812
 
 
1813
 
    def set_invalid(self, invalid):
1814
 
        self.get_object("ok_button").set_sensitive(not invalid)
1815
 
 
1816
 
    def on_name_entry_changed(self, entry):
1817
 
        name = entry.get_text()
1818
 
        try:
1819
 
            self.home.child(name)
1820
 
        except filepath.InsecurePath:
1821
 
            self.set_invalid(True)
1822
 
        else:
1823
 
            model = self.model
1824
 
            itr = model.get_iter_root()
1825
 
            while itr:
1826
 
                if model.get_value(itr, 0) == name:
1827
 
                    self.set_invalid(True)
1828
 
                    break
1829
 
                itr = model.iter_next(itr)
1830
 
            else:
1831
 
                self.set_invalid(False)
1832
 
 
1833
 
    @destroy_on_exit
1834
 
    def on_response(self, dialog, response_id):
1835
 
        if response_id == gtk.RESPONSE_OK:
1836
 
            project.current.save_as(self.get_project_name(), self.factory)