package bndtools.central; import java.io.File; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeoutException; import org.bndtools.utils.swt.SWTConcurrencyUtil; import org.eclipse.core.resources.WorkspaceJob; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.viewers.TreePath; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.swt.widgets.Display; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceRegistration; import org.osgi.util.promise.Promise; import org.osgi.util.promise.Success; import aQute.bnd.build.Workspace; import aQute.bnd.osgi.Jar; import aQute.bnd.service.RepositoryListenerPlugin; import aQute.bnd.service.RepositoryPlugin; import bndtools.Plugin; public class RepositoriesViewRefresher implements RepositoryListenerPlugin { public interface RefreshModel { List<RepositoryPlugin> getRepositories(); } // private static final ILogger logger = Logger.getLogger(RepositoriesViewRefresher.class); private boolean redo = false; private boolean busy = false; private final ServiceRegistration<RepositoryListenerPlugin> registration; private final Map<TreeViewer,RefreshModel> viewers = new ConcurrentHashMap<>(); RepositoriesViewRefresher() { ServiceRegistration<RepositoryListenerPlugin> reg = null; Bundle bundle = FrameworkUtil.getBundle(RepositoriesViewRefresher.class); if (bundle != null) { BundleContext context = bundle.getBundleContext(); if (context != null) reg = context.registerService(RepositoryListenerPlugin.class, this, null); } this.registration = reg; } public void refreshRepositories(final RepositoryPlugin target) { synchronized (this) { if (busy) { redo = true; return; } busy = true; redo = false; } // // Since this can delay, we move this to the background // new WorkspaceJob("Updating repositories content") { @Override public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException { try { if (monitor == null) monitor = new NullProgressMonitor(); Set<RepositoryPlugin> repos = new HashSet<>(); if (target != null) repos.add(target); else { for (RefreshModel m : viewers.values()) { repos.addAll(m.getRepositories()); } } ensureLoaded(monitor, repos); // get repositories first, then do UI thread work final Map<Entry<TreeViewer,RefreshModel>,List<RepositoryPlugin>> entryRepos = new HashMap<>(); for (Map.Entry<TreeViewer,RefreshModel> entry : viewers.entrySet()) { entryRepos.put(entry, entry.getValue().getRepositories()); } // // And now back to the UI thread // getDisplay().asyncExec(new Runnable() { @Override public void run() { synchronized (RepositoriesViewRefresher.this) { redo = false; } for (Map.Entry<TreeViewer,RefreshModel> entry : viewers.entrySet()) { TreePath[] expandedTreePaths = entry.getKey().getExpandedTreePaths(); entry.getKey().setInput(entryRepos.get(entry)); if (expandedTreePaths != null && expandedTreePaths.length > 0) entry.getKey().setExpandedTreePaths(expandedTreePaths); } synchronized (RepositoriesViewRefresher.this) { busy = false; if (redo) { refreshRepositories(null); } } } }); } catch (Exception e) { e.printStackTrace(); } return Status.OK_STATUS; } }.schedule(1000); } private IStatus ensureLoaded(IProgressMonitor monitor, Collection<RepositoryPlugin> repos) { int n = 0; try { final RepositoryPlugin workspaceRepo = Central.getWorkspaceRepository(); for (RepositoryPlugin repo : repos) { if (monitor.isCanceled()) { return Status.CANCEL_STATUS; } monitor.beginTask(repo.getName(), n++); if (repo != workspaceRepo) { repo.list(null); // looks silly but is here to incur any download time continue; } // We must safely call bnd to list workspace repo try { Central.bndCall(new Callable<List<String>>() { @Override public List<String> call() throws Exception { return workspaceRepo.list(null); } }, monitor); } catch (TimeoutException | InterruptedException e) { return new Status(Status.ERROR, Plugin.PLUGIN_ID, "Unable to acquire lock to refresh repository " + repo.getName(), e); } } } catch (Exception e) { return new Status(Status.ERROR, Plugin.PLUGIN_ID, "Exception refreshing repositories", e); } return Status.OK_STATUS; } public void addViewer(final TreeViewer viewer, final RefreshModel model) { this.viewers.put(viewer, model); Central.onWorkspaceInit(new Success<Workspace,Void>() { @Override public Promise<Void> call(Promise<Workspace> resolved) throws Exception { new Job("Updating repositories") { @Override protected IStatus run(IProgressMonitor monitor) { final List<RepositoryPlugin> repositories = model.getRepositories(); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { viewer.setInput(repositories); } }); return Status.OK_STATUS; } }.schedule(); return null; } }); } public void removeViewer(TreeViewer viewer) { this.viewers.remove(viewer); } @Override public void bundleAdded(final RepositoryPlugin repository, Jar jar, File file) { refreshRepositories(repository); } @Override public void bundleRemoved(final RepositoryPlugin repository, Jar jar, File file) { refreshRepositories(repository); } @Override public void repositoryRefreshed(final RepositoryPlugin repository) { refreshRepositories(repository); } @Override public void repositoriesRefreshed() { refreshRepositories(null); } public void close() { if (registration != null) registration.unregister(); } public static Display getDisplay() { Display display = Display.getCurrent(); //may be null if outside the UI thread if (display == null) display = Display.getDefault(); return display; } public void setRepositories(final TreeViewer viewer, final RefreshModel refresh) { synchronized (this) { if (busy) { redo = true; return; } busy = true; } new WorkspaceJob("Setting repositories") { @Override public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException { ensureLoaded(monitor, refresh.getRepositories()); SWTConcurrencyUtil.execForControl(viewer.getControl(), true, new Runnable() { @Override public void run() { viewer.setInput(refresh.getRepositories()); synchronized (RepositoriesViewRefresher.this) { busy = false; if (redo) refreshRepositories(null); } } }); return Status.OK_STATUS; } }.schedule(); } }