1 /*
2  * PackagesPreferencesPane.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 
16 
17 package org.rstudio.studio.client.workbench.prefs.views;
18 
19 import com.google.gwt.dom.client.Style.Unit;
20 import com.google.gwt.core.client.JsArray;
21 import com.google.gwt.event.dom.client.ClickHandler;
22 import com.google.gwt.resources.client.ImageResource;
23 import com.google.gwt.user.client.ui.CheckBox;
24 import com.google.gwt.user.client.ui.HorizontalPanel;
25 import com.google.inject.Inject;
26 
27 import java.util.ArrayList;
28 
29 import org.rstudio.core.client.BrowseCap;
30 import org.rstudio.core.client.Debug;
31 import org.rstudio.core.client.ElementIds;
32 import org.rstudio.core.client.prefs.RestartRequirement;
33 import org.rstudio.core.client.resources.ImageResource2x;
34 import org.rstudio.core.client.theme.DialogTabLayoutPanel;
35 import org.rstudio.core.client.theme.VerticalTabPanel;
36 import org.rstudio.core.client.widget.FormLabel;
37 import org.rstudio.core.client.widget.InfoBar;
38 import org.rstudio.core.client.widget.MessageDialog;
39 import org.rstudio.core.client.widget.TextBoxWithButton;
40 import org.rstudio.studio.client.common.GlobalDisplay;
41 import org.rstudio.studio.client.common.HelpLink;
42 import org.rstudio.studio.client.common.PackagesHelpLink;
43 import org.rstudio.studio.client.common.SimpleRequestCallback;
44 import org.rstudio.studio.client.common.mirrors.DefaultCRANMirror;
45 import org.rstudio.studio.client.common.mirrors.model.CRANMirror;
46 import org.rstudio.studio.client.common.mirrors.model.MirrorsServerOperations;
47 import org.rstudio.studio.client.common.repos.SecondaryReposWidget;
48 import org.rstudio.studio.client.server.ServerError;
49 import org.rstudio.studio.client.workbench.model.Session;
50 import org.rstudio.studio.client.workbench.prefs.model.UserPrefs;
51 
52 public class PackagesPreferencesPane extends PreferencesPane
53 {
54    @Inject
PackagesPreferencesPane(PreferencesDialogResources res, GlobalDisplay globalDisplay, UserPrefs uiPrefs, Session session, final DefaultCRANMirror defaultCRANMirror, MirrorsServerOperations server)55    public PackagesPreferencesPane(PreferencesDialogResources res,
56                                   GlobalDisplay globalDisplay,
57                                   UserPrefs uiPrefs,
58                                   Session session,
59                                   final DefaultCRANMirror defaultCRANMirror,
60                                   MirrorsServerOperations server)
61    {
62       res_ = res;
63       globalDisplay_ = globalDisplay;
64       server_ = server;
65 
66       secondaryReposWidget_ = new SecondaryReposWidget();
67 
68       VerticalTabPanel management = new VerticalTabPanel(ElementIds.PACKAGE_MANAGEMENT_PREFS);
69       VerticalTabPanel development = new VerticalTabPanel(ElementIds.PACKAGE_DEVELOPMENT_PREFS);
70 
71       management.add(headerLabel("Package Management"));
72 
73       infoBar_ = new InfoBar(InfoBar.WARNING);
74       infoBar_.setText("CRAN repositories modified outside package preferences.");
75       infoBar_.addStyleName(res_.styles().themeInfobar());
76       spaced(infoBar_);
77 
78       ClickHandler selectPrimaryRepo = (clickEvent) ->
79       {
80          defaultCRANMirror.choose(cranMirror ->
81          {
82             cranMirror_ = cranMirror;
83             cranMirrorTextBox_.setText(cranMirror_.getDisplay());
84 
85             secondaryReposWidget_.setCranRepoUrl(
86                   cranMirror_.getURL(),
87                   cranMirror_.isCustom()
88             );
89          });
90       };
91 
92       cranMirrorTextBox_ = new TextBoxWithButton(
93             "Primary CRAN repository:",
94             "",
95             "Change...",
96             null,
97             ElementIds.TextBoxButtonId.PRIMARY_CRAN,
98             true,
99             selectPrimaryRepo);
100 
101       cranMirrorTextBox_.getTextBox().addValueChangeHandler(event ->
102       {
103          if (!event.getValue().equals(cranMirror_.getDisplay()))
104          {
105             secondaryReposWidget_.setCranRepoUrl(event.getValue(), true);
106          }
107       });
108 
109       nudgeRight(cranMirrorTextBox_);
110       textBoxWithChooser(cranMirrorTextBox_);
111       cranMirrorTextBox_.setText("");
112 
113       if (session.getSessionInfo().getAllowCRANReposEdit())
114       {
115          management.add(infoBar_);
116 
117          lessSpaced(cranMirrorTextBox_);
118          management.add(cranMirrorTextBox_);
119 
120          FormLabel secondaryReposLabel = new FormLabel(
121                "Secondary repositories:",
122                secondaryReposWidget_.getLabeledWidget());
123          secondaryReposLabel.getElement().getStyle().setMarginLeft(2, Unit.PX);
124          secondaryReposLabel.getElement().getStyle().setMarginBottom(2, Unit.PX);
125 
126          management.add(spacedBefore(secondaryReposLabel));
127          management.add(secondaryReposWidget_);
128       }
129 
130       CheckBox chkEnablePackages = checkboxPref("Enable packages pane",
131          uiPrefs.packagesPaneEnabled());
132 
133       chkEnablePackages.addValueChangeHandler(event -> reloadRequired_ = true);
134 
135       if (!session.getSessionInfo().getDisablePackages())
136       {
137          management.add(spacedBefore(chkEnablePackages));
138       }
139 
140       useSecurePackageDownload_ = new CheckBox(
141             "Use secure download method for HTTP");
142       HorizontalPanel secureDownloadPanel = checkBoxWithHelp(
143                         useSecurePackageDownload_, "secure_download", "Help on secure package downloads for R");
144       lessSpaced(secureDownloadPanel);
145       management.add(secureDownloadPanel);
146 
147       useInternet2_ = new CheckBox(
148                         "Use Internet Explorer library/proxy for HTTP",
149                         true);
150       if (BrowseCap.isWindowsDesktop())
151       {
152          lessSpaced(chkEnablePackages);
153          spaced(useInternet2_);
154          management.add(useInternet2_);
155       }
156       else
157       {
158          spaced(useSecurePackageDownload_);
159          useSecurePackageDownload_.getElement().getStyle().setMarginBottom(12, Unit.PX);
160       }
161 
162       management.add(spacedBefore(new HelpLink("Managing Packages", "managing_packages")));
163 
164       development.add(headerLabel("Package Development"));
165 
166       useDevtools_ = new CheckBox("Use devtools package functions if available");
167       lessSpaced(useDevtools_);
168       development.add(useDevtools_);
169 
170       development.add(checkboxPref("Save all files prior to building packages", uiPrefs.saveFilesBeforeBuild()));
171       development.add(checkboxPref("Automatically navigate editor to build errors", uiPrefs.navigateToBuildError()));
172 
173       hideObjectFiles_ = new CheckBox("Hide object files in package src directory");
174       lessSpaced(hideObjectFiles_);
175       development.add(hideObjectFiles_);
176 
177       cleanupAfterCheckSuccess_ = new CheckBox("Cleanup output after successful R CMD check");
178       lessSpaced(cleanupAfterCheckSuccess_);
179       development.add(cleanupAfterCheckSuccess_);
180 
181       viewDirAfterCheckFailure_ = new CheckBox("View Rcheck directory after failed R CMD check");
182       lessSpaced(viewDirAfterCheckFailure_);
183       development.add(viewDirAfterCheckFailure_);
184 
185       development.add(checkboxPref("Use Rcpp template when creating C++ files", uiPrefs.useRcppTemplate()));
186 
187       useNewlineInMakefiles_ = new CheckBox("Always use LF line-endings in Unix Makefiles");
188       lessSpaced(useNewlineInMakefiles_);
189       development.add(useNewlineInMakefiles_);
190 
191       HelpLink packagesHelpLink = new PackagesHelpLink();
192       packagesHelpLink.getElement().getStyle().setMarginTop(12, Unit.PX);
193       nudgeRight(packagesHelpLink);
194       development.add(packagesHelpLink);
195 
196       cranMirrorTextBox_.setEnabled(false);
197       useInternet2_.setEnabled(false);
198       cleanupAfterCheckSuccess_.setEnabled(false);
199       viewDirAfterCheckFailure_.setEnabled(false);
200       hideObjectFiles_.setEnabled(false);
201       useDevtools_.setEnabled(false);
202       useSecurePackageDownload_.setEnabled(false);
203 
204       DialogTabLayoutPanel tabPanel = new DialogTabLayoutPanel("Packages");
205       tabPanel.setSize("435px", "533px");
206       tabPanel.add(management, "Management", management.getBasePanelId());
207       tabPanel.add(development, "Development", development.getBasePanelId());
208       tabPanel.selectTab(0);
209       add(tabPanel);
210    }
211 
212    @Override
getIcon()213    public ImageResource getIcon()
214    {
215       return new ImageResource2x(res_.iconPackages2x());
216    }
217 
218    @Override
validate()219    public boolean validate()
220    {
221       return true;
222    }
223 
224    @Override
getName()225    public String getName()
226    {
227       return "Packages";
228    }
229 
230    @Override
initialize(UserPrefs prefs)231    protected void initialize(UserPrefs prefs)
232    {
233       cranMirrorTextBox_.setEnabled(true);
234       CRANMirror mirror = prefs.cranMirror().getValue().cast();
235       if (!mirror.isEmpty())
236       {
237          cranMirror_ = mirror;
238 
239          secondaryReposWidget_.setCranRepoUrl(
240             cranMirror_.getURL(),
241             cranMirror_.isCustom()
242          );
243 
244          cranMirrorTextBox_.setText(cranMirror_.getDisplay());
245 
246          cranMirrorStored_ = cranMirrorTextBox_.getTextBox().getText();
247 
248          secondaryReposWidget_.setRepos(cranMirror_.getSecondaryRepos());
249       }
250 
251       useInternet2_.setEnabled(true);
252       useInternet2_.setValue(prefs.useInternet2().getValue());
253       useInternet2_.addValueChangeHandler(event -> globalDisplay_.showMessage(
254             MessageDialog.INFO,
255             "Restart R Required",
256             "You must restart your R session for this setting " +
257             "to take effect.")
258       );
259 
260       cleanupAfterCheckSuccess_.setEnabled(true);
261       cleanupAfterCheckSuccess_.setValue(prefs.cleanupAfterRCmdCheck().getValue());
262 
263       viewDirAfterCheckFailure_.setEnabled(true);
264       viewDirAfterCheckFailure_.setValue(prefs.viewDirAfterRCmdCheck().getValue());
265 
266       hideObjectFiles_.setEnabled(true);
267       hideObjectFiles_.setValue(prefs.hideObjectFiles().getValue());
268 
269       useDevtools_.setEnabled(true);
270       useDevtools_.setValue(prefs.useDevtools().getValue());
271 
272       useSecurePackageDownload_.setEnabled(true);
273       useSecurePackageDownload_.setValue(prefs.useSecureDownload().getValue());
274 
275       useNewlineInMakefiles_.setEnabled(true);
276       useNewlineInMakefiles_.setValue(prefs.useNewlinesInMakefiles().getValue());
277 
278       server_.getCRANActives(
279          new SimpleRequestCallback<JsArray<CRANMirror>>() {
280             @Override
281             public void onResponseReceived(JsArray<CRANMirror> mirrors)
282             {
283                boolean cranDiffers = false;
284 
285                ArrayList<CRANMirror> secondary = cranMirror_.getSecondaryRepos();
286 
287                if (secondary.size() + 1 != mirrors.length() || mirrors.length() == 0)
288                {
289                   cranDiffers = true;
290                }
291                else
292                {
293                   // First entry should always be CRAN when set by preferences
294                   if (!mirrors.get(0).getName().equals("CRAN") ||
295                       !mirrors.get(0).getURL().equals(cranMirror_.getURL())) {
296                      cranDiffers = true;
297                   }
298                   for(int i=1; i<mirrors.length(); i++)
299                   {
300                      if (!mirrors.get(i).getName().equals(secondary.get(i-1).getName()) ||
301                          !mirrors.get(i).getURL().equals(secondary.get(i-1).getURL()))
302                      {
303                         cranDiffers = true;
304                         break;
305                      }
306                   }
307                }
308 
309                if (cranDiffers)
310                {
311                   infoBar_.addStyleName(res_.styles().themeInfobarShowing());
312                }
313             }
314 
315             @Override
316             public void onError(ServerError error)
317             {
318                Debug.logError(error);
319             }
320          }
321       );
322    }
323 
secondaryReposHasChanged()324    private boolean secondaryReposHasChanged()
325    {
326       ArrayList<CRANMirror> secondaryRepos = secondaryReposWidget_.getRepos();
327 
328       if (secondaryRepos.size() != cranMirror_.getSecondaryRepos().size())
329          return true;
330 
331       for (int i = 0; i < secondaryRepos.size(); i++)
332       {
333          if (secondaryRepos.get(i).getSecondary() != cranMirror_.getSecondaryRepos().get(i).getSecondary())
334             return true;
335       }
336 
337       return false;
338    }
339 
340    @Override
onApply(UserPrefs prefs)341    public RestartRequirement onApply(UserPrefs prefs)
342    {
343       RestartRequirement restartRequirement = super.onApply(prefs);
344 
345       if (reloadRequired_)
346          restartRequirement.setUiReloadRequired(true);
347 
348       String mirrorTextValue = cranMirrorTextBox_.getTextBox().getText();
349 
350       boolean cranRepoChanged = !mirrorTextValue.equals(cranMirrorStored_);
351       boolean cranRepoChangedToUrl = cranRepoChanged &&
352                                       mirrorTextValue.startsWith("http");
353 
354       if (cranRepoChanged || secondaryReposHasChanged())
355       {
356          if (cranRepoChangedToUrl)
357          {
358             cranMirror_.setURL(mirrorTextValue);
359 
360             cranMirror_.setAsCustom();
361          }
362       }
363 
364       ArrayList<CRANMirror> repos = secondaryReposWidget_.getRepos();
365       cranMirror_.setSecondaryRepos(repos);
366 
367       prefs.cranMirror().setGlobalValue(cranMirror_);
368       prefs.useInternet2().setGlobalValue(useInternet2_.getValue());
369       prefs.cleanupAfterRCmdCheck().setGlobalValue(cleanupAfterCheckSuccess_.getValue());
370       prefs.viewDirAfterRCmdCheck().setGlobalValue(viewDirAfterCheckFailure_.getValue());
371       prefs.hideObjectFiles().setGlobalValue(hideObjectFiles_.getValue());
372       prefs.useDevtools().setGlobalValue(useDevtools_.getValue());
373       prefs.useSecureDownload().setGlobalValue(useSecurePackageDownload_.getValue());
374       prefs.useNewlinesInMakefiles().setGlobalValue(useNewlineInMakefiles_.getValue());
375       prefs.cranMirror().setGlobalValue(cranMirror_);
376 
377       return restartRequirement;
378    }
379 
380    private final PreferencesDialogResources res_;
381    private final GlobalDisplay globalDisplay_;
382    private final MirrorsServerOperations server_;
383    private final InfoBar infoBar_;
384 
385    private CRANMirror cranMirror_ = CRANMirror.empty();
386    private final CheckBox useInternet2_;
387    private TextBoxWithButton cranMirrorTextBox_;
388    private final CheckBox cleanupAfterCheckSuccess_;
389    private final CheckBox viewDirAfterCheckFailure_;
390    private final CheckBox hideObjectFiles_;
391    private final CheckBox useDevtools_;
392    private final CheckBox useSecurePackageDownload_;
393    private final CheckBox useNewlineInMakefiles_;
394    private boolean reloadRequired_ = false;
395    private String cranMirrorStored_;
396    private final SecondaryReposWidget secondaryReposWidget_;
397 }
398