Lior Vernia has uploaded a new change for review. Change subject: webadmin: Small improvements to AddRemoveRowWidget ......................................................................
webadmin: Small improvements to AddRemoveRowWidget Made the following changes in AddRemoveRowWidget: * Added an event listener to the edited model's items changed event, which seems to be logic common to any usage of the widget. * Removed the setItems() call from the flush() method, which turned out to be a bad idea for various reasons, and instead just update the current items of the model. * Changed onAdd() and onRemove() to commit changes to the model by default, which seems to be useful. * Changed onAdd() and onRemove() to only be invoked when triggered by a button press (e.g. not during initialization of the widget), the semantics of which would be much more intuitive to anyone who might inherit from this widget. Change-Id: I645c6984f9bcfe6f00f8b7eb07313f4d4f3d7043 Signed-off-by: Lior Vernia <[email protected]> --- M frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/AddRemoveRowWidget.java M frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/profile/ProfilesInstanceTypeEditor.java M frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/uicommon/popup/AbstractVmPopupWidget.java M frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/Linq.java M frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/EditProfileBehavior.java M frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/NewProfileBehavior.java M frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/presenter/popup/AbstractNetworkPopupPresenterWidget.java M frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/popup/AbstractNetworkPopupView.java M frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/popup/NewNetworkPopupView.java M frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/popup/datacenter/EditNetworkPopupView.java M frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/widget/vnicProfile/VnicProfilesEditor.java 11 files changed, 119 insertions(+), 120 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/27/21627/1 diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/AddRemoveRowWidget.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/AddRemoveRowWidget.java index c4ec9d5..cedeb4f 100644 --- a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/AddRemoveRowWidget.java +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/AddRemoveRowWidget.java @@ -1,6 +1,6 @@ package org.ovirt.engine.ui.common.widget; -import java.util.ArrayList; +import java.util.Collection; import java.util.LinkedList; import java.util.List; @@ -8,6 +8,10 @@ import org.ovirt.engine.ui.common.CommonApplicationResources; import org.ovirt.engine.ui.common.widget.uicommon.popup.AbstractModelBoundPopupWidget; import org.ovirt.engine.ui.uicommonweb.models.ListModel; +import org.ovirt.engine.ui.uicompat.Event; +import org.ovirt.engine.ui.uicompat.EventArgs; +import org.ovirt.engine.ui.uicompat.IEventListener; + import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Style.Float; import com.google.gwt.event.dom.client.ClickEvent; @@ -47,39 +51,77 @@ @UiField public WidgetStyle style; - protected CommonApplicationResources resources = GWT.create(CommonApplicationResources.class); + private CommonApplicationResources resources = GWT.create(CommonApplicationResources.class); private final List<Pair<T, V>> items; + private final IEventListener itemsChangedListener; + private M model; + private Collection<T> modelItems; public AddRemoveRowWidget() { items = new LinkedList<Pair<T, V>>(); + itemsChangedListener = new IEventListener() { + + @Override + public void eventRaised(Event ev, Object sender, EventArgs args) { + init(model); + } + }; } - protected void init(ListModel model) { + /** + * This method initializes the entries of the widget, by creating an entry for each value in the backing model and + * an additional "ghost" entry. It is called whenever this widget is edited or an ItemsChangedEvent is raised from + * the backing model. + * + * @param model + * the model backing this widget. + */ + protected void init(M model) { items.clear(); contentPanel.clear(); - Iterable<T> values = model.getItems(); - if (values != null) { - for (T value : values) { - addEntry(value); - } + + modelItems = (Collection<T>) model.getItems(); + if (modelItems == null) { + modelItems = new LinkedList<T>(); + model.setItems(modelItems); // this will invoke init() again with the empty list as values instead of null + return; + } + + for (T value : modelItems) { + addEntry(value); } addGhostEntry(); } - protected void flush(ListModel model) { - ArrayList<T> values = new ArrayList<T>(); + @Override + public void edit(final M model) { + // guard against multiple calls to edit() + if (this.model != null) { + this.model.getItemsChangedEvent().removeListener(itemsChangedListener); + } + + this.model = model; + model.getItemsChangedEvent().addListener(itemsChangedListener); + init(model); + } + + @Override + public M flush() { + modelItems.clear(); for (Pair<T, V> item : items) { T value = item.getFirst(); if (!isGhost(value)) { - values.add(value); + modelItems.add(value); } } - model.setItems(values); + return model; } private void addGhostEntry() { - addEntry(createGhostValue()); + T value = createGhostValue(); + addEntry(value); + modelItems.add(value); } private void addEntry(T value) { @@ -108,18 +150,18 @@ AddRemoveRowPanel entry = new AddRemoveRowPanel(widget, button); contentPanel.add(entry); - - onAdd(value, widget); } private void removeEntry(Pair<T, V> item) { items.remove(item); contentPanel.remove(item.getSecond().getParent()); - onRemove(item.getFirst(), item.getSecond()); } private PushButton createButton(final Pair<T, V> item) { - boolean ghostItem = isGhost(item.getFirst()); + final T value = item.getFirst(); + final V widget = item.getSecond(); + + boolean ghostItem = isGhost(value); final PushButton button = new PushButton(new Image(ghostItem ? resources.increaseIcon() : resources.decreaseIcon())); button.addStyleName(style.buttonStyle()); @@ -130,8 +172,9 @@ @Override public void onClick(ClickEvent event) { - ((AddRemoveRowPanel) item.getSecond().getParent()).swapButton(createButton(item)); + ((AddRemoveRowPanel) widget.getParent()).swapButton(createButton(item)); addGhostEntry(); + onAdd(value, widget); } } : new ClickHandler() { @@ -139,6 +182,7 @@ @Override public void onClick(ClickEvent event) { removeEntry(item); + onRemove(value, widget); } }); @@ -173,19 +217,9 @@ } /** - * This method is called straight after an entry is removed. Override to specify implementation-specific behavior. - * - * @param value - * the value removed. - * @param widget - * the widget removed. - */ - protected void onRemove(T value, V widget) { - // do nothing - } - - /** - * This method is called straight after an entry is added. Override to specify implementation-specific behavior. + * This method is called straight after an entry is added by pressing the plus button. Note that this new entry will + * necessarily be a "ghost" entry, as the plus button always adds entries that are initially in ghost state. + * Override to specify implementation-specific behavior. * * @param value * the value added. @@ -197,6 +231,20 @@ } /** + * This method is called straight after an entry is removed by pressing the minues button. Note that this entry will + * necessarily be a non-"ghost" entry, as otherwise the minus button would have been disabled. Override to specify + * implementation-specific behavior. + * + * @param value + * the value removed. + * @param widget + * the widget removed. + */ + protected void onRemove(T value, V widget) { + modelItems.remove(value); + } + + /** * This method should return a new widget of type V backed by a value of type T. * * @param value diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/profile/ProfilesInstanceTypeEditor.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/profile/ProfilesInstanceTypeEditor.java index 0ef1443..02c2824 100644 --- a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/profile/ProfilesInstanceTypeEditor.java +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/profile/ProfilesInstanceTypeEditor.java @@ -1,12 +1,5 @@ package org.ovirt.engine.ui.common.widget.profile; -import com.google.gwt.core.client.GWT; -import com.google.gwt.editor.client.SimpleBeanEditorDriver; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.Widget; import java.util.ArrayList; import java.util.List; @@ -18,9 +11,14 @@ import org.ovirt.engine.ui.uicommonweb.dataprovider.AsyncDataProvider; import org.ovirt.engine.ui.uicommonweb.models.ListModel; import org.ovirt.engine.ui.uicommonweb.models.vms.VnicInstanceType; -import org.ovirt.engine.ui.uicompat.Event; -import org.ovirt.engine.ui.uicompat.EventArgs; -import org.ovirt.engine.ui.uicompat.IEventListener; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.editor.client.SimpleBeanEditorDriver; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Widget; public class ProfilesInstanceTypeEditor extends AddRemoveRowWidget<ListModel, VnicInstanceType, ProfileInstanceTypeEditor> implements HasElementId { @@ -41,11 +39,9 @@ private static final CommonApplicationMessages messages = GWT.create(CommonApplicationMessages.class); - private ListModel vnicsModel; private ListModel profilesModel; - private IEventListener vnicsChangedListener; private Iterable<VnicProfileView> vnicProfiles; - private List<VmNetworkInterface> vnics; + private final List<VmNetworkInterface> vnics; private int realEntryCount; public ProfilesInstanceTypeEditor() { @@ -59,40 +55,30 @@ this.elementId = elementId; } - public void edit(ListModel vnicsModel, ListModel profilesModel) { - this.vnicsModel = vnicsModel; - this.profilesModel = profilesModel; - - vnicsChangedListener = new IEventListener() { - - @Override - public void eventRaised(Event ev, Object sender, EventArgs args) { - init(); - } - }; - vnicsModel.getItemsChangedEvent().addListener(vnicsChangedListener); - - driver.edit(vnicsModel); - init(); - } - - private void init() { + @Override + protected void init(ListModel model) { vnicProfiles = profilesModel.getItems(); if (vnicProfiles == null) { vnicProfiles = new ArrayList<VnicProfileView>(); } + Iterable<VnicInstanceType> values = model.getItems(); vnics.clear(); - realEntryCount = 0; - Iterable<VnicInstanceType> values = vnicsModel.getItems(); if (values != null) { for (VnicInstanceType value : values) { vnics.add(value.getNetworkInterface()); } } - updateHeaderLabel(); + super.init(model); - super.init(vnicsModel); + realEntryCount = vnics.size() - 1; // don't count the ghost entry + updateHeaderLabel(); + } + + public void edit(ListModel vnicsModel, ListModel profilesModel) { + this.profilesModel = profilesModel; + super.edit(vnicsModel); + driver.edit(vnicsModel); } /** @@ -107,10 +93,7 @@ @Override public ListModel flush() { - vnicsModel.getItemsChangedEvent().removeListener(vnicsChangedListener); // remove to avoid calling init() here - flush(vnicsModel); - vnicsModel.getItemsChangedEvent().addListener(vnicsChangedListener); // put back in case dialog wasn't closed - + super.flush(); return driver.flush(); } @@ -127,11 +110,7 @@ @Override protected void onAdd(VnicInstanceType value, ProfileInstanceTypeEditor widget) { super.onAdd(value, widget); - vnics.add(value.getNetworkInterface()); - if (isGhost(value)) { - // this will be offset when the entry is toggled to ghost - ++realEntryCount; - } + ++realEntryCount; // necessarily a ghost entry, but this will be offset when the entry is toggled to ghost updateHeaderLabel(); } @@ -139,7 +118,7 @@ protected void onRemove(VnicInstanceType value, ProfileInstanceTypeEditor widget) { super.onRemove(value, widget); vnics.remove(value.getNetworkInterface()); - --realEntryCount; + --realEntryCount; // necessarily a real entry updateHeaderLabel(); } @@ -178,9 +157,6 @@ item.profileEditor.setEnabled(!becomingGhost); item.profileEditor.asWidget().setEnabled(true); - if (!becomingGhost) { - vnics.add(value.getNetworkInterface()); - } realEntryCount += (becomingGhost ? -1 : 1); updateHeaderLabel(); } diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/uicommon/popup/AbstractVmPopupWidget.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/uicommon/popup/AbstractVmPopupWidget.java index d20fe53..78ed7ca 100644 --- a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/uicommon/popup/AbstractVmPopupWidget.java +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/uicommon/popup/AbstractVmPopupWidget.java @@ -1044,6 +1044,7 @@ priorityEditor.setRowData(new ArrayList<EntityModel>()); priorityEditor.asEditor().edit(model.getPriority()); driver.edit(model); + profilesInstanceTypeEditor.edit(model.getNicsWithLogicalNetworks(), model.getVnicProfiles()); initTabAvailabilityListeners(model); initListeners(model); initCustomPropertySheet(model); @@ -1172,8 +1173,6 @@ } } }); - - profilesInstanceTypeEditor.edit(object.getNicsWithLogicalNetworks(), object.getVnicProfiles()); } /** diff --git a/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/Linq.java b/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/Linq.java index de3f2ad..c3df1a4 100644 --- a/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/Linq.java +++ b/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/Linq.java @@ -592,15 +592,6 @@ return list; } - public static <TSource> boolean containsByIdentity(Iterable<TSource> source, Object item) { - for (TSource sourceItem : source) { - if (sourceItem == item) { - return true; - } - } - return false; - } - public static Version selectHighestVersion(ArrayList<Version> versions) { Version retVersion = firstOrDefault(versions); diff --git a/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/EditProfileBehavior.java b/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/EditProfileBehavior.java index 472b918..0b545fe 100644 --- a/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/EditProfileBehavior.java +++ b/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/EditProfileBehavior.java @@ -27,6 +27,8 @@ return; } } + + profileList.setSelectedItem(VnicProfileView.EMPTY); } } diff --git a/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/NewProfileBehavior.java b/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/NewProfileBehavior.java index fbfba70..11eb0e0 100644 --- a/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/NewProfileBehavior.java +++ b/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/models/vms/NewProfileBehavior.java @@ -24,7 +24,7 @@ return; } } - profileList.setSelectedItem(profiles.size() > 0 ? profiles.get(0) : null); + profileList.setSelectedItem(profiles.size() > 0 ? profiles.get(0) : VnicProfileView.EMPTY); } } diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/presenter/popup/AbstractNetworkPopupPresenterWidget.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/presenter/popup/AbstractNetworkPopupPresenterWidget.java index 8e75074..fc8e780 100644 --- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/presenter/popup/AbstractNetworkPopupPresenterWidget.java +++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/presenter/popup/AbstractNetworkPopupPresenterWidget.java @@ -58,7 +58,7 @@ } }); - IEventListener updateProfilesListener = new IEventListener() { + model.getDataCenters().getSelectedItemChangedEvent().addListener(new IEventListener() { @Override public void eventRaised(Event ev, Object sender, EventArgs args) { StoragePool dc = model.getSelectedDc(); @@ -67,9 +67,7 @@ dc.getId(), model.getDefaultProfile()); } - }; - model.getDataCenters().getSelectedItemChangedEvent().addListener(updateProfilesListener); - model.getProfiles().getItemsChangedEvent().addListener(updateProfilesListener); + }); } @Override diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/popup/AbstractNetworkPopupView.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/popup/AbstractNetworkPopupView.java index 1d92701..b0d8489 100644 --- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/popup/AbstractNetworkPopupView.java +++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/popup/AbstractNetworkPopupView.java @@ -365,6 +365,12 @@ } @Override + public T flush() { + profilesEditor.flush(); + return null; + } + + @Override public void updateVisibility() { messageLabel.setVisible(false); } diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/popup/NewNetworkPopupView.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/popup/NewNetworkPopupView.java index 7c47593..913ff47 100644 --- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/popup/NewNetworkPopupView.java +++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/popup/NewNetworkPopupView.java @@ -46,6 +46,7 @@ @Override public NewNetworkModel flush() { + super.flush(); return driver.flush(); } diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/popup/datacenter/EditNetworkPopupView.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/popup/datacenter/EditNetworkPopupView.java index 2597954..33d1805 100644 --- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/popup/datacenter/EditNetworkPopupView.java +++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/popup/datacenter/EditNetworkPopupView.java @@ -49,6 +49,7 @@ @Override public EditNetworkModel flush() { + super.flush(); return driver.flush(); } diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/widget/vnicProfile/VnicProfilesEditor.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/widget/vnicProfile/VnicProfilesEditor.java index 9529d8c..2d70d7a 100644 --- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/widget/vnicProfile/VnicProfilesEditor.java +++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/widget/vnicProfile/VnicProfilesEditor.java @@ -1,11 +1,8 @@ package org.ovirt.engine.ui.webadmin.widget.vnicProfile; -import java.util.Collection; - import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.Version; import org.ovirt.engine.ui.common.widget.AddRemoveRowWidget; -import org.ovirt.engine.ui.uicommonweb.Linq; import org.ovirt.engine.ui.uicommonweb.models.ListModel; import org.ovirt.engine.ui.uicommonweb.models.profiles.NewVnicProfileModel; import org.ovirt.engine.ui.uicommonweb.models.profiles.VnicProfileModel; @@ -26,7 +23,6 @@ WidgetUiBinder uiBinder = GWT.create(WidgetUiBinder.class); } - private Collection<VnicProfileModel> profiles; private Version dcCompatibilityVersion; private Guid dcId; private VnicProfileModel defaultProfile; @@ -38,11 +34,10 @@ public void edit(ListModel model, Version dcCompatibilityVersion, Guid dcId, VnicProfileModel defaultProfile) { driver.edit(model); - profiles = (Collection<VnicProfileModel>) model.getItems(); this.dcCompatibilityVersion = dcCompatibilityVersion; this.dcId = dcId; this.defaultProfile = defaultProfile; - init(model); + super.edit(model); } /** @@ -55,6 +50,7 @@ } public ListModel flush() { + super.flush(); return driver.flush(); } @@ -83,25 +79,6 @@ protected void toggleGhost(VnicProfileModel value, VnicProfileWidget widget, boolean becomingGhost) { widget.publicUseEditor.setEnabled(!becomingGhost && value.getPublicUse().getIsChangable()); widget.networkQoSEditor.setEnabled(!becomingGhost && value.getNetworkQoS().getIsChangable()); - - // commit change to model without triggering items changed event - if (profiles != null) { - if (becomingGhost) { - profiles.remove(value); - } else if (!Linq.containsByIdentity(profiles, value)) { - profiles.add(value); - } - } - } - - @Override - protected void onRemove(VnicProfileModel value, VnicProfileWidget widget) { - super.onRemove(value, widget); - - // commit change to model without triggering items changed event - if (profiles != null) { - profiles.remove(value); - } } } -- To view, visit http://gerrit.ovirt.org/21627 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I645c6984f9bcfe6f00f8b7eb07313f4d4f3d7043 Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Lior Vernia <[email protected]> _______________________________________________ Engine-patches mailing list [email protected] http://lists.ovirt.org/mailman/listinfo/engine-patches
