/******************************************************************************* * Copyright (c) 2015 ARM Ltd. and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * ARM Ltd and ARM Germany GmbH - Initial API and implementation * A snippet for file change monitoring is taken from: * http://stackoverflow.com/questions/16251273/can-i-watch-for-single-file-change-with-watchservice-not-the-whole-directory *******************************************************************************/ package com.arm.cmsis.pack; import java.io.File; import java.io.IOException; import java.net.URI; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import com.arm.cmsis.pack.ICpPackInstaller.ConsoleType; import com.arm.cmsis.pack.common.CmsisConstants; import com.arm.cmsis.pack.data.CpPack; import com.arm.cmsis.pack.data.CpPackCollection; import com.arm.cmsis.pack.data.CpPackFamily; import com.arm.cmsis.pack.data.ICpBoard; import com.arm.cmsis.pack.data.ICpItem; import com.arm.cmsis.pack.data.ICpPack; import com.arm.cmsis.pack.data.ICpPack.PackState; import com.arm.cmsis.pack.data.ICpPackCollection; import com.arm.cmsis.pack.data.ICpPackFamily; import com.arm.cmsis.pack.events.IRteEventListener; import com.arm.cmsis.pack.events.IRteEventProxy; import com.arm.cmsis.pack.events.RteEvent; import com.arm.cmsis.pack.events.RtePackJobResult; import com.arm.cmsis.pack.generic.IAttributes; import com.arm.cmsis.pack.parser.ICpXmlParser; import com.arm.cmsis.pack.parser.PdscParser; import com.arm.cmsis.pack.preferences.CpPreferenceInitializer; import com.arm.cmsis.pack.repository.CpRepositoryList; import com.arm.cmsis.pack.rte.boards.IRteBoardDeviceItem; import com.arm.cmsis.pack.rte.boards.RteBoardDeviceItem; import com.arm.cmsis.pack.rte.devices.IRteDeviceItem; import com.arm.cmsis.pack.rte.devices.RteDeviceItem; import com.arm.cmsis.pack.rte.examples.IRteExampleItem; import com.arm.cmsis.pack.rte.examples.RteExampleItem; import com.arm.cmsis.pack.utils.FileChangeWatcher; import com.arm.cmsis.pack.utils.Utils; import com.arm.cmsis.pack.utils.VersionComparator; /** * Default simple CMSIS-Pack manager */ public class CpPackManager implements ICpPackManager, IRteEventListener { protected ICpPackCollection allPacks = null; // global pack collection protected ICpPackCollection allInstalledPacks = null; // all installed pack collection protected ICpPackCollection allGenericPacks = null; // generic pack collection protected ICpPackCollection allDevicePacks = null; // device-specific pack collection protected ICpPackFamily allErrorPacks = null; // error pack collection protected ICpXmlParser pdscParser = null; protected IRteDeviceItem allDevices = null; protected IRteDeviceItem allInstalledDevices = null; protected Map<String, ICpBoard> allBoards = null; protected IRteBoardDeviceItem allRteBoardDevices = null; protected IRteExampleItem allExamples = null; protected String cmsisPackRootDirectory = null; protected URI cmsisPackRootURI = null; protected boolean bPacksLoaded = false; protected boolean bReloading = false; // reload is in progress protected boolean bReloadPending = false; // reload is requested, but pack installer is still busy protected IRteEventProxy fRteEventProxy = null; protected ICpPackInstaller fPackInstaller = null; protected CpRepositoryList fRepoList = null; protected Map<String, ICpPack> fGeneratedPacks = null; protected ICpPack.PackState packState = PackState.UNKNOWN; protected PackIdxWatcher packIdxWatcher = null; protected GpdscWatcher gpdscWatcher = new GpdscWatcher(); class PackIdxWatcher extends FileChangeWatcher { public PackIdxWatcher(){ super(FileChangeWatcher.ALL); } public void restartWatch() { clearWatch(); String idxFile = getPackIdxFile(); if(idxFile == null || idxFile.isEmpty()) { return; } File file = new File(idxFile); if (!file.exists()) { try { // ensure directory exists String dir = getCmsisPackRootDirectory(); FileChangeWatcher.createDirectories(dir); file.createNewFile(); } catch (IOException e) { e.printStackTrace(); return; } } registerFile(idxFile); startWatch(); } @Override protected void action(String file, int kind) { if (fPackInstaller == null || !fPackInstaller.isBusy()) { if(!isReloadPending()) { reload(); } } } } class GpdscWatcher extends FileChangeWatcher { public GpdscWatcher(){ super(FileChangeWatcher.ALL); } @Override public synchronized void registerFile(String file) { super.registerFile(file); startWatch(); } @Override protected void action(String file, int kind) { refreshGpdsc(file, kind); } } /** * Start watch the pack.idx file in the pack root folder */ protected void startPackIdxWatcher() { if (packIdxWatcher == null) { packIdxWatcher = new PackIdxWatcher(); } packIdxWatcher.restartWatch(); } /** * Stop watch the pack.idx file in the pack root folder */ protected void stopPackIdxWatcher() { if (packIdxWatcher != null) { packIdxWatcher.stopWatch(); } } /** * Stop watch the pack.idx file in the pack root folder */ protected void clearPackIdxWatcher() { if (packIdxWatcher != null) { packIdxWatcher.clearWatch(); packIdxWatcher = null; } } /** * Default pack manager implementation */ public CpPackManager() { } public String getPackIdxFile() { String idxFile = getCmsisPackRootDirectory(); if(idxFile != null && !idxFile.isEmpty()) { idxFile = Utils.addTrailingSlash(idxFile) + CmsisConstants.PACK_IDX; } return idxFile; } @Override public IRteEventProxy getRteEventProxy() { return fRteEventProxy; } @Override public void setRteEventProxy(IRteEventProxy rteEventProxy) { fRteEventProxy = rteEventProxy; fRteEventProxy.addListener(this); } protected void emitRteEvent(String topic) { if(fRteEventProxy != null) { fRteEventProxy.notifyListeners(new RteEvent(topic)); } } @Override public void setPackInstaller(ICpPackInstaller packInstaller) { fPackInstaller = packInstaller; } @Override public ICpPackInstaller getPackInstaller() { return fPackInstaller; } @Override public boolean initParser(String xsdFile){ if(pdscParser == null) { pdscParser = new PdscParser(xsdFile); } else { pdscParser.setXsdFile(xsdFile); } return pdscParser.init(); } @Override synchronized public void clear() { allPacks = null; allErrorPacks = null; allInstalledPacks = null; allGenericPacks = null; allDevicePacks = null; allDevices = null; allInstalledDevices = null; allBoards = null; allRteBoardDevices = null; allExamples = null; fGeneratedPacks = null; bPacksLoaded = false; if(pdscParser != null) { pdscParser.clear(); } } @Override public void reload() { if(isReloading()) { return; } setReloading(true); clear(); getPacks(); // triggers load if(fPackInstaller != null) { fPackInstaller.reset(); } emitRteEvent(RteEvent.PACKS_RELOADED); setReloadPending(false); setReloading(false); } protected synchronized boolean isReloading() { return bReloading; } protected synchronized void setReloading(boolean loading) { bReloading = loading; } synchronized boolean isReloadPending() { return bReloadPending; } synchronized void setReloadPending(boolean pending) { bReloadPending = pending; } @Override public void destroy() { clear(); pdscParser = null; bReloading = false; bReloadPending = false; clearPackIdxWatcher(); gpdscWatcher.clearWatch(); gpdscWatcher = null; } @Override synchronized public ICpPackCollection getPacks() { if(allPacks == null) { bPacksLoaded = loadPacks(cmsisPackRootDirectory); } return allPacks; } @Override synchronized public ICpPackCollection getInstalledPacks() { if(allPacks == null) { bPacksLoaded = loadPacks(cmsisPackRootDirectory); } return allInstalledPacks; } @Override synchronized public ICpPackCollection getDevicePacks() { if(allPacks == null) { bPacksLoaded = loadPacks(cmsisPackRootDirectory); } return allDevicePacks; } @Override synchronized public ICpPackCollection getGenericPacks() { if(allPacks == null) { bPacksLoaded = loadPacks(cmsisPackRootDirectory); } return allGenericPacks; } @Override synchronized public ICpPackFamily getErrorPacks() { return allErrorPacks; } @Override synchronized public IRteDeviceItem getDevices() { getPacks(); // ensure allPacks are loaded if(allDevices == null && bPacksLoaded && allPacks != null) { allDevices = RteDeviceItem.createTree(allPacks.getPacks()); } return allDevices; } @Override synchronized public IRteDeviceItem getInstalledDevices() { getPacks(); // ensure allPacks are loaded if(allInstalledDevices == null && bPacksLoaded && allInstalledPacks != null) { allInstalledDevices = RteDeviceItem.createTree(allInstalledPacks.getLatestPacks()); } return allInstalledDevices; } @Override synchronized public Map<String, ICpBoard> getBoards() { getPacks(); // ensure allPacks are loaded if(allBoards == null && bPacksLoaded && allPacks != null) { collectBoards(); } return allBoards; } protected void collectBoards() { allBoards = new HashMap<String, ICpBoard>(); Collection<ICpPack> packs = allPacks.getPacks(); for(ICpPack pack: packs) { addBoards(pack); } } @Override synchronized public IRteBoardDeviceItem getRteBoardDevices() { getPacks(); // ensure allPacks are loaded if(allRteBoardDevices == null && bPacksLoaded && allPacks != null) { allRteBoardDevices = RteBoardDeviceItem.createTree(allPacks.getPacks()); } return allRteBoardDevices; } @Override public Collection<ICpBoard> getCompatibleBoards(IAttributes deviceAttributes) { List<ICpBoard> boards = new LinkedList<ICpBoard>(); getBoards(); if(allBoards == null || allBoards.isEmpty()) { return boards; } for(ICpBoard b : allBoards.values()){ if(b.hasCompatibleDevice(deviceAttributes)) { boards.add(b); } } return boards; } @Override synchronized public IRteExampleItem getExamples() { getPacks(); // ensure allPacks are loaded if(allExamples == null && bPacksLoaded && allPacks != null) { allExamples = RteExampleItem.createTree(allPacks.getPacks()); } return allExamples; } @Override synchronized public boolean loadPacks(final String rootDirectory){ if(rootDirectory == null || rootDirectory.isEmpty()) { return false; } File root = new File(rootDirectory); if( !root.exists()) { return false; } packState = PackState.AVAILABLE; File webFile = new File(getCmsisPackWebDir()); if (!webFile.exists()) { webFile.mkdir(); } Collection<String> availableFileNames = Utils.findPdscFiles(webFile, null, 0); loadPacks(availableFileNames); packState = PackState.DOWNLOADED; File downloadFile = new File(getCmsisPackDownloadDir()); if (!downloadFile.exists()) { downloadFile.mkdir(); } Collection<String> downloadedFileNames = Utils.findPdscFiles(downloadFile, null, 0); loadPacks(downloadedFileNames); packState = PackState.INSTALLED; Collection<String> installedFileNames = Utils.findPdscFiles(root, null, 3); loadPacks(installedFileNames); packState = PackState.UNKNOWN; return true; } @Override public boolean loadPacks(final Collection<String> fileNames){ if(fileNames == null || fileNames.isEmpty()) { return true; // nothing to load => success } boolean success = true; for(String f : fileNames) { if(loadPack(f) == null) { success = false; } } return success; } @Override public ICpPack readPack(String file){ if (pdscParser == null) { initParser(null); } ICpItem item = pdscParser.parseFile(file); if(item != null && item instanceof ICpPack) { return (ICpPack)item; } if(pdscParser.getErrorCount() > 0) { List<String> errors = pdscParser.getErrorStrings(); if(errors != null && !errors.isEmpty()) { for(String msg : errors) { if(msg != null && !msg.isEmpty()){ getRteEventProxy().emitRteEvent(RteEvent.PRINT_ERROR, msg); } } } } return null; } protected ICpPack loadPack(String file){ if(allPacks == null) { allPacks = new CpPackCollection(); } if (allGenericPacks == null) { allGenericPacks = new CpPackCollection(CmsisConstants.GENERIC); } if (allDevicePacks == null) { allDevicePacks = new CpPackCollection(CmsisConstants.DEVICE_SPECIFIC); } if (allInstalledPacks == null) { allInstalledPacks = new CpPackCollection(); } ICpPack pack = readPack(file); if (pack != null && CmsisConstants.PACKAGE_TAG.equals(pack.getTag())) { pack.setPackState(packState); allPacks.addChild(pack); if (packState == PackState.INSTALLED) { allInstalledPacks.addChild(pack); } if (pack.isDevicelessPack()) { allGenericPacks.addChild(pack); } else { allDevicePacks.addChild(pack); } } else { if (allErrorPacks == null) { allErrorPacks = new CpPackFamily(null, CmsisConstants.ERRORS); } pack = new CpPack(allErrorPacks); pack.setFileName(file); pack.setText(Utils.extractFileName(file)); pack.setTag(Utils.extractFileName(file)); pack.setPackState(PackState.ERROR); allErrorPacks.addChild(pack); String errorString; if (!pdscParser.getErrorStrings().isEmpty()) { errorString = pdscParser.getErrorStrings().get(0); } else if (!CmsisConstants.PACKAGE_TAG.equals(pack.getTag())) { errorString = pack.getFileName() + ": " //$NON-NLS-1$ + CpStrings.CpPackManager_UnrecognizedFileFormatError; } else { errorString = pack.getFileName() + ": " //$NON-NLS-1$ + CpStrings.CpPackManager_DefaultError; } if(fPackInstaller != null) { fPackInstaller.printInConsole(CpStrings.CpPackManager_ErrorWhileParsing + errorString, ConsoleType.ERROR); } } return pack; } @Override public String getCmsisPackRootDirectory() { return cmsisPackRootDirectory; } @Override synchronized public CpRepositoryList getCpRepositoryList() { if (fRepoList == null) { fRepoList = new CpRepositoryList(); } return fRepoList; } @Override public URI getCmsisPackRootURI() { return cmsisPackRootURI; } @Override public void setCmsisPackRootDirectory(String packRootDirectory) { String normalizedPackRoot = null; String osPackRoot = CmsisConstants.EMPTY_STRING; if(packRootDirectory != null) { // normalize and convert to Unix format IPath p = new Path(Utils.removeTrailingSlash(packRootDirectory)); normalizedPackRoot = p.toString(); osPackRoot = p.toOSString(); } if (cmsisPackRootDirectory == null) { if (normalizedPackRoot == null || normalizedPackRoot.isEmpty()) { return; } } else if (cmsisPackRootDirectory.equals(normalizedPackRoot) && bPacksLoaded) { return; } clearPackIdxWatcher(); if (normalizedPackRoot == null || normalizedPackRoot.isEmpty()) { cmsisPackRootDirectory = null; cmsisPackRootURI = null; } else { cmsisPackRootDirectory = normalizedPackRoot; try { // ensure directory exists FileChangeWatcher.createDirectories(cmsisPackRootDirectory); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } File f = new File(cmsisPackRootDirectory); cmsisPackRootURI = f.toURI(); } CpPreferenceInitializer.setPackRoot(osPackRoot); reload(); startPackIdxWatcher(); // resume watching } @Override public boolean arePacksLoaded() { return bPacksLoaded; } @Override public void setParser(ICpXmlParser xmlParser) { pdscParser = xmlParser; } @Override public ICpXmlParser getParser() { return pdscParser; } @Override public void handle(RteEvent event) { if(event.getTopic().startsWith(RteEvent.PACK_JOB)) { processPackJob((RtePackJobResult) event.getData(), event.getTopic()); } } protected void processPackJob(RtePackJobResult jobResult, String topic) { if(isReloading()) { return; // do not interfere with reload } if(!isReloadPending()) { // reload will do all those changes anyway switch (topic) { case RteEvent.PACK_INSTALL_JOB_FINISHED: processPackAdded(jobResult); break; case RteEvent.PACK_REMOVE_JOB_FINISHED: case RteEvent.PACK_DELETE_JOB_FINISHED: processPackRemoved(jobResult, topic); break; case RteEvent.PACK_IMPORT_FOLDER_JOB_FINISHED: if (jobResult.isSuccess()) { setReloadPending(true); } break; default: break; } } if(fPackInstaller != null && !fPackInstaller.isBusy()) { stopPackIdxWatcher(); // suspend interrupting change events FileChangeWatcher.touchFile(getPackIdxFile()); // let others know is that we also have made changed if(isReloadPending()) { reload(); // resumes pack index watching } else { emitRteEvent(RteEvent.PACKS_UPDATED); } startPackIdxWatcher(); } } protected void processPackAdded(RtePackJobResult jobResult) { if (jobResult == null || !jobResult.isSuccess()) { return; } ICpPack pack = jobResult.getPack(); if(pack != null) { processPackAdded(pack); allInstalledPacks.addChild(pack); } } protected void processPackAdded(ICpPack pack) { if(pack == null) { return; } // Update pack collection allPacks.addChild(pack); if (pack.isDevicelessPack()) { allGenericPacks.addChild(pack); } else { allDevicePacks.addChild(pack); } // Update RteDevice Tree if (allDevices != null) { allDevices.addDevices(pack); } if (allInstalledDevices != null) { allInstalledDevices.addDevices(pack); } // Update Board Collection if (allRteBoardDevices != null) { allRteBoardDevices.addBoards(pack); } addBoards(pack); // Update Examples Collection if (allExamples != null) { allExamples.addExamples(pack); } } protected void processPackRemoved(RtePackJobResult jobResult, String topic) { if (jobResult== null || !jobResult.isSuccess()) { return; } ICpPack pack = jobResult.getPack(); // if the deleted pack is an error pack, only need to remove it from allErrorPacks if (pack.getPackState() == PackState.ERROR) { allErrorPacks.removeChild(pack); pack.setParent(null); return; } // Remove Pack from all installed packs Collection<ICpPackCollection> packCollections = new LinkedList<>(); packCollections.add(allInstalledPacks); packCollections.add(allPacks); if (RteEvent.PACK_DELETE_JOB_FINISHED.equals(topic)) { if (pack.isDevicelessPack()) { packCollections.add(allGenericPacks); } else { packCollections.add(allDevicePacks); } } for (ICpPackCollection packCollection : packCollections) { String familyId = pack.getPackFamilyId(); for (ICpItem packFamily : packCollection.getChildren()) { if (familyId.equals(packFamily.getPackFamilyId())) { packFamily.removeChild(pack); } } } // Remove Device from device tree if pack is not the latest version of this pack family if (allDevices != null) { allDevices.removeDevices(pack); } if (allInstalledDevices != null) { allInstalledDevices.removeDevices(pack); } // Remove Board from board tree if (allRteBoardDevices != null) { allRteBoardDevices.removeBoards(pack); } // Remove Example from examples tree if (allExamples != null) { allExamples.removeExamples(pack); } // Add new pack into the packs, which could be the new pdsc file in the .Web or the .Download folder ICpPack newPack = jobResult.getNewPack(); processPackAdded(newPack); } protected void addBoards(ICpPack pack) { if (pack == null) { return; } Collection<? extends ICpItem> boards = pack.getGrandChildren(CmsisConstants.BOARDS_TAG); if (allBoards != null && boards != null) { for(ICpItem item : boards) { if(!(item instanceof ICpBoard)) { continue; } ICpBoard currentBoard = (ICpBoard)item; String id = currentBoard.getId(); ICpBoard previousBoard = allBoards.get(id); if (previousBoard == null || isToReplaceExistingItem(previousBoard, currentBoard)) { allBoards.put(id, currentBoard); } } } } /** * Checks if to replace an existing item in a collection with the current one depending item's pack states and versions * @param previous existing item * @param current current item * @return true if to replace existing with current */ protected static boolean isToReplaceExistingItem(ICpItem previous, ICpItem current) { PackState ps1 = previous.getPack().getPackState(); PackState ps2 = current.getPack().getPackState(); if (ps1.ordinal() < ps2.ordinal()) { return false; } else if (ps1.ordinal() > ps2.ordinal()) { return true; } else { String pv1 = previous.getPack().getVersion(); String pv2 = current.getPack().getVersion(); if (VersionComparator.versionCompare(pv1, pv2) < 0) { return true; } } return false; } @Override public synchronized ICpPack loadGpdsc(String file) { if(file == null || file .isEmpty()) { return null; } if(fGeneratedPacks != null && fGeneratedPacks.containsKey(file)) { return fGeneratedPacks.get(file); } ICpPack pack = doLoadGpdsc(file); gpdscWatcher.registerFile(file); // register file to watch change, even if file does not exists return pack; } synchronized void refreshGpdsc(String file, int kind) { if(kind == FileChangeWatcher.DELETE) { fGeneratedPacks.put(file, null); } else { try { Thread.sleep(500); // let file system some time to finish writing } catch (InterruptedException e) { // ignore the exception } doLoadGpdsc(file); } getRteEventProxy().emitRteEvent(RteEvent.GPDSC_CHANGED, file); } synchronized protected ICpPack doLoadGpdsc(String file) { ICpPack pack = readPack(file); if(pack != null) { pack.setPackState(PackState.GENERATED); } if(fGeneratedPacks == null) { fGeneratedPacks = new HashMap<String, ICpPack>(); } fGeneratedPacks.put(file, pack); return pack; } }