| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.modules.javascript.cdnjs; |
| |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.swing.JComponent; |
| import org.netbeans.api.annotations.common.CheckForNull; |
| import org.netbeans.api.progress.ProgressHandle; |
| import org.netbeans.api.project.Project; |
| import org.netbeans.modules.javascript.cdnjs.ui.SelectionPanel; |
| import org.netbeans.modules.web.common.api.WebUtils; |
| import org.netbeans.spi.project.support.ant.PropertyUtils; |
| import org.netbeans.spi.project.ui.support.ProjectCustomizer; |
| import org.openide.DialogDisplayer; |
| import org.openide.NotifyDescriptor; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.util.Lookup; |
| import org.openide.util.NbBundle; |
| import org.openide.util.RequestProcessor; |
| |
| /** |
| * Customizer for CDNJS libraries. |
| * |
| * @author Jan Stola |
| */ |
| public class LibraryCustomizer implements ProjectCustomizer.CompositeCategoryProvider { |
| public static final String CATEGORY_NAME = "CDNJS"; // NOI18N |
| |
| private final boolean checkWebRoot; |
| |
| public LibraryCustomizer() { |
| this(false); |
| } |
| |
| public LibraryCustomizer(boolean checkWebRoot) { |
| this.checkWebRoot = checkWebRoot; |
| } |
| |
| @Override |
| @NbBundle.Messages("LibraryCustomizer.displayName=CDNJS") |
| public ProjectCustomizer.Category createCategory(Lookup context) { |
| if (checkWebRoot |
| && !WebUtils.hasWebRoot(context.lookup(Project.class))) { |
| return null; |
| } |
| return ProjectCustomizer.Category.create( |
| CATEGORY_NAME, Bundle.LibraryCustomizer_displayName(), null); |
| } |
| |
| @Override |
| public JComponent createComponent(ProjectCustomizer.Category category, Lookup context) { |
| Project project = context.lookup(Project.class); |
| Library.Version[] libraries = LibraryPersistence.getDefault().loadLibraries(project); |
| File webRoot = LibraryUtils.getWebRoot(project); |
| assert webRoot != null : project; |
| String libraryFolder = LibraryUtils.getLibraryFolder(project); |
| final SelectionPanel customizer = new SelectionPanel(project, libraries, webRoot, libraryFolder); |
| category.setStoreListener(new StoreListener(project, webRoot, customizer)); |
| return customizer; |
| } |
| |
| static class StoreListener implements ActionListener, Runnable { |
| |
| static final Logger LOGGER = Logger.getLogger(StoreListener.class.getName()); |
| |
| private final Project project; |
| private final File webRoot; |
| private final SelectionPanel customizer; |
| private ProgressHandle progressHandle; |
| |
| StoreListener(Project project, File webRoot, SelectionPanel customizer) { |
| this.project = project; |
| this.webRoot = webRoot; |
| this.customizer = customizer; |
| } |
| |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| RequestProcessor.getDefault().post(this); |
| } |
| |
| @Override |
| @NbBundle.Messages("LibraryCustomizer.updatingLibraries=Updating JavaScript libraries...") |
| public void run() { |
| progressHandle = ProgressHandle.createHandle(Bundle.LibraryCustomizer_updatingLibraries()); |
| progressHandle.start(); |
| |
| try { |
| Library.Version[] selectedVersions = customizer.getSelectedLibraries(); |
| Library.Version[] versionsToStore = updateLibraries(selectedVersions); |
| try { |
| LibraryPersistence.getDefault().storeLibraries(project, versionsToStore); |
| } catch (IOException ex) { |
| Logger.getLogger(StoreListener.class.getName()).log(Level.INFO, "Cannot store libraries", ex); |
| } |
| |
| String libraryFolder = customizer.getLibraryFolder(); |
| LibraryUtils.storeLibraryFolder(project, libraryFolder); |
| } finally { |
| progressHandle.finish(); |
| } |
| } |
| |
| @NbBundle.Messages({ |
| "# {0} - library name", |
| "# {1} - library version name", |
| "LibraryCustomizer.downloadFailed=Download of version {1} of library {0} failed!", |
| "# {0} - library name", |
| "# {1} - library version name", |
| "LibraryCustomizer.installationFailed=Installation of version {1} of library {0} failed!", |
| "LibraryCustomizer.libraryFolderFailure=Unable to create library folder!", |
| "LibraryCustomizer.creatingLibraryFolder=Creating a folder for JavaScript libraries.", |
| "# {0} - library name", |
| "# {1} - file path", |
| "LibraryCustomizer.downloadingFile=Downloading file {1} of {0}." |
| }) |
| private Library.Version[] updateLibraries(Library.Version[] newLibraries) { |
| List<String> errors = new ArrayList<>(); |
| |
| Library.Version[] oldLibraries = LibraryPersistence.getDefault().loadLibraries(project); |
| Map<String,Library.Version> oldMap = toMap(oldLibraries); |
| Map<String,Library.Version> newMap = toMap(newLibraries); |
| |
| // Identify versions to install and keep |
| List<Library.Version> toInstall = new ArrayList<>(); |
| List<String> toKeep = new ArrayList<>(); |
| for (Library.Version newVersion : newLibraries) { |
| String libraryName = newVersion.getLibrary().getName(); |
| Library.Version oldVersion = oldMap.get(libraryName); |
| if (oldVersion == null) { |
| toInstall.add(newVersion); |
| } else if (oldVersion.getName().equals(newVersion.getName())) { |
| toKeep.add(libraryName); |
| } else { |
| toInstall.add(newVersion); |
| } |
| } |
| |
| // Download versions to install |
| Map<String,File[]> downloadedFiles = new HashMap<>(); |
| for (Library.Version version : toInstall) { |
| String libraryName = version.getLibrary().getName(); |
| try { |
| String[] fileNames = version.getFiles(); |
| File[] files = new File[fileNames.length]; |
| for (int fileIndex=0; fileIndex<files.length; fileIndex++) { |
| progressHandle.progress(Bundle.LibraryCustomizer_downloadingFile(libraryName, fileNames[fileIndex])); |
| files[fileIndex] = LibraryProvider.getInstance().downloadLibraryFile(version, fileIndex); |
| } |
| downloadedFiles.put(libraryName, files); |
| } catch (IOException ioex) { |
| String errorMessage = Bundle.LibraryCustomizer_downloadFailed(libraryName, version.getName()); |
| errors.add(errorMessage); |
| Logger.getLogger(LibraryCustomizer.class.getName()).log(Level.INFO, errorMessage, ioex); |
| Library.Version oldVersion = oldMap.get(libraryName); |
| if (oldVersion == null) { |
| newMap.remove(libraryName); |
| } else { |
| newMap.put(libraryName, oldVersion); |
| } |
| } |
| } |
| |
| // Identify versions to remove |
| List<Library.Version> toRemove = new ArrayList<>(); |
| for (Library.Version oldVersion : oldLibraries) { |
| String libraryName = oldVersion.getLibrary().getName(); |
| Library.Version newVersion = newMap.get(libraryName); |
| if (newVersion == null || !newVersion.getName().equals(oldVersion.getName())) { |
| toRemove.add(oldVersion); |
| oldMap.remove(libraryName); |
| } |
| } |
| |
| // Remove the identified versions |
| uninstallLibraries(toRemove); |
| |
| // Create library folder |
| FileObject librariesFob = null; |
| if (!toInstall.isEmpty() || !toKeep.isEmpty()) { |
| librariesFob = createLibrariesFolder(errors); |
| if (librariesFob == null) { |
| reportErrors(errors); |
| return oldMap.values().toArray(new Library.Version[oldMap.size()]); |
| } |
| } |
| |
| // Install the identified versions |
| for (Library.Version version : toInstall) { |
| String libraryName = version.getLibrary().getName(); |
| File[] files = downloadedFiles.get(libraryName); |
| if (files != null) { |
| try { |
| installLibrary(librariesFob, version, files); |
| newMap.put(libraryName, version); |
| } catch (IOException ioex) { |
| String errorMessage = Bundle.LibraryCustomizer_installationFailed(libraryName, version.getName()); |
| errors.add(errorMessage); |
| Logger.getLogger(LibraryCustomizer.class.getName()).log(Level.INFO, errorMessage, ioex); |
| newMap.remove(libraryName); |
| } |
| } |
| } |
| |
| // Install/remove files in the versions that are kept |
| String newLibraryFolder = customizer.getLibraryFolder(); |
| String oldLibraryFolder = LibraryUtils.getLibraryFolder(project); |
| boolean libFolderChanged = !newLibraryFolder.equals(oldLibraryFolder); |
| for (String libraryName : toKeep) { |
| Library.Version oldVersion = oldMap.get(libraryName); |
| if (libFolderChanged) { |
| moveLibrary(librariesFob, oldVersion, errors); |
| } |
| Library.Version newVersion = newMap.get(libraryName); |
| Library.Version versionToStore = updateLibrary(librariesFob, oldVersion, newVersion, errors); |
| if (versionToStore != null) { |
| newMap.put(libraryName, versionToStore); |
| } |
| } |
| |
| reportErrors(errors); |
| |
| return newMap.values().toArray(new Library.Version[newMap.size()]); |
| } |
| |
| private void reportErrors(List<String> errors) { |
| if (!errors.isEmpty()) { |
| StringBuilder message = new StringBuilder(); |
| for (String error : errors) { |
| if (message.length() != 0) { |
| message.append('\n'); |
| } |
| message.append(error); |
| } |
| NotifyDescriptor descriptor = new NotifyDescriptor.Message( |
| message.toString(), NotifyDescriptor.ERROR_MESSAGE); |
| DialogDisplayer.getDefault().notifyLater(descriptor); |
| } |
| } |
| |
| private FileObject createLibrariesFolder(List<String> errors) { |
| progressHandle.progress(Bundle.LibraryCustomizer_creatingLibraryFolder()); |
| FileObject librariesFob = null; |
| try { |
| String librariesFolderName = customizer.getLibraryFolder(); |
| FileObject webRootFob = FileUtil.toFileObject(webRoot); |
| librariesFob = FileUtil.createFolder(webRootFob, librariesFolderName); |
| } catch (IOException ioex) { |
| String errorMessage = Bundle.LibraryCustomizer_libraryFolderFailure(); |
| errors.add(errorMessage); |
| Logger.getLogger(LibraryCustomizer.class.getName()).log(Level.INFO, errorMessage, ioex); |
| } |
| return librariesFob; |
| } |
| |
| @NbBundle.Messages({ |
| "# {0} - library name", |
| "LibraryCustomizer.libraryFolderCreationFailed=Unable to create the folder for the library {0}!", |
| "# {0} - library name", |
| "# {1} - file path", |
| "LibraryCustomizer.moveFailed=Move of the file {1} of the library {0} failed!", |
| "# {0} - library name", |
| "LibraryCustomizer.movingLibrary=Moving library {0} into the new library folder." |
| }) |
| private void moveLibrary(FileObject librariesFolder, Library.Version version, List<String> errors) { |
| String libraryName = version.getLibrary().getName(); |
| progressHandle.progress(Bundle.LibraryCustomizer_movingLibrary(libraryName)); |
| FileObject projectFob = project.getProjectDirectory(); |
| try { |
| FileObject libraryFolder = FileUtil.createFolder(librariesFolder, libraryName); |
| File projectDir = FileUtil.toFile(projectFob); |
| String[] files = version.getFiles(); |
| String[] localFiles = version.getLocalFiles(); |
| for (int i=0; i<files.length; i++) { |
| File file = PropertyUtils.resolveFile(projectDir, localFiles[i]); |
| if (file.exists()) { |
| try { |
| FileObject fob = FileUtil.toFileObject(file); |
| String[] pathElements = files[i].split("/"); // NOI18N |
| FileObject fileFolder = libraryFolder; |
| for (int j=0; j<pathElements.length-1; j++) { |
| fileFolder = FileUtil.createFolder(fileFolder, pathElements[j]); |
| } |
| String fileName = pathElements[pathElements.length-1]; |
| int index = fileName.lastIndexOf('.'); |
| if (index != -1) { |
| fileName = fileName.substring(0,index); |
| } |
| fob = FileUtil.moveFile(fob, fileFolder, fileName); |
| localFiles[i] = PropertyUtils.relativizeFile(projectDir, FileUtil.toFile(fob)); |
| removeFile(file.getParentFile()); |
| } catch (IOException ioex) { |
| String errorMessage = Bundle.LibraryCustomizer_moveFailed(libraryName, files[i]); |
| errors.add(errorMessage); |
| Logger.getLogger(LibraryCustomizer.class.getName()).log(Level.INFO, errorMessage, ioex); |
| } |
| } |
| } |
| } catch (IOException ioex) { |
| String errorMessage = Bundle.LibraryCustomizer_libraryFolderCreationFailed(libraryName); |
| errors.add(errorMessage); |
| Logger.getLogger(LibraryCustomizer.class.getName()).log(Level.INFO, errorMessage, ioex); |
| } |
| } |
| |
| @NbBundle.Messages({ |
| "# {0} - library name", |
| "# {1} - file name/path", |
| "LibraryCustomizer.updateFailed=Update of library {0} failed for file {1}.", |
| "# {0} - library name", |
| "# {1} - file name/path", |
| "LibraryCustomizer.deletingFile=Deleting file {1} of {0}.", |
| "# {0} - library name", |
| "LibraryCustomizer.folderNotCreated=Folder for library {0} cannot be created.", |
| }) |
| @CheckForNull |
| private Library.Version updateLibrary(FileObject librariesFolder, |
| Library.Version oldVersion, Library.Version newVersion, |
| List<String> errors) { |
| FileObject projectFob = project.getProjectDirectory(); |
| File projectDir = FileUtil.toFile(projectFob); |
| LibraryProvider libraryProvider = LibraryProvider.getInstance(); |
| String libraryName = oldVersion.getLibrary().getName(); |
| FileObject libraryFolder = librariesFolder.getFileObject(libraryName); |
| if (libraryFolder == null) { |
| // #267246 - likely broken library |
| assert LibraryUtils.isBroken(project, oldVersion) : "This library should be broken: " + oldVersion.getLibrary().getName(); |
| try { |
| libraryFolder = FileUtil.createFolder(librariesFolder, libraryName); |
| } catch (IOException ex) { |
| LOGGER.log(Level.INFO, "Cannot created folder '" + libraryName + "' in '" + FileUtil.getFileDisplayName(librariesFolder) + "'", ex); |
| errors.add(Bundle.LibraryCustomizer_folderNotCreated(libraryName)); |
| return null; |
| } |
| } |
| |
| // Install missing files |
| Map<String,String> oldFilesMap = new HashMap<>(); |
| String[] oldFiles = oldVersion.getFiles(); |
| String[] oldLocalFiles = oldVersion.getLocalFiles(); |
| for (int i=0; i<oldFiles.length; i++) { |
| File file = PropertyUtils.resolveFile(projectDir, oldLocalFiles[i]); |
| if (file.exists()) { |
| oldFilesMap.put(oldFiles[i], oldLocalFiles[i]); |
| } |
| } |
| List<String> fileList = new ArrayList<>(); |
| List<String> localFileList = new ArrayList<>(); |
| String[] filesToInstall = newVersion.getFiles(); |
| for (int fileIndex = 0; fileIndex < filesToInstall.length; fileIndex++) { |
| String filePath = filesToInstall[fileIndex]; |
| try { |
| String localPath = oldFilesMap.get(filePath); |
| if (localPath == null) { |
| // Installation needed |
| String[] pathElements = filePath.split("/"); // NOI18N |
| FileObject fileFolder = libraryFolder; |
| for (int j=0; j<pathElements.length-1; j++) { |
| fileFolder = FileUtil.createFolder(fileFolder, pathElements[j]); |
| } |
| progressHandle.progress(Bundle.LibraryCustomizer_downloadingFile(libraryName, filePath)); |
| File file = libraryProvider.downloadLibraryFile(newVersion, fileIndex); |
| FileObject tmpFob = FileUtil.toFileObject(file); |
| String fileName = pathElements[pathElements.length-1]; |
| int index = fileName.lastIndexOf('.'); |
| if (index != -1) { |
| fileName = fileName.substring(0,index); |
| } |
| FileObject fob = FileUtil.copyFile(tmpFob, fileFolder, fileName); |
| if (!file.delete()) { |
| LOGGER.log(Level.INFO, "Cannot delete file {0}", file); |
| } |
| localPath = PropertyUtils.relativizeFile(projectDir, FileUtil.toFile(fob)); |
| } |
| fileList.add(filePath); |
| localFileList.add(localPath); |
| } catch (IOException ioex) { |
| String errorMessage = Bundle.LibraryCustomizer_updateFailed(libraryName, filePath); |
| errors.add(errorMessage); |
| LOGGER.log(Level.INFO, errorMessage, ioex); |
| return null; |
| } |
| } |
| |
| // Remove files that are no longer needed |
| for (int i=0; i<oldFiles.length; i++) { |
| if (!fileList.contains(oldFiles[i])) { |
| progressHandle.progress(Bundle.LibraryCustomizer_deletingFile(libraryName, oldLocalFiles[i])); |
| uninstallFile(oldLocalFiles[i]); |
| } |
| } |
| |
| Library.Version versionToStore = newVersion.filterVersion(Collections.emptySet()); |
| versionToStore.setFileInfo( |
| fileList.toArray(new String[fileList.size()]), |
| localFileList.toArray(new String[localFileList.size()]) |
| ); |
| return versionToStore; |
| } |
| |
| @NbBundle.Messages({ |
| "# {0} - library name", |
| "# {1} - version name", |
| "LibraryCustomizer.removingLibrary=Removing version {1} of {0}." |
| }) |
| private void uninstallLibraries(List<Library.Version> libraries) { |
| for (Library.Version version : libraries) { |
| progressHandle.progress(Bundle.LibraryCustomizer_removingLibrary(version.getLibrary().getName(), version.getName())); |
| for (String fileName : version.getLocalFiles()) { |
| uninstallFile(fileName); |
| } |
| } |
| } |
| |
| private void removeFile(File file) { |
| while (!webRoot.equals(file)) { |
| File parent = file.getParentFile(); |
| if (!file.delete()) { |
| // We have reached a parent directory that is not empty |
| break; |
| } |
| file = parent; |
| } |
| } |
| |
| private void uninstallFile(String filePath) { |
| File projectDir = FileUtil.toFile(project.getProjectDirectory()); |
| File file = PropertyUtils.resolveFile(projectDir, filePath); |
| if (file.exists()) { |
| removeFile(file); |
| } else { |
| Logger.getLogger(LibraryCustomizer.class.getName()).log( |
| Level.INFO, "Cannot delete file {0}. It no longer exists.", |
| new Object[]{file.getAbsolutePath()}); |
| } |
| } |
| |
| @NbBundle.Messages({ |
| "# {0} - library name", |
| "# {1} - version name", |
| "LibraryCustomizer.installingLibrary=Installing version {1} of {0}." |
| }) |
| private void installLibrary(FileObject librariesFolder, |
| Library.Version version, File[] libraryFiles) throws IOException { |
| progressHandle.progress(Bundle.LibraryCustomizer_installingLibrary(version.getLibrary().getName(), version.getName())); |
| String libraryName = version.getLibrary().getName(); |
| FileObject libraryFob = FileUtil.createFolder(librariesFolder, libraryName); |
| |
| FileObject projectFob = project.getProjectDirectory(); |
| File projectDir = FileUtil.toFile(projectFob); |
| String[] fileNames = version.getFiles(); |
| String[] localFiles = new String[fileNames.length]; |
| for (int i=0; i<fileNames.length; i++) { |
| FileObject tmpFob = FileUtil.toFileObject(libraryFiles[i]); |
| String fileName = fileNames[i]; |
| int index = fileName.lastIndexOf('.'); |
| if (index != -1) { |
| fileName = fileName.substring(0, index); |
| } |
| String[] path = fileName.split("/"); // NOI18N |
| FileObject fileFolder = libraryFob; |
| for (int j=0; j<path.length-1; j++) { |
| fileFolder = FileUtil.createFolder(fileFolder, path[j]); |
| } |
| FileObject fob = FileUtil.copyFile(tmpFob, fileFolder, path[path.length-1]); |
| if (!libraryFiles[i].delete()) { |
| LOGGER.log(Level.INFO, "Cannot delete file {0}", libraryFiles[i]); |
| } |
| File file = FileUtil.toFile(fob); |
| localFiles[i] = PropertyUtils.relativizeFile(projectDir, file); |
| } |
| version.setFileInfo(fileNames, localFiles); |
| } |
| |
| private Map<String,Library.Version> toMap(Library.Version[] libraries) { |
| Map<String,Library.Version> map = new HashMap<>(); |
| for (Library.Version library : libraries) { |
| map.put(library.getLibrary().getName(), library); |
| } |
| return map; |
| } |
| |
| } |
| |
| } |