1 /* 2 * FileDialog.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.core.client.files.filedialog; 16 17 import com.google.gwt.aria.client.DialogRole; 18 import com.google.gwt.event.logical.shared.SelectionEvent; 19 20 import org.rstudio.core.client.StringUtil; 21 import org.rstudio.core.client.files.FileSystemContext; 22 import org.rstudio.core.client.files.FileSystemItem; 23 import org.rstudio.core.client.widget.ProgressOperationWithInput; 24 25 public abstract class FileDialog extends FileSystemDialog 26 { FileDialog(String title, String caption, DialogRole role, String buttonName, boolean promptOnOverwrite, boolean allowNonexistentFile, boolean allowFolderCreation, FileSystemContext context, String filter, ProgressOperationWithInput<FileSystemItem> operation)27 protected FileDialog(String title, 28 String caption, 29 DialogRole role, 30 String buttonName, 31 boolean promptOnOverwrite, 32 boolean allowNonexistentFile, 33 boolean allowFolderCreation, 34 FileSystemContext context, 35 String filter, 36 ProgressOperationWithInput<FileSystemItem> operation) 37 { 38 super(title, caption, role, buttonName, context, filter, allowFolderCreation, 39 operation); 40 41 promptOnOverwrite_ = promptOnOverwrite; 42 allowNonexistentFile_ = allowNonexistentFile; 43 } 44 45 /** 46 * This should ONLY be called from the accept() method, otherwise the 47 * filename may contain invalid data which could throw an exception. 48 * @return 49 */ 50 @Override getSelectedItem()51 protected FileSystemItem getSelectedItem() 52 { 53 return context_.itemForName(browser_.getFilename(), false, false); 54 } 55 56 @Override getFilenameLabel()57 public String getFilenameLabel() 58 { 59 return "File name"; 60 } 61 62 /** 63 * Validate the current state of the dialog. Subclasses can override 64 * this but super.shouldAccept() MUST be the last validation that occurs. 65 * @return true if the dialog is in a valid state for acceptance 66 */ shouldAccept()67 protected boolean shouldAccept() 68 { 69 // It's possible the user has typed the name of a file, but hasn't 70 // actually selected a file in the browser. If the user has typed 71 // a filename, then prefer using that over what the previously 72 // selected file might have been. 73 // 74 // Note that selecting an item in the file widget will also update 75 // the browser filename. 76 String filename = browser_.getFilename().trim(); 77 if (StringUtil.isNullOrEmpty(filename)) 78 filename = browser_.getSelectedValue().trim(); 79 80 if (filename.length() == 0) 81 return false; 82 83 // Make sure the browser's notion of the filename is in sync. 84 browser_.setFilename(filename); 85 86 int lastIndex = filename.lastIndexOf('/'); 87 if (lastIndex >= 0) 88 { 89 String dir = filename.substring(0, lastIndex); 90 if (dir.length() == 0) 91 dir = "/"; 92 String file = filename.substring(lastIndex + 1); 93 94 // Targeted fix for "611: Permission denied error when attempting to 95 // browse /shared folder in open file dialog". The /shared folder 96 // doesn't have list permissions. 97 if (dir.equals("/shared")) 98 { 99 cd(filename); 100 return false; 101 } 102 103 browser_.setFilename(file); 104 browser_.setFilenameEnabled(false); 105 attemptAcceptOnNextNavigate_ = true; 106 cd(dir); 107 return false; 108 } 109 110 String filenameValidationError = context_.validatePathElement(filename, true); 111 if (filenameValidationError != null) 112 { 113 browser_.selectFilename(); 114 showError(filenameValidationError); 115 return false; 116 } 117 118 if (navigateIfDirectory()) 119 return false; 120 121 boolean useExactFilename = 122 browser_.getSelectedValue() != null && 123 browser_.getSelectedValue() == filename; 124 125 if (!useExactFilename || getAlwaysMungeFilename()) 126 { 127 browser_.setFilename(mungeFilename(filename)); 128 } 129 130 if (navigateIfDirectory()) 131 return false; 132 133 FileSystemItem item = context_.itemForName(filename, true, false); 134 if (item == null) 135 { 136 if (!allowNonexistentFile_) 137 { 138 showError("File does not exist"); 139 return false; 140 } 141 } 142 else 143 { 144 if (item.isDirectory()) 145 { 146 assert false : "This case should be covered by navigateIfDirectory"; 147 return false; 148 } 149 else if (promptOnOverwrite_) 150 { 151 /* WARNING. showOverwritePrompt() MAY CAUSE accept() TO BE CALLED 152 DIRECTLY. ALL OTHER VALIDATION *MUST* BE COMPLETE BEFORE 153 CALLING showOverwritePrompt()!!! */ 154 showOverwritePrompt(); 155 return false; 156 } 157 } 158 159 return true; 160 } 161 navigateIfDirectory()162 private boolean navigateIfDirectory() 163 { 164 FileSystemItem item = context_.itemForName(browser_.getFilename(), 165 true, 166 false); 167 if (item != null && item.isDirectory()) 168 { 169 browser_.setFilename(""); 170 cd(item.getName()); 171 return true; 172 } 173 return false; 174 } 175 176 /** 177 * Gives subclasses an opportunity to change the filename before acceptance. 178 * This happens AFTER validation so it's imperative that no potentially 179 * illegal values be returned from this method. 180 */ mungeFilename(String filename)181 protected String mungeFilename(String filename) 182 { 183 return filename; 184 } 185 getAlwaysMungeFilename()186 protected boolean getAlwaysMungeFilename() 187 { 188 return false; 189 } 190 cd(String path)191 protected void cd(String path) 192 { 193 browser_.setFilenameEnabled(false); 194 browser_.cd(path); 195 } 196 197 @Override onNavigated()198 public void onNavigated() 199 { 200 super.onNavigated(); 201 202 browser_.setFilenameEnabled(true); 203 if (attemptAcceptOnNextNavigate_) 204 { 205 attemptAcceptOnNextNavigate_ = false; 206 maybeAccept(); 207 } 208 } 209 210 @Override focusInitialControl()211 protected void focusInitialControl() 212 { 213 browser_.setFilenameFocus(true); 214 browser_.selectFilename(); 215 } 216 217 @Override onSelection(SelectionEvent<FileSystemItem> event)218 public void onSelection(SelectionEvent<FileSystemItem> event) 219 { 220 super.onSelection(event); 221 222 FileSystemItem item = event.getSelectedItem(); 223 if (item != null && !item.isDirectory()) 224 browser_.setFilename(item.getName()); 225 } 226 227 @Override onError(String errorMessage)228 public void onError(String errorMessage) 229 { 230 attemptAcceptOnNextNavigate_ = false; 231 super.onError(errorMessage); 232 } 233 234 protected boolean promptOnOverwrite_; 235 protected boolean allowNonexistentFile_; 236 private boolean attemptAcceptOnNextNavigate_ = false; 237 }