/**
* Copyright (C) 2005 - 2012 Eric Van Dewoestine
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.eclim.installer.step;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Properties;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.filechooser.FileFilter;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.taskdefs.Delete;
import org.apache.tools.ant.taskdefs.condition.Os;
import org.apache.tools.ant.types.FileSet;
import org.formic.InstallContext;
import org.formic.Installer;
import org.formic.util.CommandExecutor;
import org.formic.util.File;
import org.formic.util.dialog.gui.GuiDialogs;
import org.formic.wizard.form.GuiForm;
import org.formic.wizard.form.Validator;
import org.formic.wizard.form.gui.component.FileChooser;
import org.formic.wizard.form.validator.ValidatorBuilder;
import org.formic.wizard.step.AbstractGuiStep;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import foxtrot.Task;
import foxtrot.Worker;
import net.miginfocom.swing.MigLayout;
/**
* Step for choosing the vimfiles directory to install vim scripts in.
*
* @author Eric Van Dewoestine
*/
public class VimStep
extends AbstractGuiStep
{
private static final Logger logger = LoggerFactory.getLogger(VimStep.class);
private static final String[] WINDOWS_VIMS = {
"vim.bat",
"gvim.bat",
"vim.exe",
"gvim.exe",
"C:/Program Files (x86)/Vim/vim73/vim.exe",
"C:/Program Files (x86)/Vim/vim73/gvim.exe",
"C:/Program Files (x86)/Vim/vim72/vim.exe",
"C:/Program Files (x86)/Vim/vim72/gvim.exe",
"C:/Program Files/Vim/vim73/vim.exe",
"C:/Program Files/Vim/vim73/gvim.exe",
"C:/Program Files/Vim/vim72/vim.exe",
"C:/Program Files/Vim/vim72/gvim.exe",
"C:/Program Files/Vim/vim71/vim.exe",
"C:/Program Files/Vim/vim71/gvim.exe",
"C:/Program Files/Vim/vim70/vim.exe",
"C:/Program Files/Vim/vim70/gvim.exe",
};
private static final String[] WINDOWS_GVIMS = {
"C:/Program Files (x86)/Vim/vim73/gvim.exe",
"C:/Program Files (x86)/Vim/vim72/gvim.exe",
"C:/Program Files/Vim/vim73/gvim.exe",
"C:/Program Files/Vim/vim72/gvim.exe",
"C:/Program Files/Vim/vim71/gvim.exe",
"C:/Program Files/Vim/vim70/gvim.exe",
};
private static final String[] UNIX_VIMS = {"vim", "gvim"};
private static final String COMMAND =
"redir! > <file> | silent! echo &rtp | quit";
private JPanel panel;
private FileChooser fileChooser;
private JList dirList;
private JCheckBox skipCheckBox;
private boolean rtpAttempted;
private boolean homeVimCreatePrompted;
private String[] runtimePath;
/**
* Constructs the step.
*/
public VimStep(String name, Properties properties)
{
super(name, properties);
}
/**
* {@inheritDoc}
* @see org.formic.wizard.step.GuiStep#init()
*/
public Component init()
{
GuiForm form = createForm();
String files = fieldName("files");
fileChooser = new FileChooser(JFileChooser.DIRECTORIES_ONLY);
// allow just .vim dirs to not be hidden
fileChooser.getFileChooser().setFileHidingEnabled(false);
fileChooser.getFileChooser().addChoosableFileFilter(new FileFilter(){
public boolean accept(java.io.File f) {
String path = f.getAbsolutePath();
return f.isDirectory() && (
path.matches(".*/\\.vim(/.*|$)") ||
!path.matches(".*/\\..*"));
}
public String getDescription() {
return null;
}
});
String skip = fieldName("skip");
skipCheckBox = new JCheckBox(Installer.getString(skip));
skipCheckBox.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
boolean selected = ((JCheckBox)e.getSource()).isSelected();
JTextField fileField = fileChooser.getTextField();
fileField.setEnabled(!selected);
fileChooser.getButton().setEnabled(!selected);
if (dirList != null){
dirList.setEnabled(!selected);
}
// hacky
Validator validator = (Validator)
fileField.getClientProperty("validator");
setValid(selected || validator.isValid(fileField.getText()));
}
});
panel = new JPanel(new MigLayout(
"wrap 2", "[fill]", "[] [] [] [fill, grow]"));
panel.add(form.createMessagePanel(), "span");
panel.add(new JLabel(Installer.getString(files)), "split");
panel.add(fileChooser, "skip");
panel.add(skipCheckBox, "span");
form.bind(files,
fileChooser.getTextField(),
new ValidatorBuilder()
.required()
.isDirectory()
.fileExists()
.isWritable()
.validator());
return panel;
}
/**
* {@inheritDoc}
* @see org.formic.wizard.WizardStep#displayed()
*/
public void displayed()
{
if(!rtpAttempted){
rtpAttempted = true;
setBusy(true);
try{
runtimePath = (String[])Worker.post(new Task(){
public Object run() throws Exception {
setGvimProperty();
return getVimRuntimePath();
}
});
// filter out dirs the user doesn't have permission write to.
ArrayList<String> filtered = new ArrayList<String>();
if (runtimePath != null){
for (String path : runtimePath){
if (new File(path).canWrite()){
if (Installer.isUninstall()){
File eclimDir = new File(path + "/eclim");
if (eclimDir.exists()){
if (eclimDir.canWrite()){
filtered.add(path);
}else{
logger.warn(
path + "/eclim is not writable by the current user");
}
}
}else{
filtered.add(path);
}
}
}
}
String[] rtp = filtered.toArray(new String[filtered.size()]);
if(rtp == null || rtp.length == 0){
if(!Installer.isUninstall()){
if(!homeVimCreatePrompted){
createUserVimFiles("No suitable vim files directory found.");
}else{
GuiDialogs.showWarning(
"Your vim install is still reporting no\n" +
"suitable vim files directories.\n" +
"You will need to manually specify one.");
}
}
}else{
if(rtp.length == 1){
fileChooser.getTextField().setText(rtp[0]);
// try to discourage windows users from installing eclim files in
// their vim installation.
if(new File(rtp[0] + "/gvim.exe").exists()){
createUserVimFiles("No user vim files directory found.");
}
}else{
dirList = new JList(rtp);
dirList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
JScrollPane scrollPane = new JScrollPane(dirList);
panel.add(scrollPane, "span, grow");
dirList.addListSelectionListener(new ListSelectionListener(){
public void valueChanged(ListSelectionEvent event){
if(!event.getValueIsAdjusting()){
fileChooser.getTextField()
.setText((String)dirList.getSelectedValue());
}
}
});
dirList.setSelectedIndex(0);
}
}
}catch(Exception e){
e.printStackTrace();
}
setBusy(false);
fileChooser.getTextField().grabFocus();
}
}
/**
* {@inheritDoc}
* @see org.formic.wizard.WizardStep#proceed()
*/
public boolean proceed()
{
boolean proceed = super.proceed();
if (proceed){
InstallContext context = Installer.getContext();
context.setValue("vim.skip", Boolean.valueOf(skipCheckBox.isSelected()));
String vimfiles = (String)context.getValue("vim.files");
if (vimfiles != null){
vimfiles = vimfiles.replace('\\', '/');
context.setValue("vim.files", vimfiles);
// Check if the user has the eclim vim files already installed in
// another directory in their vim's runtime path.
// on windows, since case is insensitive, lower the path.
if (Os.isFamily(Os.FAMILY_WINDOWS)){
vimfiles = vimfiles.toLowerCase();
}
if(runtimePath != null && runtimePath.length > 0){
for (String rpath : runtimePath){
String path = rpath;
if (Os.isFamily(Os.FAMILY_WINDOWS)){
path = path.toLowerCase();
}
if (vimfiles.equals(path)){
continue;
}
File fpath = new File(path + "/plugin/eclim.vim");
if (!fpath.exists()){
continue;
}
if (fpath.canWrite()){
boolean remove = GuiDialogs.showConfirm(
"You appear to have one or more of the eclim vim files\n" +
"installed in another directory:\n" +
" " + rpath + "\n" +
"Would you like the installer to remove those files now?");
if (remove){
Delete delete = new Delete();
delete.setProject(Installer.getProject());
delete.setTaskName("delete");
delete.setIncludeEmptyDirs(true);
delete.setFailOnError(true);
FileSet set = new FileSet();
set.setDir(new File(path + "/eclim"));
set.createInclude().setName("**/*");
set.createExclude().setName("after/**/*");
set.createExclude().setName("resources/**/*");
delete.addFileset(set);
try{
boolean deleted = fpath.delete();
if (!deleted){
throw new BuildException(
"Failed to delete file: plugin/eclim.vim");
}
delete.execute();
}catch(BuildException be){
GuiDialogs.showError(
"Failed to delete old eclim vim files:\n" +
" " + be.getMessage() + "\n" +
"You may continue with the installation, but if old eclim\n" +
"vim files remain, chances are that you will receive\n" +
"errors upon starting (g)vim and the older version of\n" +
"the files may take precedence over the ones you are\n" +
"installing now, leading to indeterminate behavior.");
}
}
proceed = remove;
}else{
GuiDialogs.showWarning(
"You appear to have one or more of the eclim vim files\n" +
"installed in another directory:\n" +
" " + rpath + "\n" +
"Unfortunately it seems you do not have write access to\n" +
"that directory. You may continue with the installation,\n" +
"but chances are that you will receive errors upon starting\n" +
"(g)vim and the older version of the files may take precedence\n" +
"over the ones you are installing now, leading to indeterminate\n" +
"behavior.");
}
}
}
}
}
return proceed;
}
/**
* Attempt to find where gvim is installed.
*/
private void setGvimProperty()
{
try{
String[] gvims = null;
if(Os.isFamily(Os.FAMILY_WINDOWS)){
gvims = WINDOWS_GVIMS;
for (String gvim : gvims){
if (new File(gvim).isFile()){
Installer.getProject().setProperty("eclim.gvim", gvim);
break;
}
}
}else{
String vim = Os.isFamily(Os.FAMILY_MAC) ? "mvim" : "gvim";
CommandExecutor executor =
CommandExecutor.execute(new String[]{"which", vim}, 1000);
if(executor.getReturnCode() == 0){
String result = executor.getResult().trim();
logger.info("which " + vim + ": " + result);
Installer.getProject().setProperty("eclim.gvim", result);
}else{
logger.info("which " + vim + ':' +
" out=" + executor.getResult() +
" err=" + executor.getErrorMessage());
}
}
}catch(Exception e){
e.printStackTrace();
}
}
/**
* Prompt the user to create the standard user local vim files directory.
*
* @param message The message indicating the primary reason we're asking them
* if they want to create the user local directory.
*/
private void createUserVimFiles(String message)
{
homeVimCreatePrompted = true;
File vimfiles = new File(
System.getProperty("user.home") + '/' +
(Os.isFamily(Os.FAMILY_WINDOWS) ? "vimfiles" : ".vim"));
System.out.println(
"Checking for user vim files directory: " + vimfiles);
if(!vimfiles.exists()){
boolean create = GuiDialogs.showConfirm(
message + "\n" +
"Would you like to create the standard\n" +
"directory for your system?\n" +
vimfiles);
if(create){
boolean created = vimfiles.mkdir();
if(created){
rtpAttempted = false;
displayed();
}else{
GuiDialogs.showError("Unable to create directory: " + vimfiles);
}
}
}else{
fileChooser.getTextField().setText(
vimfiles.getAbsolutePath().replace('\\', '/'));
}
}
/**
* Attempts to determine available paths in vim's runtime path.
*
* @return Array of paths or null if unable to determine any.
*/
private String[] getVimRuntimePath()
{
try{
java.io.File tempFile = File.createTempFile("eclim_installer", null);
String command = COMMAND.replaceFirst("<file>",
tempFile.getAbsolutePath().replace('\\', '/').replaceAll(" ", "\\ "));
String[] vims = null;
if(Os.isFamily(Os.FAMILY_WINDOWS)){
vims = WINDOWS_VIMS;
}else{
vims = UNIX_VIMS;
}
String[] args = {
null, "-f", "-X",
"-u", "NONE", "-U", "NONE",
"--cmd", command,
};
for (int ii = 0; ii < vims.length; ii++){
args[0] = vims[ii];
CommandExecutor executor = CommandExecutor.execute(args, 5000);
if(executor.getReturnCode() == 0){
return parseVimRuntimePathResults(tempFile);
}
if (executor.isShutdown()){
return null;
}
executor.destroy();
}
}catch(Exception e){
GuiDialogs.showError("Error determining your vim runtime path.", e);
}
return null;
}
/**
* Parses the results of echoing vim runtime path to a file.
*
* @param file The file containing the results.
* @return The results.
*/
private String[] parseVimRuntimePathResults(java.io.File file)
{
FileInputStream in = null;
try{
String contents = IOUtils.toString(in = new FileInputStream(file));
String[] paths = StringUtils.stripAll(StringUtils.split(contents, ','));
ArrayList<String> results = new ArrayList<String>();
for (String path : paths){
if(new File(path).isDirectory()){
results.add(path.replace('\\', '/'));
}
}
return results.toArray(new String[results.size()]);
}catch(Exception e){
e.printStackTrace();
}finally{
IOUtils.closeQuietly(in);
file.deleteOnExit();
file.delete();
}
return null;
}
}