1 /* TemporaryInternetFilesPanel.java -- Display and sets cache settings. 2 Copyright (C) 2010 Red Hat 3 4 This program is free software; you can redistribute it and/or modify 5 it under the terms of the GNU General Public License as published by 6 the Free Software Foundation; either version 2 of the License, or 7 (at your option) any later version. 8 9 This program is distributed in the hope that it will be useful, but 10 WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 General Public License for more details. 13 14 You should have received a copy of the GNU General Public License 15 along with this program; if not, write to the Free Software 16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 */ 18 19 package net.sourceforge.jnlp.controlpanel; 20 21 import static java.lang.Integer.parseInt; 22 import static java.lang.Long.parseLong; 23 24 import java.awt.BorderLayout; 25 import java.awt.GridBagConstraints; 26 import java.awt.GridBagLayout; 27 import java.awt.event.ActionEvent; 28 import java.awt.event.ActionListener; 29 import java.awt.event.ComponentAdapter; 30 import java.awt.event.ComponentEvent; 31 import java.awt.event.ItemEvent; 32 import java.awt.event.ItemListener; 33 import java.io.File; 34 import java.util.ArrayList; 35 import java.util.List; 36 37 import javax.swing.JButton; 38 import javax.swing.JCheckBox; 39 import javax.swing.JComboBox; 40 import javax.swing.JFileChooser; 41 import javax.swing.JLabel; 42 import javax.swing.JOptionPane; 43 import javax.swing.JPanel; 44 import javax.swing.JSpinner; 45 import javax.swing.JTextField; 46 import javax.swing.SpinnerNumberModel; 47 import javax.swing.event.ChangeEvent; 48 import javax.swing.event.ChangeListener; 49 50 import net.sourceforge.jnlp.config.DeploymentConfiguration; 51 import net.sourceforge.jnlp.config.PathsAndFiles; 52 import net.sourceforge.jnlp.runtime.Translator; 53 54 /** 55 * The actual panel that contains the fields that the user can edit accordingly. 56 * This is provided as a pane for inside the Panel itself, can also be used to 57 * display as a dialog. 58 * TODO: Add functionality: 59 * 60 * @author Andrew Su (asu@redhat.com, andrew.su@utoronto.ca) 61 * 62 */ 63 @SuppressWarnings("serial") 64 public class TemporaryInternetFilesPanel extends NamedBorderPanel { 65 66 private static final Long CACHE_UNLIMITED_SIZE = -1l; 67 private static final Long CACHE_MIN_SIZE = 0l; 68 private static final Long CACHE_MAX_SIZE = (long) Integer.MAX_VALUE; 69 private static final Long SPINNER_STEP_SIZE = 10l; 70 private final JSpinner cacheSizeSpinner; 71 72 private static final long BYTES_TO_MEGABYTES = 1024l * 1024l; 73 74 private final JCheckBox limitCacheSizeCheckBox; 75 private final JLabel cacheSizeWarningLabel; 76 private final DeploymentConfiguration config; 77 private final JComboBox<ComboItem> cbCompression; 78 private final JButton bLocation; 79 private final JButton resetLocation; 80 private final JTextField location; 81 private final JLabel locationDescription; 82 private final JLabel lCompression; 83 private final JLabel lCacheSize; 84 private final JButton bViewFiles; 85 private final JPanel diskSpacePanel; 86 TemporaryInternetFilesPanel(final DeploymentConfiguration config)87 public TemporaryInternetFilesPanel(final DeploymentConfiguration config) { 88 super(Translator.R("CPHeadTempInternetFiles")); 89 this.config = config; 90 setLayout(new BorderLayout()); 91 cacheSizeSpinner = new JSpinner(); 92 limitCacheSizeCheckBox = new JCheckBox(Translator.R("TIFPLimitCacheSize")); 93 cacheSizeWarningLabel = new JLabel(); 94 lCacheSize = new JLabel(Translator.R("TIFPCacheSize") + ":"); 95 96 ComboItem[] compressionOptions = {new ComboItem(Translator.R("TIFPNone"), "0"), 97 new ComboItem("1", "1"), 98 new ComboItem("2", "2"), 99 new ComboItem("3", "3"), 100 new ComboItem("4", "4"), 101 new ComboItem("5", "5"), 102 new ComboItem("6", "6"), 103 new ComboItem("7", "7"), 104 new ComboItem("8", "8"), 105 new ComboItem(Translator.R("TIFPMax"), "9"),}; 106 cbCompression = new JComboBox<>(compressionOptions); 107 lCompression = new JLabel(Translator.R("TIFPCompressionLevel") + ":"); // Sets compression level for jar files. 108 109 bLocation = new JButton(Translator.R("TIFPChange") + "..."); 110 resetLocation = new JButton(Translator.R("CPFilesLogsDestDirResert")); 111 location = new JTextField(PathsAndFiles.CACHE_DIR.getFullPath(config)); 112 locationDescription = new JLabel(Translator.R("TIFPLocationLabel") + ":"); 113 bViewFiles = new JButton(Translator.R("TIFPViewFiles")); 114 115 diskSpacePanel = new JPanel(); 116 diskSpacePanel.setLayout(new GridBagLayout()); 117 118 addComponents(); 119 if (limitCacheSizeCheckBox.isSelected()) { 120 showCacheSizeSpinnerGUIElements(true); 121 122 if (parseLong(cacheSizeSpinner.getValue().toString()) == 0) { 123 showCompressionAndLocationGUIElements(false); 124 } else { 125 showCompressionAndLocationGUIElements(true); 126 } 127 128 } else { 129 showCacheSizeSpinnerGUIElements(false); 130 showCompressionAndLocationGUIElements(true); 131 } 132 } 133 134 /** 135 * Add components to panel. 136 */ addComponents()137 private void addComponents() { 138 GridBagConstraints c = new GridBagConstraints(); 139 c.fill = GridBagConstraints.BOTH; 140 141 JLabel description = new JLabel(Translator.R("CPTempInternetFilesDescription")); 142 143 // This section deals with how to use the disk space. 144 cbCompression.setSelectedIndex(parseInt(this.config.getProperty(DeploymentConfiguration.KEY_CACHE_COMPRESSION_ENABLED))); 145 cbCompression.addItemListener(new ItemListener() { 146 @Override 147 public void itemStateChanged(ItemEvent e) { 148 config.setProperty(DeploymentConfiguration.KEY_CACHE_COMPRESSION_ENABLED, ((ComboItem) e.getItem()).getValue()); 149 } 150 }); 151 152 //Override getNextValue and getPreviousValue to make it jump to the closest increment/decrement of step size 153 final Long configCacheSize = parseLong(this.config.getProperty(DeploymentConfiguration.KEY_CACHE_MAX_SIZE)); 154 final Long initialCacheSize = configCacheSize < CACHE_MIN_SIZE ? CACHE_MIN_SIZE : configCacheSize; 155 final SpinnerNumberModel snmCacheSize = new PowerOfSpinnerNumberModel(initialCacheSize, TemporaryInternetFilesPanel.CACHE_MIN_SIZE, TemporaryInternetFilesPanel.CACHE_MAX_SIZE, TemporaryInternetFilesPanel.SPINNER_STEP_SIZE); 156 cacheSizeSpinner.setModel(snmCacheSize); 157 158 final SpinnerChangeListener listener = new SpinnerChangeListener(); 159 cacheSizeSpinner.addChangeListener(listener); 160 cacheSizeSpinner.setToolTipText(Translator.R("TIFPCacheSizeSpinnerTooltip", CACHE_MIN_SIZE, CACHE_MAX_SIZE)); 161 162 limitCacheSizeCheckBox.setSelected(configCacheSize >= CACHE_MIN_SIZE); 163 limitCacheSizeCheckBox.addItemListener(new CheckboxItemListener()); 164 165 166 c.gridx = 0; 167 c.weightx = 1; 168 c.gridy = 0; 169 diskSpacePanel.add(limitCacheSizeCheckBox, c); 170 c.gridy = 1; 171 diskSpacePanel.add(lCacheSize, c); 172 c.gridx = 1; 173 c.weightx = 1; 174 c.gridwidth = 2; 175 diskSpacePanel.add(cacheSizeSpinner, c); 176 c.gridwidth = 1; 177 c.gridy = 2; 178 c.gridx = 0; 179 diskSpacePanel.add(cacheSizeWarningLabel, c); 180 181 c.gridx = 0; 182 c.gridy = 3; 183 diskSpacePanel.add(lCompression, c); 184 c.gridx = 1; 185 c.gridwidth = 2; 186 diskSpacePanel.add(cbCompression, c); 187 resetLocation.addActionListener(new ActionListener() { 188 @Override 189 public void actionPerformed(ActionEvent e) { 190 location.setText(PathsAndFiles.CACHE_DIR.getDefaultFullPath()); 191 //background engine is clever. Will reset for us if it will see default path here 192 PathsAndFiles.CACHE_DIR.setValue(PathsAndFiles.CACHE_DIR.getDefaultFullPath(), config); 193 showCacheSizeSpinnerGUIElements(limitCacheSizeCheckBox.isSelected()); 194 } 195 }); 196 // This displays the option for changing location of cache 197 // User can NOT edit the text field must do it through dialog. 198 location.setEditable(false); // Can not c&p into the location field. 199 bLocation.addActionListener(new ActionListener() { 200 @Override 201 public void actionPerformed(ActionEvent e) { 202 JFileChooser fileChooser = new JFileChooser(location.getText()); 203 fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); 204 fileChooser.setFileHidingEnabled(false); 205 fileChooser.setAcceptAllFileFilterUsed(false); 206 fileChooser.setDialogTitle(Translator.R("TIFPLocationLabel")); 207 if (fileChooser.showDialog(null, Translator.R("TIFPFileChooserChooseButton")) == JFileChooser.APPROVE_OPTION) { 208 // Check if we have permission to write to that location. 209 String result = fileChooser.getSelectedFile().getAbsolutePath(); 210 File dirLocation = new File(result); 211 boolean canWrite = dirLocation.canWrite(); 212 while (!canWrite && dirLocation != null) { // File does not exist, or no permission. 213 214 if (dirLocation.exists()) { 215 JOptionPane.showMessageDialog(null, "No permission to write to this location."); 216 return; 217 } 218 219 dirLocation = dirLocation.getParentFile(); 220 canWrite = dirLocation.canWrite(); 221 } 222 223 if (canWrite) { 224 location.setText(result); 225 PathsAndFiles.CACHE_DIR.setValue(result, config); 226 showCacheSizeSpinnerGUIElements(limitCacheSizeCheckBox.isSelected()); 227 } 228 } 229 } 230 }); 231 232 bViewFiles.addActionListener(new ActionListener() { 233 @Override 234 public void actionPerformed(ActionEvent e) { 235 CacheViewer.showCacheDialog(config); 236 } 237 }); 238 239 c.gridy = 4; 240 c.gridx = 0; 241 c.weightx = 1; 242 c.gridwidth = 1; 243 diskSpacePanel.add(locationDescription, c); 244 c.gridy = 5; 245 c.gridwidth = 1; 246 diskSpacePanel.add(location, c); 247 c.gridx = 1; 248 c.weightx = 0.5; 249 diskSpacePanel.add(resetLocation, c); 250 c.gridx = 2; 251 c.weightx = 0.5; 252 diskSpacePanel.add(bLocation, c); 253 c.gridx = 3; 254 diskSpacePanel.add(bViewFiles, c); 255 256 JPanel panel = new JPanel(); 257 panel.setLayout(new BorderLayout()); 258 panel.add(description, BorderLayout.NORTH); 259 panel.add(diskSpacePanel, BorderLayout.CENTER); 260 add(panel, BorderLayout.CENTER); 261 262 this.addComponentListener(new ComponentAdapter() { 263 @Override 264 public void componentShown(final ComponentEvent componentEvent) { 265 listener.stateChanged(null); 266 } 267 }); 268 269 } 270 getCurrentUsableSpace()271 private long getCurrentUsableSpace() { 272 long usableDiskSpace = new File(PathsAndFiles.CACHE_DIR.getFullPath(config)).getUsableSpace() / BYTES_TO_MEGABYTES; // getUsableSpace returns bytes 273 return usableDiskSpace; 274 } 275 276 private static class PowerOfSpinnerNumberModel extends SpinnerNumberModel { 277 private final List<Long> powersOf; 278 PowerOfSpinnerNumberModel(final Long initialCacheSize, final Long cacheMinSize, final Long cacheMaxSize, final Long spinnerStepSize)279 public PowerOfSpinnerNumberModel(final Long initialCacheSize, final Long cacheMinSize, final Long cacheMaxSize, final Long spinnerStepSize) { 280 super(initialCacheSize, cacheMinSize, cacheMaxSize, spinnerStepSize); 281 powersOf = new ArrayList<>(); 282 final int powersListSize = (int) Math.floor(Math.log(cacheMaxSize) / Math.log(spinnerStepSize) + 1); 283 284 for (int i = 0; i < powersListSize; i++) { 285 final long powerOfTen = (long) Math.pow(spinnerStepSize, i); 286 powersOf.add(powerOfTen); 287 } 288 } 289 290 @Override getNextValue()291 public Long getNextValue() { 292 final Number raw = (Number) super.getValue(); 293 if (super.getNextValue() == null) { 294 return (Long) getMaximum(); 295 } 296 297 final Long original = raw.longValue(); 298 final Long result = original - (original % powersOf.get(String.valueOf(original).length() - 1)) + powersOf.get(String.valueOf(original).length() - 1); 299 if (result < (Long) getMaximum()) { 300 return result; 301 } 302 303 return (Long) getMaximum(); 304 } 305 306 @Override getPreviousValue()307 public Long getPreviousValue() { 308 final Number raw = (Number) super.getValue(); 309 final Long original = raw.longValue(); 310 if (super.getPreviousValue() == null) { 311 if (original > 0) { 312 return original - 1; 313 } 314 315 return (Long) getMinimum(); 316 } 317 318 final Long result; 319 if (powersOf.contains(original)) { 320 result = original - powersOf.get(String.valueOf(original).length() - 2); 321 return result; 322 } else { 323 324 if (original % powersOf.get(String.valueOf(original).length() - 1) == 0) { 325 result = original - powersOf.get(String.valueOf(original).length() - 1); 326 } else { 327 result = original - original % powersOf.get(String.valueOf(original).length() - 1); 328 } 329 330 if (result > Long.valueOf(0)) { 331 return result; 332 } 333 } 334 335 return Long.valueOf(0); 336 } 337 338 } 339 340 private class SpinnerChangeListener implements ChangeListener { 341 342 @Override stateChanged(final ChangeEvent e)343 public void stateChanged(final ChangeEvent e) { 344 final long usableDiskSpace = getCurrentUsableSpace(); 345 final long cacheSizeSpinnerValue = (long) cacheSizeSpinner.getValue(); 346 347 if (limitCacheSizeCheckBox.isSelected()) { 348 showCompressionAndLocationGUIElements(true); 349 350 if (cacheSizeSpinnerValue > usableDiskSpace) { 351 cacheSizeWarningLabel.setText(Translator.R("TIFPCacheSizeSpinnerValueTooLargeWarning", usableDiskSpace)); 352 } else if (cacheSizeSpinnerValue == 0) { 353 cacheSizeWarningLabel.setText(Translator.R("TIFPCacheSizeSetToNoCaching")); 354 showCompressionAndLocationGUIElements(false); 355 } else { 356 cacheSizeWarningLabel.setText(Translator.R("TIFPCacheSizeSpinnerLargeValueWarning", usableDiskSpace)); 357 } 358 359 config.setProperty(DeploymentConfiguration.KEY_CACHE_MAX_SIZE, Long.valueOf(cacheSizeSpinnerValue).toString()); 360 } else { 361 showCacheSizeSpinnerGUIElements(false); 362 showCompressionAndLocationGUIElements(true); 363 } 364 } 365 } 366 367 private class CheckboxItemListener implements ItemListener { 368 @Override itemStateChanged(final ItemEvent e)369 public void itemStateChanged(final ItemEvent e) { 370 final boolean selected = e.getStateChange() == ItemEvent.SELECTED; 371 showCacheSizeSpinnerGUIElements(selected); 372 373 if (parseLong(cacheSizeSpinner.getValue().toString()) == 0 && selected) { 374 showCompressionAndLocationGUIElements(false); 375 } else { 376 showCompressionAndLocationGUIElements(true); 377 } 378 379 if (selected) { 380 config.setProperty(DeploymentConfiguration.KEY_CACHE_MAX_SIZE, cacheSizeSpinner.getValue().toString()); 381 } else { 382 config.setProperty(DeploymentConfiguration.KEY_CACHE_MAX_SIZE, Long.toString(CACHE_UNLIMITED_SIZE)); 383 } 384 385 config.setProperty(DeploymentConfiguration.KEY_CACHE_ENABLED, String.valueOf(!selected)); 386 } 387 } 388 showCompressionAndLocationGUIElements(boolean bool)389 private void showCompressionAndLocationGUIElements(boolean bool) { 390 cbCompression.setEnabled(bool); 391 lCompression.setEnabled(bool); 392 resetLocation.setEnabled(bool); 393 bLocation.setEnabled(bool); 394 location.setEnabled(bool); 395 locationDescription.setEnabled(bool); 396 bViewFiles.setEnabled(bool); 397 } 398 showCacheSizeSpinnerGUIElements(boolean bool)399 private void showCacheSizeSpinnerGUIElements(boolean bool){ 400 lCacheSize.setEnabled(bool); 401 cacheSizeSpinner.setEnabled(bool); 402 cacheSizeWarningLabel.setEnabled(bool); 403 long usableDiskSpace = getCurrentUsableSpace(); 404 if(bool == false) { 405 cacheSizeSpinner.setToolTipText(null); 406 cacheSizeWarningLabel.setText(Translator.R("TIFPCacheSizeSpinnerLargeValueWarning", usableDiskSpace)); 407 } else { 408 409 cacheSizeSpinner.setToolTipText(Translator.R("TIFPCacheSizeSpinnerTooltip", CACHE_MIN_SIZE, CACHE_MAX_SIZE)); 410 final long cacheSizeSpinnerValue = (long) cacheSizeSpinner.getValue(); 411 if(cacheSizeSpinnerValue > usableDiskSpace) { 412 cacheSizeWarningLabel.setText(Translator.R("TIFPCacheSizeSpinnerValueTooLargeWarning", usableDiskSpace)); 413 } else if (cacheSizeSpinnerValue == 0) { 414 cacheSizeWarningLabel.setText(Translator.R("TIFPCacheSizeSetToNoCaching")); 415 } 416 } 417 } 418 }