/******************************************************************************* * Copyright (c) 2013, 2015 Red Hat, Inc. * 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: * Red Hat Inc. - initial API and implementation and/or initial documentation *******************************************************************************/ package org.eclipse.thym.core.plugin.registry; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.apache.commons.io.FileUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.cache.CacheConfig; import org.apache.http.impl.client.cache.CachingHttpClient; import org.apache.http.impl.client.cache.HeapResourceFactory; import org.eclipse.core.runtime.Assert; 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.ecf.filetransfer.IncomingFileTransferException; import org.eclipse.ecf.filetransfer.identity.FileCreateException; import org.eclipse.ecf.filetransfer.identity.FileIDFactory; import org.eclipse.ecf.filetransfer.identity.IFileID; import org.eclipse.ecf.filetransfer.service.IRetrieveFileTransfer; import org.eclipse.thym.core.HybridCore; import org.eclipse.thym.core.internal.util.BundleHttpCacheStorage; import org.eclipse.thym.core.internal.util.HttpUtil; import org.eclipse.thym.core.platform.PlatformConstants; import org.eclipse.thym.core.plugin.registry.CordovaRegistryPlugin.RegistryPluginVersion; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; public class CordovaPluginRegistryManager { private static final String REGISTRY_URL = "http://registry.npmjs.org/"; // private static final String PLUGIN_LIST_URL = private final File cacheHome; private HashMap<String, CordovaRegistryPlugin> detailedPluginInfoCache = new HashMap<String, CordovaRegistryPlugin>(); public CordovaPluginRegistryManager() { cacheHome = new File(FileUtils.getUserDirectory(), ".plugman"+File.separator+"cache"); } public CordovaRegistryPlugin getCordovaPluginInfo(String name) throws CoreException { CordovaRegistryPlugin plugin = detailedPluginInfoCache.get(name); if(plugin != null ) return plugin; DefaultHttpClient defHttpClient = new DefaultHttpClient(); HttpUtil.setupProxy(defHttpClient); HttpClient client =new CachingHttpClient(defHttpClient, new HeapResourceFactory(), new BundleHttpCacheStorage(HybridCore.getContext().getBundle()), getCacheConfig()); HttpGet get = new HttpGet(REGISTRY_URL+name); HttpResponse response; try { response = client.execute(get); HttpEntity entity = response.getEntity(); InputStream stream = entity.getContent(); JsonReader reader = new JsonReader(new InputStreamReader(stream)); plugin = new CordovaRegistryPlugin(); readPluginInfo(reader, plugin); this.detailedPluginInfoCache.put(name, plugin); return plugin; } catch (ClientProtocolException e) { throw new CoreException(new Status(IStatus.ERROR, HybridCore.PLUGIN_ID, "Can not retrieve plugin information for " + name, e)); } catch (IOException e) { throw new CoreException(new Status(IStatus.ERROR, HybridCore.PLUGIN_ID, "Can not retrieve plugin information for " + name, e)); } } /** * Returns a directory where the given version of the Cordova Plugin * can be installed from. This method downloads the given * cordova plugin if necessary. * * @param plugin * @return */ public File getInstallationDirectory( RegistryPluginVersion plugin, IProgressMonitor monitor ){ if(monitor == null ) monitor = new NullProgressMonitor(); File pluginDir = getFromCache(plugin); if (pluginDir != null ){ return pluginDir; } File newCacheDir = calculateCacheDir(plugin); IRetrieveFileTransfer transfer = HybridCore.getDefault().getFileTransferService(); IFileID remoteFileID; try { remoteFileID = FileIDFactory.getDefault().createFileID(transfer.getRetrieveNamespace(), plugin.getTarball()); Object lock = new Object(); PluginReceiver receiver = new PluginReceiver(newCacheDir,monitor, lock); synchronized (lock) { transfer.sendRetrieveRequest(remoteFileID, receiver, null); lock.wait(); } } catch (FileCreateException e) { HybridCore.log(IStatus.ERROR, "Cordova plugin fetch error", e); } catch (IncomingFileTransferException e) { HybridCore.log(IStatus.ERROR, "Cordova plugin fetch error", e); } catch (InterruptedException e) { HybridCore.log(IStatus.ERROR, "Cordova plugin fetch error", e); } return new File(newCacheDir, "package"); } private File getFromCache( RegistryPluginVersion plugin ){ File cachedPluginDir = calculateCacheDir(plugin); File packageDir = new File(cachedPluginDir,"package"); if( !packageDir.isDirectory()){ return null; } File pluginxml = new File(packageDir, PlatformConstants.FILE_XML_PLUGIN); if(cachedPluginDir.isDirectory() && pluginxml.exists()) return packageDir; return null; } private File calculateCacheDir(RegistryPluginVersion plugin) { File cachedPluginDir = new File(this.cacheHome, plugin.getName() + File.separator + plugin.getVersionNumber()); return cachedPluginDir; } private static CacheConfig getCacheConfig(){ CacheConfig config = new CacheConfig(); config.setMaxObjectSize(120 *1024); return config; } public List<CordovaRegistryPluginInfo> retrievePluginInfos(IProgressMonitor monitor) throws CoreException { if(monitor == null ) monitor = new NullProgressMonitor(); monitor.beginTask("Retrieve plug-in registry catalog", 10); DefaultHttpClient theHttpClient = new DefaultHttpClient(); HttpUtil.setupProxy(theHttpClient); HttpClient client = new CachingHttpClient(theHttpClient, new HeapResourceFactory(), new BundleHttpCacheStorage(HybridCore.getContext().getBundle()), getCacheConfig()); JsonReader reader= null; try { if(monitor.isCanceled()){ return null; } String url = REGISTRY_URL + "-/_view/byKeyword?startkey=%5B%22ecosystem:cordova%22%5D&endkey=%5B%22ecosystem:cordova1%22%5D&group_level=3"; HttpGet get = new HttpGet(URI.create(url)); HttpResponse response = client.execute(get); HttpEntity entity = response.getEntity(); InputStream stream = entity.getContent(); monitor.worked(7); reader = new JsonReader(new InputStreamReader(stream)); reader.beginObject();//start the Registry final ArrayList<CordovaRegistryPluginInfo> plugins = new ArrayList<CordovaRegistryPluginInfo>(); while(reader.hasNext()){ JsonToken token = reader.peek(); switch (token) { case BEGIN_ARRAY: reader.beginArray(); break; case BEGIN_OBJECT: plugins.add(parseCordovaRegistryPluginInfo(reader)); break; default: reader.skipValue(); break; } } return plugins; } catch (ClientProtocolException e) { throw new CoreException(new Status(IStatus.ERROR, HybridCore.PLUGIN_ID, "Can not retrieve plugin catalog",e)); } catch (IOException e) { throw new CoreException(new Status(IStatus.ERROR, HybridCore.PLUGIN_ID, "Can not retrieve plugin catalog", e)); }finally{ if(reader != null ) try { reader.close(); } catch (IOException e) { /*ignored*/ } monitor.done(); } } private CordovaRegistryPluginInfo parseCordovaRegistryPluginInfo(JsonReader reader) throws IOException{ CordovaRegistryPluginInfo info = new CordovaRegistryPluginInfo(); reader.beginObject(); reader.skipValue(); // name reader.beginArray(); reader.nextString(); //ecosystem:cordova info.setName(safeReadStringValue(reader)); info.setDescription(safeReadStringValue(reader)); reader.endArray(); reader.nextName();reader.nextInt(); reader.endObject(); return info; } private String safeReadStringValue(JsonReader reader) throws IOException{ if(reader.peek() == JsonToken.STRING){ return reader.nextString(); } reader.skipValue(); return ""; } private void readVersionInfo(JsonReader reader, RegistryPluginVersion version)throws IOException{ Assert.isNotNull(version); reader.beginObject(); while(reader.hasNext()){ JsonToken token = reader.peek(); switch (token) { case NAME: String name = reader.nextName(); if("dist".equals(name)){ parseDistDetails(reader, version); break; } break; default: reader.skipValue(); break; } } reader.endObject(); } private void readPluginInfo(JsonReader reader, CordovaRegistryPlugin plugin ) throws IOException { Assert.isNotNull(plugin); reader.beginObject(); while (reader.hasNext()) { JsonToken token = reader.peek(); switch (token) { case NAME: { String name = reader.nextName(); if ("name".equals(name)) { plugin.setName(reader.nextString()); break; } if ("description".equals(name)) { plugin.setDescription(reader.nextString()); break; } if ("keywords".equals(name)) { parseKeywords(reader, plugin); break; } if("maintainers".equals(name)){ parseMaintainers(reader,plugin); break; } if("dist-tags".equals(name)){ parseLatestVersion(reader,plugin); break; } if("versions".equals(name) ){ parseVersions(reader, plugin); break; } if("license".equals(name)){ plugin.setLicense(reader.nextString()); break; } break; } default: reader.skipValue(); break; } } reader.endObject(); } private void parseDistDetails(JsonReader reader, RegistryPluginVersion plugin) throws IOException{ reader.beginObject(); JsonToken token = reader.peek(); while(token != JsonToken.END_OBJECT){ switch (token) { case NAME: String name = reader.nextName(); if("shasum".equals(name)){ plugin.setShasum(reader.nextString()); break; } if("tarball".equals(name)){ plugin.setTarball(reader.nextString()); break; } break; default: reader.skipValue(); break; } token = reader.peek(); } reader.endObject(); } private void parseVersions(JsonReader reader, CordovaRegistryPlugin plugin) throws IOException{ reader.beginObject();//versions JsonToken token = reader.peek(); while( token != JsonToken.END_OBJECT ){ switch (token) { case NAME: RegistryPluginVersion version = plugin.new RegistryPluginVersion(); version.setVersionNumber(reader.nextName()); readVersionInfo(reader, version); plugin.addVersion(version); break; default: reader.skipValue(); break; } token = reader.peek(); } reader.endObject(); } private void parseLatestVersion(JsonReader reader, CordovaRegistryPlugin plugin) throws IOException{ reader.beginObject(); JsonToken token = reader.peek(); while ( token != JsonToken.END_OBJECT){ switch (token) { case NAME: String tag = reader.nextName(); if("latest".equals(tag)){ plugin.setLatestVersion(reader.nextString()); } break; default: reader.skipValue(); break; } token = reader.peek(); } reader.endObject(); } private void parseMaintainers(JsonReader reader, CordovaRegistryPlugin plugin) throws IOException{ reader.beginArray(); String name=null, email = null; JsonToken token = reader.peek(); while( token != JsonToken.END_ARRAY ){ switch (token) { case BEGIN_OBJECT: reader.beginObject(); name = email = null; break; case END_OBJECT: reader.endObject(); plugin.addMaintainer(email, name); break; case NAME: String tagName = reader.nextName(); if("name".equals(tagName)){ name = reader.nextString(); break; } if("email".equals(tagName)){ email = reader.nextString(); break; } default: Assert.isTrue(false, "Unexpected token"); break; } token =reader.peek(); } reader.endArray(); } private void parseKeywords(JsonReader reader, CordovaRegistryPlugin plugin) throws IOException{ reader.beginArray(); while(reader.hasNext()){ plugin.addKeyword(reader.nextString()); } reader.endArray(); } }