1# Copyright (C) 2008, 2013, 2014 Red Hat, Inc.
2# Copyright (C) 2008 Cole Robinson <crobinso@redhat.com>
3#
4# This work is licensed under the GNU GPLv2 or later.
5# See the COPYING file in the top-level directory.
6
7from gi.repository import Gtk
8
9from virtinst import log
10from virtinst import StorageVolume
11
12from .lib import uiutil
13from .asyncjob import vmmAsyncJob
14from .baseclass import vmmGObjectUI
15from .xmleditor import vmmXMLEditor
16
17
18class vmmCreateVolume(vmmGObjectUI):
19    __gsignals__ = {
20        "vol-created": (vmmGObjectUI.RUN_FIRST, None, [object, object]),
21    }
22
23    def __init__(self, conn, parent_pool):
24        vmmGObjectUI.__init__(self, "createvol.ui", "vmm-create-vol")
25        self.conn = conn
26
27        self._parent_pool = parent_pool
28        self._name_hint = None
29        self._storage_browser = None
30
31        self._xmleditor = vmmXMLEditor(self.builder, self.topwin,
32                self.widget("details-box-align"),
33                self.widget("details-box"))
34        self._xmleditor.connect("xml-requested",
35                self._xmleditor_xml_requested_cb)
36
37        self.builder.connect_signals({
38            "on_vmm_create_vol_delete_event": self.close,
39            "on_vol_cancel_clicked": self.close,
40            "on_vol_create_clicked": self._create_clicked_cb,
41
42            "on_vol_name_changed": self._vol_name_changed_cb,
43            "on_vol_format_changed": self._vol_format_changed_cb,
44            "on_backing_browse_clicked": self._browse_backing_clicked_cb,
45        })
46        self.bind_escape_key_close()
47
48        self._init_state()
49
50
51    #######################
52    # Standard UI methods #
53    #######################
54
55    def show(self, parent):
56        try:
57            parent_xml = self._parent_pool.xmlobj.get_xml()
58        except Exception:  # pragma: no cover
59            log.debug("Error getting parent_pool xml", exc_info=True)
60            parent_xml = None
61
62        log.debug("Showing new volume wizard for parent_pool=\n%s",
63            parent_xml)
64        self._reset_state()
65        self.topwin.set_transient_for(parent)
66        self.topwin.present()
67
68    def close(self, ignore1=None, ignore2=None):
69        log.debug("Closing new volume wizard")
70        self.topwin.hide()
71        if self._storage_browser:
72            self._storage_browser.close()
73        self.set_modal(False)
74        return 1
75
76    def _cleanup(self):
77        self.conn = None
78        self._parent_pool = None
79        self._xmleditor.cleanup()
80        self._xmleditor = None
81
82        if self._storage_browser:
83            self._storage_browser.cleanup()
84            self._storage_browser = None
85
86
87    ##############
88    # Public API #
89    ##############
90
91    def set_name_hint(self, hint):
92        self._name_hint = hint
93
94    def set_modal(self, modal):
95        self.topwin.set_modal(bool(modal))
96
97    def set_parent_pool(self, pool):
98        self._parent_pool = pool
99
100
101    ###########
102    # UI init #
103    ###########
104
105    def _init_state(self):
106        format_list = self.widget("vol-format")
107        format_model = Gtk.ListStore(str, str)
108        format_list.set_model(format_model)
109        uiutil.init_combo_text_column(format_list, 1)
110        for fmt in ["raw", "qcow2"]:
111            format_model.append([fmt, fmt])
112
113    def _reset_state(self):
114        self._xmleditor.reset_state()
115
116        vol = self._make_stub_vol()
117
118        hasformat = vol.supports_format()
119        uiutil.set_grid_row_visible(self.widget("vol-format"), hasformat)
120        uiutil.set_list_selection(self.widget("vol-format"),
121            self.conn.get_default_storage_format())
122
123        self.widget("vol-name").set_text(self._default_vol_name() or "")
124        self.widget("vol-name").grab_focus()
125        self.widget("vol-name").emit("changed")
126
127        self.widget("backing-store").set_text("")
128        self.widget("vol-nonsparse").set_active(
129                not self._should_default_sparse())
130        self._show_sparse()
131        self._show_backing()
132        self.widget("backing-expander").set_expanded(False)
133
134        pool_avail = int(self._parent_pool.get_available() / 1024 / 1024 / 1024)
135        default_cap = 20
136        self.widget("vol-capacity").set_range(0.1, 1000000)
137        self.widget("vol-capacity").set_value(min(default_cap, pool_avail))
138
139        self.widget("vol-parent-info").set_markup(
140                        _("<b>%(volume)s's</b> available space: %(size)s") % {
141                            "volume": self._parent_pool.get_name(),
142                            "size": self._parent_pool.get_pretty_available(),
143                        })
144
145
146    ###################
147    # Helper routines #
148    ###################
149
150    def _get_config_format(self):
151        if not self.widget("vol-format").get_visible():
152            return None
153        return uiutil.get_list_selection(self.widget("vol-format"))
154
155    def _default_vol_name(self):
156        hint = self._name_hint or "vol"
157        suffix = self._default_suffix()
158        ret = ""
159        try:
160            ret = StorageVolume.find_free_name(
161                self.conn.get_backend(),
162                self._parent_pool.get_backend(),
163                hint, suffix=suffix)
164            if ret and suffix:
165                ret = ret.rsplit(".", 1)[0]
166        except Exception:  # pragma: no cover
167            log.exception("Error finding a default vol name")
168
169        return ret
170
171    def _default_suffix(self):
172        vol = self._make_stub_vol()
173        if vol.file_type != vol.TYPE_FILE:
174            return ""
175        return StorageVolume.get_file_extension_for_format(
176            self._get_config_format())
177
178    def _should_default_sparse(self):
179        return self._get_config_format() == "qcow2"
180
181    def _can_sparse(self):
182        dtype = self._parent_pool.xmlobj.get_disk_type()
183        return dtype == StorageVolume.TYPE_FILE
184
185    def _show_sparse(self):
186        uiutil.set_grid_row_visible(
187            self.widget("vol-nonsparse"), self._can_sparse())
188
189    def _can_backing(self):
190        if self._parent_pool.get_type() == "logical":
191            return True
192        if self._get_config_format() == "qcow2":
193            return True
194        return False
195
196    def _show_backing(self):
197        uiutil.set_grid_row_visible(
198            self.widget("backing-expander"), self._can_backing())
199
200    def _browse_file(self):
201        if self._storage_browser is None:
202            def cb(src, text):
203                ignore = src
204                self.widget("backing-store").set_text(text)
205
206            from .storagebrowse import vmmStorageBrowser
207            self._storage_browser = vmmStorageBrowser(self.conn)
208            self._storage_browser.set_finish_cb(cb)
209            self._storage_browser.topwin.set_modal(self.topwin.get_modal())
210            self._storage_browser.set_browse_reason(
211                self.config.CONFIG_DIR_IMAGE)
212
213        self._storage_browser.show(self.topwin)
214
215    def _show_err(self, info, details=None):
216        self.err.show_err(info, details, modal=self.topwin.get_modal())
217
218
219    ###################
220    # Object building #
221    ###################
222
223    def _make_stub_vol(self, xml=None):
224        vol = StorageVolume(self.conn.get_backend(), parsexml=xml)
225        vol.pool = self._parent_pool.get_backend()
226        return vol
227
228    def _build_xmlobj_from_xmleditor(self):
229        xml = self._xmleditor.get_xml()
230        log.debug("Using XML from xmleditor:\n%s", xml)
231        return self._make_stub_vol(xml=xml)
232
233    def _build_xmlobj_from_ui(self):
234        name = self.widget("vol-name").get_text()
235        suffix = self.widget("vol-name-suffix").get_text()
236        volname = name + suffix
237        fmt = self._get_config_format()
238        cap = self.widget("vol-capacity").get_value()
239        nonsparse = self.widget("vol-nonsparse").get_active()
240        backing = self.widget("backing-store").get_text()
241
242        alloc = 0
243        if nonsparse:
244            alloc = cap
245
246        vol = self._make_stub_vol()
247        vol.name = volname
248        vol.capacity = (cap * 1024 * 1024 * 1024)
249        vol.allocation = (alloc * 1024 * 1024 * 1024)
250        if backing:
251            vol.backing_store = backing
252        if fmt:
253            vol.format = fmt
254        return vol
255
256    def _build_xmlobj(self, check_xmleditor):
257        try:
258            xmlobj = self._build_xmlobj_from_ui()
259            if check_xmleditor and self._xmleditor.is_xml_selected():
260                xmlobj = self._build_xmlobj_from_xmleditor()
261            return xmlobj
262        except Exception as e:
263            self.err.show_err(_("Error building XML: %s") % str(e))
264
265
266    ##################
267    # Object install #
268    ##################
269
270    def _pool_refreshed_cb(self, pool, volname):
271        vol = pool.get_volume_by_name(volname)
272        self.emit("vol-created", pool, vol)
273
274    def _finish_cb(self, error, details, vol):
275        self.reset_finish_cursor()
276
277        if error:  # pragma: no cover
278            error = _("Error creating vol: %s") % error
279            self._show_err(error, details=details)
280            return
281        self._parent_pool.connect("refreshed",
282            self._pool_refreshed_cb, vol.name)
283        self.idle_add(self._parent_pool.refresh)
284        self.close()
285
286    def _finish(self):
287        vol = self._build_xmlobj(check_xmleditor=True)
288        if not vol:
289            return
290
291        try:
292            vol.validate()
293        except Exception as e:
294            return self._show_err(_("Error validating volume: %s") % str(e))
295
296        self.set_finish_cursor()
297        progWin = vmmAsyncJob(self._async_vol_create, [vol],
298                              self._finish_cb, [vol],
299                              _("Creating storage volume..."),
300                              _("Creating the storage volume may take a "
301                                "while..."),
302                              self.topwin)
303        progWin.run()
304
305    def _async_vol_create(self, asyncjob, vol):
306        conn = self.conn.get_backend()
307
308        # Lookup different pool obj
309        newpool = conn.storagePoolLookupByName(self._parent_pool.get_name())
310        vol.pool = newpool
311
312        meter = asyncjob.get_meter()
313        log.debug("Starting background vol creation.")
314        vol.install(meter=meter)
315        log.debug("vol creation complete.")
316
317
318    ################
319    # UI listeners #
320    ################
321
322    def _xmleditor_xml_requested_cb(self, src):
323        xmlobj = self._build_xmlobj(check_xmleditor=False)
324        self._xmleditor.set_xml(xmlobj and xmlobj.get_xml() or "")
325
326    def _vol_format_changed_cb(self, src):
327        self._show_sparse()
328        self.widget("vol-nonsparse").set_active(
329                not self._should_default_sparse())
330        self._show_backing()
331        self.widget("vol-name").emit("changed")
332
333    def _vol_name_changed_cb(self, src):
334        text = src.get_text()
335
336        suffix = self._default_suffix()
337        if "." in text:
338            suffix = ""
339        self.widget("vol-name-suffix").set_text(suffix)
340
341    def _browse_backing_clicked_cb(self, src):
342        self._browse_file()
343
344    def _create_clicked_cb(self, src):
345        self._finish()
346