1 /* 2 * ChooseMirrorDialog.java 3 * 4 * Copyright (C) 2021 by RStudio, PBC 5 * 6 * Unless you have received this program directly from RStudio pursuant 7 * to the terms of a commercial license agreement with RStudio, then 8 * this program is licensed to you under the terms of version 3 of the 9 * GNU Affero General Public License. This program is distributed WITHOUT 10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. 13 * 14 */ 15 package org.rstudio.studio.client.common.mirrors; 16 17 import java.util.ArrayList; 18 19 import com.google.gwt.aria.client.Roles; 20 import org.rstudio.core.client.Debug; 21 import org.rstudio.core.client.StringUtil; 22 import org.rstudio.core.client.widget.FormLabel; 23 import org.rstudio.core.client.widget.ModalDialog; 24 import org.rstudio.core.client.widget.OperationWithInput; 25 import org.rstudio.core.client.widget.ProgressIndicator; 26 import org.rstudio.core.client.widget.SimplePanelWithProgress; 27 import org.rstudio.core.client.widget.images.ProgressImages; 28 import org.rstudio.studio.client.common.GlobalDisplay; 29 import org.rstudio.studio.client.common.SimpleRequestCallback; 30 import org.rstudio.studio.client.common.mirrors.model.CRANMirror; 31 import org.rstudio.studio.client.common.mirrors.model.MirrorsServerOperations; 32 import org.rstudio.studio.client.server.ServerDataSource; 33 import org.rstudio.studio.client.server.ServerError; 34 import org.rstudio.studio.client.server.ServerRequestCallback; 35 36 import com.google.gwt.core.client.GWT; 37 import com.google.gwt.core.client.JsArray; 38 import com.google.gwt.dom.client.Style.Unit; 39 import com.google.gwt.event.dom.client.DoubleClickEvent; 40 import com.google.gwt.event.dom.client.DoubleClickHandler; 41 import com.google.gwt.resources.client.ClientBundle; 42 import com.google.gwt.resources.client.CssResource; 43 import com.google.gwt.user.client.ui.ListBox; 44 import com.google.gwt.user.client.ui.TextBox; 45 import com.google.gwt.user.client.ui.VerticalPanel; 46 import com.google.gwt.user.client.ui.Widget; 47 48 public class ChooseMirrorDialog extends ModalDialog<CRANMirror> 49 { 50 public interface Source 51 extends ServerDataSource<JsArray<CRANMirror>> 52 { getLabel(CRANMirror mirror)53 String getLabel(CRANMirror mirror); getURL(CRANMirror mirror)54 String getURL(CRANMirror mirror); 55 } 56 ChooseMirrorDialog(GlobalDisplay globalDisplay, Source mirrorSource, OperationWithInput<CRANMirror> inputOperation, MirrorsServerOperations mirrorOperations)57 public ChooseMirrorDialog(GlobalDisplay globalDisplay, 58 Source mirrorSource, 59 OperationWithInput<CRANMirror> inputOperation, 60 MirrorsServerOperations mirrorOperations) 61 { 62 super("Retrieving list of CRAN mirrors...", Roles.getDialogRole(), inputOperation); 63 globalDisplay_ = globalDisplay; 64 mirrorSource_ = mirrorSource; 65 mirrorOperations_ = mirrorOperations; 66 progressIndicator_ = addProgressIndicator(false); 67 } 68 69 @Override collectInput()70 protected CRANMirror collectInput() 71 { 72 if (!StringUtil.isNullOrEmpty(customTextBox_.getText())) 73 { 74 CRANMirror cranMirror = CRANMirror.empty(); 75 cranMirror.setURL(customTextBox_.getText()); 76 77 cranMirror.setAsCustom(); 78 79 return cranMirror; 80 } 81 else if (listBox_ != null && listBox_.getSelectedIndex() >= 0) 82 { 83 return mirrors_.get(listBox_.getSelectedIndex()); 84 } 85 else 86 { 87 return null; 88 } 89 } 90 validateSync(CRANMirror input)91 private boolean validateSync(CRANMirror input) 92 { 93 if (input == null) 94 { 95 globalDisplay_.showErrorMessage("Error", 96 "Please select a CRAN Mirror"); 97 return false; 98 } 99 else 100 { 101 return true; 102 } 103 } 104 105 @Override validateAsync(CRANMirror input, OperationWithInput<Boolean> onValidated)106 protected void validateAsync(CRANMirror input, 107 OperationWithInput<Boolean> onValidated) 108 { 109 if (!validateSync(input)) 110 { 111 onValidated.execute(false); 112 return; 113 } 114 115 if (input.isCustom()) 116 { 117 progressIndicator_.onProgress("Validating CRAN repository..."); 118 119 mirrorOperations_.validateCranRepo( 120 new ServerRequestCallback<Boolean>() 121 { 122 public void onResponseReceived(Boolean validated) 123 { 124 progressIndicator_.onCompleted(); 125 126 if (!validated) 127 { 128 progressIndicator_.onError("The given URL does not appear to be a valid CRAN repository"); 129 onValidated.execute(false); 130 } 131 else 132 { 133 onValidated.execute(true); 134 } 135 } 136 137 @Override 138 public void onError(ServerError error) 139 { 140 progressIndicator_.onCompleted(); 141 142 Debug.logError(error); 143 progressIndicator_.onError(error.getMessage()); 144 145 onValidated.execute(false); 146 } 147 }, 148 input.getURL()); 149 } 150 else 151 { 152 onValidated.execute(true); 153 } 154 } 155 156 @Override createMainWidget()157 protected Widget createMainWidget() 158 { 159 VerticalPanel root = new VerticalPanel(); 160 161 customTextBox_ = new TextBox(); 162 customTextBox_.setStylePrimaryName(RESOURCES.styles().customRepo()); 163 FormLabel customLabel = new FormLabel("Custom:", customTextBox_); 164 root.add(customLabel); 165 root.add(customTextBox_); 166 167 FormLabel mirrorsLabel = new FormLabel("CRAN Mirrors:"); 168 mirrorsLabel.getElement().getStyle().setMarginTop(8, Unit.PX); 169 root.add(mirrorsLabel); 170 171 // create progress container 172 final SimplePanelWithProgress panel = new SimplePanelWithProgress( 173 ProgressImages.createLargeGray()); 174 root.add(panel); 175 176 panel.setStylePrimaryName(RESOURCES.styles().mainWidget()); 177 178 // show progress (with delay) 179 panel.showProgress(200); 180 181 // query data source for packages 182 mirrorSource_.requestData(new SimpleRequestCallback<JsArray<CRANMirror>>() { 183 184 @Override 185 public void onResponseReceived(JsArray<CRANMirror> mirrors) 186 { 187 // keep internal list of mirrors 188 mirrors_ = new ArrayList<>(mirrors.length()); 189 190 // create list box and select default item 191 listBox_ = new ListBox(); 192 listBox_.setMultipleSelect(false); 193 listBox_.setVisibleItemCount(18); // all 194 listBox_.setWidth("100%"); 195 if (mirrors.length() > 0) 196 { 197 for(int i=0; i<mirrors.length(); i++) 198 { 199 CRANMirror mirror = mirrors.get(i); 200 if (mirrorSource_.getLabel(mirror).startsWith("0-Cloud")) 201 continue; 202 mirrors_.add(mirror); 203 String item = mirrorSource_.getLabel(mirror); 204 String value = mirrorSource_.getURL(mirror); 205 206 listBox_.addItem(item, value); 207 } 208 209 listBox_.setSelectedIndex(0); 210 } 211 212 mirrorsLabel.setFor(listBox_); 213 214 // set it into the panel 215 panel.setWidget(listBox_); 216 217 // set caption 218 setText("Choose Primary Repository"); 219 220 // update ok button on changed 221 listBox_.addDoubleClickHandler(new DoubleClickHandler() { 222 @Override 223 public void onDoubleClick(DoubleClickEvent event) 224 { 225 clickOkButton(); 226 } 227 }); 228 229 // if the list box is larger than the space we initially allocated 230 // then increase the panel height 231 final int kDefaultPanelHeight = 265; 232 if (listBox_.getOffsetHeight() > kDefaultPanelHeight) 233 panel.setHeight(listBox_.getOffsetHeight() + "px"); 234 } 235 236 @Override 237 public void onError(ServerError error) 238 { 239 closeDialog(); 240 super.onError(error); 241 } 242 }); 243 244 return root; 245 } 246 247 static interface Styles extends CssResource 248 { mainWidget()249 String mainWidget(); customRepo()250 String customRepo(); 251 } 252 253 static interface Resources extends ClientBundle 254 { 255 @Source("ChooseMirrorDialog.css") styles()256 Styles styles(); 257 } 258 259 static Resources RESOURCES = (Resources)GWT.create(Resources.class); ensureStylesInjected()260 public static void ensureStylesInjected() 261 { 262 RESOURCES.styles().ensureInjected(); 263 } 264 265 private final GlobalDisplay globalDisplay_; 266 private final Source mirrorSource_; 267 private ArrayList<CRANMirror> mirrors_ = null; 268 private ListBox listBox_ = null; 269 private TextBox customTextBox_ = null; 270 private final MirrorsServerOperations mirrorOperations_; 271 private final ProgressIndicator progressIndicator_; 272 } 273