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 }