blob: 4bf1c886d789f4e0ec6525fe87b237f7115b8f6e [file] [log] [blame]
// Copyright (C) 2009 The Android Open Source Project
//
// Licensed 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 com.google.gerrit.pgm.init;
import com.google.common.base.Strings;
import com.google.common.io.Files;
import com.google.gerrit.common.Die;
import com.google.gerrit.common.IoUtil;
import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.util.HttpSupport;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
/** Get optional or required 3rd party library files into $site_path/lib. */
class LibraryDownloader {
private final ConsoleUI ui;
private final File lib_dir;
private boolean required;
private String name;
private String jarUrl;
private String sha1;
private String remove;
private List<LibraryDownloader> needs;
private LibraryDownloader neededBy;
private File dst;
private boolean download; // download or copy
private boolean exists;
@Inject
LibraryDownloader(ConsoleUI ui, SitePaths site) {
this.ui = ui;
this.lib_dir = site.lib_dir;
this.needs = new ArrayList<>(2);
}
void setName(final String name) {
this.name = name;
}
void setJarUrl(final String url) {
this.jarUrl = url;
download = jarUrl.startsWith("http");
}
void setSHA1(final String sha1) {
this.sha1 = sha1;
}
void setRemove(String remove) {
this.remove = remove;
}
void addNeeds(LibraryDownloader lib) {
needs.add(lib);
}
void downloadRequired() {
setRequired(true);
download();
}
void downloadOptional() {
required = false;
download();
}
private void setRequired(boolean r) {
required = r;
for (LibraryDownloader d : needs) {
d.setRequired(r);
}
}
private void download() {
if (jarUrl == null || !jarUrl.contains("/")) {
throw new IllegalStateException("Invalid JarUrl for " + name);
}
final String jarName = jarUrl.substring(jarUrl.lastIndexOf('/') + 1);
if (jarName.contains("/") || jarName.contains("\\")) {
throw new IllegalStateException("Invalid JarUrl: " + jarUrl);
}
if (name == null) {
name = jarName;
}
dst = new File(lib_dir, jarName);
if (dst.exists()) {
exists = true;
} else if (shouldGet()) {
doGet();
}
if (exists) {
for (LibraryDownloader d : needs) {
d.neededBy = this;
d.downloadRequired();
}
}
}
private boolean shouldGet() {
if (ui.isBatch()) {
return required;
} else {
final StringBuilder msg = new StringBuilder();
msg.append("\n");
msg.append("Gerrit Code Review is not shipped with %s\n");
if (neededBy != null) {
msg.append(String.format(
"** This library is required by %s. **\n",
neededBy.name));
} else if (required) {
msg.append("** This library is required for your configuration. **\n");
} else {
msg.append(" If available, Gerrit can take advantage of features\n");
msg.append(" in the library, but will also function without it.\n");
}
msg.append(String.format(
"%s and install it now", download ? "Download" : "Copy"));
return ui.yesno(true, msg.toString(), name);
}
}
private void doGet() {
if (!lib_dir.exists() && !lib_dir.mkdirs()) {
throw new Die("Cannot create " + lib_dir);
}
try {
removeStaleVersions();
if (download) {
doGetByHttp();
} else {
doGetByLocalCopy();
}
verifyFileChecksum();
} catch (IOException err) {
dst.delete();
if (ui.isBatch()) {
throw new Die("error: Cannot get " + jarUrl, err);
}
System.err.println();
System.err.println();
System.err.println("error: " + err.getMessage());
System.err.println("Please download:");
System.err.println();
System.err.println(" " + jarUrl);
System.err.println();
System.err.println("and save as:");
System.err.println();
System.err.println(" " + dst.getAbsolutePath());
System.err.println();
System.err.flush();
ui.waitForUser();
if (dst.exists()) {
verifyFileChecksum();
} else if (!ui.yesno(!required, "Continue without this library")) {
throw new Die("aborted by user");
}
}
if (dst.exists()) {
exists = true;
IoUtil.loadJARs(dst);
}
}
private void removeStaleVersions() {
if (!Strings.isNullOrEmpty(remove)) {
String[] names = lib_dir.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.matches("^" + remove + "$");
}
});
if (names != null) {
for (String old : names) {
String bak = "." + old + ".backup";
ui.message("Renaming %s to %s", old, bak);
if (!new File(lib_dir, old).renameTo(new File(lib_dir, bak))) {
throw new Die("cannot rename " + old);
}
}
}
}
}
private void doGetByLocalCopy() throws IOException {
System.err.print("Copying " + jarUrl + " ...");
File f = url2file(jarUrl);
if (!f.exists()) {
StringBuilder msg = new StringBuilder()
.append("\n")
.append("Can not find the %s at this location: %s\n")
.append("Please provide alternative URL");
f = url2file(ui.readString(null, msg.toString(), name, jarUrl));
}
Files.copy(f, dst);
}
private static File url2file(final String urlString) throws IOException {
final URL url = new URL(urlString);
try {
return new File(url.toURI());
} catch (URISyntaxException e) {
return new File(url.getPath());
}
}
private void doGetByHttp() throws IOException {
System.err.print("Downloading " + jarUrl + " ...");
System.err.flush();
try {
final ProxySelector proxySelector = ProxySelector.getDefault();
final URL url = new URL(jarUrl);
final Proxy proxy = HttpSupport.proxyFor(proxySelector, url);
final HttpURLConnection c = (HttpURLConnection) url.openConnection(proxy);
final InputStream in;
switch (HttpSupport.response(c)) {
case HttpURLConnection.HTTP_OK:
in = c.getInputStream();
break;
case HttpURLConnection.HTTP_NOT_FOUND:
throw new FileNotFoundException(url.toString());
default:
throw new IOException(url.toString() + ": " + HttpSupport.response(c)
+ " " + c.getResponseMessage());
}
try {
final OutputStream out = new FileOutputStream(dst);
try {
final byte[] buf = new byte[8192];
int n;
while ((n = in.read(buf)) > 0) {
out.write(buf, 0, n);
}
} finally {
out.close();
}
} finally {
in.close();
}
System.err.println(" OK");
System.err.flush();
} catch (IOException err) {
dst.delete();
System.err.println(" !! FAIL !!");
System.err.flush();
throw err;
}
}
private void verifyFileChecksum() {
if (sha1 != null) {
try {
final MessageDigest md = MessageDigest.getInstance("SHA-1");
final FileInputStream in = new FileInputStream(dst);
try {
final byte[] buf = new byte[8192];
int n;
while ((n = in.read(buf)) > 0) {
md.update(buf, 0, n);
}
} finally {
in.close();
}
if (sha1.equals(ObjectId.fromRaw(md.digest()).name())) {
System.err.println("Checksum " + dst.getName() + " OK");
System.err.flush();
} else if (ui.isBatch()) {
dst.delete();
throw new Die(dst + " SHA-1 checksum does not match");
} else if (!ui.yesno(null /* force an answer */,
"error: SHA-1 checksum does not match\n" + "Use %s anyway",//
dst.getName())) {
dst.delete();
throw new Die("aborted by user");
}
} catch (IOException checksumError) {
dst.delete();
throw new Die("cannot checksum " + dst, checksumError);
} catch (NoSuchAlgorithmException checksumError) {
dst.delete();
throw new Die("cannot checksum " + dst, checksumError);
}
}
}
}