Started working on new import UI. Not much to see yet, but if you append ?new=1 to the index page URL then you see the new form. It can only upload a file at the moment.

git-svn-id: http://google-refine.googlecode.com/svn/trunk@1971 7d457c2a-affb-35e4-300a-418c747d4874
This commit is contained in:
David Huynh 2011-01-02 23:09:08 +00:00
parent a81dcc50cc
commit 90794d5039
21 changed files with 1337 additions and 8 deletions

View File

@ -50,6 +50,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.refine.commands.Command;
import com.google.refine.commands.importing.ImportManager;
import com.google.refine.io.FileProjectManager;
import edu.mit.simile.butterfly.Butterfly;
@ -124,6 +125,7 @@ public class RefineServlet extends Butterfly {
s_dataDir = new File(data);
FileProjectManager.initialize(s_dataDir);
ImportManager.initialize(this);
if (_timer == null) {
_timer = new Timer("autosave");

View File

@ -312,7 +312,7 @@ public abstract class Command {
HttpServletRequest request,
HttpServletResponse response,
String message,
Exception e
Throwable e
) {
VelocityContext context = new VelocityContext();

View File

@ -0,0 +1,61 @@
/*
Copyright 2011, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.google.refine.commands.importing;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.refine.commands.Command;
public class CreateImportJobCommand extends Command {
final static Logger logger = LoggerFactory.getLogger("create-import-job_command");
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long id = ImportManager.singleton().createJob().id;
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Type", "application/json");
respond(response, "{ \"jobID\" : " + id + " }");
}
}

View File

@ -0,0 +1,104 @@
/*
Copyright 2011, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.google.refine.commands.importing;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.JSONException;
import org.json.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.refine.commands.Command;
import com.google.refine.commands.importing.ImportJob.State;
public class GetImportJobStatusCommand extends Command {
final static Logger logger = LoggerFactory.getLogger("get-import-job-status_command");
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long jobID = Long.parseLong(request.getParameter("jobID"));
ImportJob job = ImportManager.singleton().getJob(jobID);
Writer w = response.getWriter();
JSONWriter writer = new JSONWriter(w);
try {
writer.object();
if (job == null) {
writer.key("code"); writer.value("error");
writer.key("message"); writer.value("No such import job");
} else {
writer.key("code"); writer.value("ok");
writer.key("state");
if (job.state == State.NEW) {
writer.value("new");
} else if (job.state == State.RETRIEVING_DATA) {
writer.value("retrieving");
writer.key("progress"); writer.value(job.retrievingProgress);
writer.key("bytesSaved"); writer.value(job.bytesSaved);
} else if (job.state == State.READY) {
writer.value("ready");
} else if (job.state == State.ERROR) {
writer.value("error");
writer.key("message"); writer.value(job.errorMessage);
if (job.exception != null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
job.exception.printStackTrace(pw);
pw.flush();
sw.flush();
writer.key("stack"); writer.value(sw.toString());
}
}
}
writer.endObject();
} catch (JSONException e) {
throw new IOException(e);
} finally {
w.flush();
w.close();
}
}
}

View File

@ -0,0 +1,49 @@
package com.google.refine.commands.importing;
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import com.google.refine.model.meta.ImportSource;
public class ImportJob {
static public enum State {
NEW,
RETRIEVING_DATA,
READY,
ERROR
}
final public long id;
final public File dir;
public long lastTouched;
public State state = State.NEW;
// Data for retrieving phase
public int retrievingProgress = 0; // from 0 to 100
public long bytesSaved = 0; // in case percentage is unknown
public String errorMessage;
public Throwable exception;
public ImportSource importSource;
public ImportJob(long id, File dir) {
this.id = id;
this.dir = dir;
dir.mkdirs();
}
public void touch() {
lastTouched = System.currentTimeMillis();
}
public void dispose() {
try {
FileUtils.deleteDirectory(dir);
} catch (IOException e) {
}
}
}

View File

@ -0,0 +1,101 @@
package com.google.refine.commands.importing;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import com.google.refine.RefineServlet;
import com.google.refine.model.meta.ImportSource;
public class ImportManager {
static final private Map<String, Class<? extends ImportSource>> nameToImportSourceClass =
new HashMap<String, Class<? extends ImportSource>>();
static final private Map<String, String> importSourceClassNameToName =
new HashMap<String, String>();
/**
* Register a single import source class.
*
* @param name importer verb for importer
* @param importerObject object implementing the importer
*
* @return true if importer was loaded and registered successfully
*/
static public boolean registerImportSourceClass(String name, Class<? extends ImportSource> klass) {
if (nameToImportSourceClass.containsKey(name)) {
return false;
}
nameToImportSourceClass.put(name, klass);
importSourceClassNameToName.put(klass.getName(), name);
return true;
}
static public Class<? extends ImportSource> getImportSourceClass(String name) {
return nameToImportSourceClass.get(name);
}
static public String getImportSourceClassName(Class<? extends ImportSource> klass) {
return importSourceClassNameToName.get(klass.getName());
}
final private RefineServlet servlet;
final private Map<Long, ImportJob> jobs = new HashMap<Long, ImportJob>();
private File importDir;
static private ImportManager singleton;
static public void initialize(RefineServlet servlet) {
singleton = new ImportManager(servlet);
}
static public ImportManager singleton() {
return singleton;
}
private ImportManager(RefineServlet servlet) {
this.servlet = servlet;
}
private File getImportDir() {
if (importDir == null) {
File tempDir = servlet.getTempDir();
importDir = tempDir == null ? new File(".import-temp") : new File(tempDir, "import");
if (importDir.exists()) {
try {
// start fresh
FileUtils.deleteDirectory(importDir);
} catch (IOException e) {
}
}
importDir.mkdirs();
}
return importDir;
}
public ImportJob createJob() {
long id = System.currentTimeMillis() + (long) (Math.random() * 1000000);
File jobDir = new File(getImportDir(), Long.toString(id));
ImportJob job = new ImportJob(id, jobDir);
jobs.put(id, job);
return job;
}
public ImportJob getJob(long id) {
return jobs.get(id);
}
public void disposeJob(long id) {
ImportJob job = getJob(id);
if (job != null) {
job.dispose();
jobs.remove(id);
}
}
}

View File

@ -0,0 +1,102 @@
/*
Copyright 2011, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.google.refine.commands.importing;
import java.io.IOException;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.refine.commands.Command;
import com.google.refine.commands.importing.ImportJob.State;
import com.google.refine.model.meta.ImportSource;
import com.google.refine.util.ParsingUtilities;
public class RetrieveImportContentCommand extends Command {
final static Logger logger = LoggerFactory.getLogger("retrieve-import-content_command");
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/*
* The uploaded file is in the POST body as a "file part". If
* we call request.getParameter() then the POST body will get
* read and we won't have a chance to parse the body ourselves.
* This is why we have to parse the URL for parameters ourselves.
* Don't call request.getParameter() before calling internalImport().
*/
Properties options = ParsingUtilities.parseUrlParameters(request);
long jobID = Long.parseLong(options.getProperty("jobID"));
ImportJob job = ImportManager.singleton().getJob(jobID);
if (job == null) {
respondWithErrorPage(request, response, "No such import job", null);
return;
} else if (job.state != State.NEW) {
respondWithErrorPage(request, response, "Import job already started", null);
return;
}
Class<? extends ImportSource> importSourceClass =
ImportManager.getImportSourceClass(options.getProperty("source"));
if (importSourceClass == null) {
respondWithErrorPage(request, response, "No such import source class", null);
return;
}
try {
ImportSource importSource = importSourceClass.newInstance();
job.importSource = importSource;
job.state = State.RETRIEVING_DATA;
importSource.retrieveContent(request, options, job);
job.retrievingProgress = 100;
job.state = State.READY;
} catch (Throwable e) {e.printStackTrace();
job.state = State.ERROR;
job.errorMessage = e.getLocalizedMessage();
job.exception = e;
respondWithErrorPage(request, response, "Failed to kick start import job", e);
}
}
}

View File

@ -0,0 +1,62 @@
package com.google.refine.model.meta;
import java.io.File;
import java.io.InputStream;
import java.util.Date;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONWriter;
import com.google.refine.commands.importing.ImportJob;
public class FileUploadImportSource extends ImportSource {
public String originalFileName;
@Override
protected void customWrite(JSONWriter writer, Properties options)
throws JSONException {
writer.key("originalFileName"); writer.value(originalFileName);
}
@Override
protected void customReconstruct(JSONObject obj) throws JSONException {
if (obj.has("originalFileName")) {
originalFileName = obj.getString("originalFileName");
}
}
@Override
public void retrieveContent(HttpServletRequest request, Properties options, ImportJob job) throws Exception {
ServletFileUpload upload = new ServletFileUpload();
FileItemIterator iter = upload.getItemIterator(request);
while (iter.hasNext()) {
FileItemStream item = iter.next();
if (!item.isFormField()) {
String fileName = item.getName();
if (fileName.length() > 0) {
InputStream stream = item.openStream();
try {
File file = new File(job.dir, "data");
this.accessTime = new Date();
this.contentType = item.getContentType();
this.encoding = request.getCharacterEncoding();
this.originalFileName = fileName;
this.size = saveStreamToFileOrDir(
item.openStream(), file, this.contentType, fileName, job, request.getContentLength());
this.isArchive = file.isDirectory();
} finally {
stream.close();
}
}
}
}
}
}

View File

@ -0,0 +1,5 @@
package com.google.refine.model.meta;
public class ImportConfig {
}

View File

@ -0,0 +1,167 @@
package com.google.refine.model.meta;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Properties;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.servlet.http.HttpServletRequest;
import org.apache.tools.bzip2.CBZip2InputStream;
import org.apache.tools.tar.TarEntry;
import org.apache.tools.tar.TarInputStream;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONWriter;
import com.google.refine.Jsonizable;
import com.google.refine.commands.importing.ImportJob;
import com.google.refine.commands.importing.ImportManager;
import com.google.refine.util.ParsingUtilities;
abstract public class ImportSource implements Jsonizable {
public Date accessTime;
public long size;
public boolean isArchive = false;
public String contentType;
public String encoding;
@Override
public void write(JSONWriter writer, Properties options)
throws JSONException {
writer.object();
writer.key("type"); writer.value(ImportManager.getImportSourceClassName(this.getClass()));
writer.key("accessTime"); writer.value(ParsingUtilities.dateToString(accessTime));
writer.key("size"); writer.value(size);
writer.key("isArchive"); writer.value(isArchive);
writer.key("contentType"); writer.value(contentType);
writer.key("encoding"); writer.value(encoding);
writer.endObject();
}
public void reconstruct(JSONObject obj) throws JSONException {
if (obj.has("accessTime")) {
accessTime = ParsingUtilities.stringToDate(obj.getString("accessTime"));
}
if (obj.has("size")) {
size = obj.getLong("size");
}
if (obj.has("isArchive")) {
isArchive = obj.getBoolean("isArchive");
}
if (obj.has("contentType")) {
contentType = obj.getString("contentType");
}
if (obj.has("encoding")) {
encoding = obj.getString("encoding");
}
customReconstruct(obj);
}
abstract public void retrieveContent(HttpServletRequest request, Properties options, ImportJob job)
throws Exception;
abstract protected void customWrite(JSONWriter writer, Properties options) throws JSONException;
abstract protected void customReconstruct(JSONObject obj) throws JSONException;
static protected long saveStreamToFileOrDir(
InputStream is,
File file,
String contentType,
String fileNameOrUrl,
ImportJob job,
long expectedSize
) throws IOException {
InputStream archiveIS = null;
if (fileNameOrUrl != null) {
try {
if (fileNameOrUrl.endsWith(".tar.gz") ||
fileNameOrUrl.endsWith(".tar.gz.gz") ||
fileNameOrUrl.endsWith(".tgz")) {
archiveIS = new TarInputStream(new GZIPInputStream(is));
} else if (fileNameOrUrl.endsWith(".tar.bz2")) {
archiveIS = new TarInputStream(new CBZip2InputStream(is));
} else if (fileNameOrUrl.endsWith(".tar")) {
archiveIS = new TarInputStream(is);
} else if (fileNameOrUrl.endsWith(".zip")) {
archiveIS = new ZipInputStream(is);
}
} catch (IOException e) {
archiveIS = null;
}
}
job.bytesSaved = 0;
if (archiveIS == null) {
saveStreamToFile(is, file, job, true, expectedSize);
} else {
job.retrievingProgress = -1;
// NOTE(SM): unfortunately, java.io does not provide any generalized class for
// archive-like input streams so while both TarInputStream and ZipInputStream
// behave precisely the same, there is no polymorphic behavior so we have
// to treat each instance explicitly... one of those times you wish you had
// closures
if (archiveIS instanceof TarInputStream) {
TarInputStream tis = (TarInputStream) archiveIS;
TarEntry te;
while ((te = tis.getNextEntry()) != null) {
if (!te.isDirectory()) {
saveStreamToFile(tis, new File(file, te.getName()), job, false, 0);
}
}
} else if (archiveIS instanceof ZipInputStream) {
ZipInputStream zis = (ZipInputStream) archiveIS;
ZipEntry ze;
long compressedSize = 0;
while ((ze = zis.getNextEntry()) != null) {
if (!ze.isDirectory()) {
saveStreamToFile(zis, new File(file, ze.getName()), job, false, 0);
compressedSize += ze.getCompressedSize(); // this might be negative if not known
if (compressedSize > 0) {
job.retrievingProgress = (int) (compressedSize * 100 / expectedSize);
}
}
}
}
}
return job.bytesSaved;
}
static private void saveStreamToFile(
InputStream is,
File file,
ImportJob job,
boolean updateProgress,
long expectedSize
) throws IOException {
byte data[] = new byte[4096];
file.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos, data.length);
int count;
while ((count = is.read(data, 0, data.length)) != -1) {
bos.write(data, 0, count);
job.bytesSaved += count;
if (updateProgress) {
job.retrievingProgress = (int) (job.bytesSaved * 100 / expectedSize);
}
}
bos.flush();
bos.close();
}
}

View File

@ -0,0 +1,28 @@
package com.google.refine.model.meta;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONWriter;
import com.google.refine.commands.importing.ImportJob;
public class TextImportSource extends ImportSource {
@Override
protected void customWrite(JSONWriter writer, Properties options)
throws JSONException {
}
@Override
protected void customReconstruct(JSONObject obj) throws JSONException {
}
@Override
public void retrieveContent(HttpServletRequest request, Properties options, ImportJob job) throws Exception {
// TODO Auto-generated method stub
}
}

View File

@ -0,0 +1,34 @@
package com.google.refine.model.meta;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONWriter;
import com.google.refine.commands.importing.ImportJob;
public class WebImportSource extends ImportSource {
public String url;
@Override
protected void customWrite(JSONWriter writer, Properties options)
throws JSONException {
writer.key("url"); writer.value(url);
}
@Override
protected void customReconstruct(JSONObject obj) throws JSONException {
if (obj.has("url")) {
url = obj.getString("url");
}
}
@Override
public void retrieveContent(HttpServletRequest request, Properties options, ImportJob job) throws Exception {
// TODO Auto-generated method stub
}
}

View File

@ -34,14 +34,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
var html = "text/html";
var encoding = "UTF-8";
var ClientSideResourceManager = Packages.com.google.refine.ClientSideResourceManager;
var bundle = true;
var bundle = false;
var templatedFiles = {
// Requests with last path segments mentioned here
// will get served from .vt files with the same names
"import" : true,
"index" : true,
"project" : true,
"preferences" : true
"preferences" : true,
"project" : true
};
function registerCommands() {
@ -49,6 +50,10 @@ function registerCommands() {
RS.registerCommand(module, "get-version", new Packages.com.google.refine.commands.GetVersionCommand());
RS.registerCommand(module, "create-import-job", new Packages.com.google.refine.commands.importing.CreateImportJobCommand());
RS.registerCommand(module, "retrieve-import-content", new Packages.com.google.refine.commands.importing.RetrieveImportContentCommand());
RS.registerCommand(module, "get-import-job-status", new Packages.com.google.refine.commands.importing.GetImportJobStatusCommand());
RS.registerCommand(module, "create-project-from-upload", new Packages.com.google.refine.commands.project.CreateProjectCommand());
RS.registerCommand(module, "import-project", new Packages.com.google.refine.commands.project.ImportProjectCommand());
RS.registerCommand(module, "export-project", new Packages.com.google.refine.commands.project.ExportProjectCommand());
@ -160,6 +165,13 @@ function registerOperations() {
OR.registerOperation(module, "recon-copy-across-columns", Packages.com.google.refine.operations.recon.ReconCopyAcrossColumnsOperation);
}
function registerImportSourceClasses() {
var RM = Packages.com.google.refine.commands.importing.ImportManager;
RM.registerImportSourceClass("file-upload", Packages.com.google.refine.model.meta.FileUploadImportSource);
RM.registerImportSourceClass("text", Packages.com.google.refine.model.meta.TextImportSource);
RM.registerImportSourceClass("web", Packages.com.google.refine.model.meta.WebImportSource);
}
/*
* This optional function is invoked from the module's init() Java function.
*/
@ -168,6 +180,7 @@ function init() {
registerCommands();
registerOperations();
registerImportSourceClasses();
var RC = Packages.com.google.refine.model.recon.ReconConfig;
RC.registerReconConfig(module, "standard-service", Packages.com.google.refine.model.recon.StandardReconConfig);
@ -180,7 +193,9 @@ function init() {
"externals/jquery-ui/jquery-ui-1.8.custom.min.js",
"externals/date.js",
"scripts/util/string.js",
"scripts/index.js"
"scripts/util/dom.js",
"scripts/index.js",
"scripts/index/import-sources.js"
]
);
@ -196,6 +211,31 @@ function init() {
]
);
ClientSideResourceManager.addPaths(
"import/scripts",
module,
[
"externals/jquery-1.4.2.min.js",
"externals/jquery-ui/jquery-ui-1.8.custom.min.js",
"externals/date.js",
"scripts/util/string.js",
"scripts/util/dom.js",
"scripts/import.js"
]
);
ClientSideResourceManager.addPaths(
"import/styles",
module,
[
"externals/jquery-ui/css/ui-lightness/jquery-ui-1.8.custom.css",
"styles/jquery-ui-overrides.less",
"styles/common.less",
"styles/pure.css",
"styles/import.less"
]
);
ClientSideResourceManager.addPaths(
"project/scripts",
module,
@ -382,6 +422,14 @@ function process(path, request, response) {
} else {
if (lastSegment in templatedFiles) {
var context = {};
var params = new Packages.java.util.Properties();
var e = request.getParameterNames();
while (e.hasMoreElements()) {
var name = e.nextElement();
params.put(name, request.getParameterValues(name)[0]);
}
context.params = params;
context.projectID = request.getParameter("project");
var styles = ClientSideResourceManager.getPaths(lastSegment + "/styles");

View File

@ -0,0 +1,61 @@
<!doctype html>
<!--
Copyright 2011, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<html lang="en">
<head>
<meta charset="utf-8">
<title>Google Refine</title>
<script type="text/javascript" src="wirings.js"></script>
<link rel="icon" type="image/png" href="images/favicon.png">
$scriptInjection
$styleInjection
</head>
<body>
<div id="header">
<a id="app-home-button" href="./"><img alt="Google Refine" src="images/logo-googlerefine-30.png" width="129" height="29" /></a>
<div id="project-title">
Project Name: <span id="project-name-button" class="app-path-section" title="Click to rename project">Untitled</span>
</div>
</div>
<div id="body">
<div bind="topPanelDiv" id="top-panel">
<h1>Preview</h1>
</div>
<div bind="middlePanelDiv" id="middle-panel">
</div>
<div bind="bottomPanelDiv" id="bottom-panel">
</div>
</div>
</body>
</html>

View File

@ -36,10 +36,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<meta charset="utf-8">
<title>Google Refine</title>
<link rel="icon" type="image/png" href="images/favicon.png">
<script type="text/javascript" src="wirings.js"></script>
$scriptInjection
$styleInjection
</head>
<body>
#if($params.new == "1")
#set($newStyle = "")
#set($oldStyle = "display: none; ")
#else
#set($oldStyle = "")
#set($newStyle = "display: none; ")
#end
<div id="container">
<div id="logo"> </div>
<div id="header-home">
@ -58,7 +66,50 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<div class="content-block-footer"><a href="javascript:openWorkspaceDir()" class="secondary">Browse workspace directory</a></div>
</div>
<div id="project-create">
<form id="file-upload-form" method="post" enctype="multipart/form-data" action="/command/core/create-project-from-upload" accept-charset="UTF-8">
<h1 style="$newStyle">Create a New Project</h1>
<div style="$newStyle" id="import-panel"><table id="import-panel-layout">
<tr>
<td id="import-panel-tab-headers">
<div>Import data from</div>
</td>
<td id="import-panel-tab-bodies"></td>
</tr>
<tr>
<td colspan="2" id="import-panel-message">
<h3>What kinds of data files can I import?</h3>
<div>TSV, CSV, *SV, Excel (.xls and .xlsx), JSON, XML, RDF as XML, and
Google Spreadsheets are all supported. Support for other formats can
be added with Refine extensions.
</div>
</td>
</tr>
</table></div>
<div style="$newStyle" id="import-progress-panel">
<div class="grid-layout layout-normal layout-full"><table>
<tr><td colspan="3" id="import-progress-message"></td></tr>
<tr><td colspan="3">
<div id="import-progress-bar-frame"><div id="import-progress-bar-body"></div></div>
</td></tr>
<tr>
<td id="import-progress-message-left"></td>
<td id="import-progress-message-center"></td>
<td id="import-progress-message-right"></td>
</tr>
<tr><td colspan="3">
<button class="button" id="import-progress-cancel-button">Cancel</button>
</td></tr>
</table></div>
<iframe id="import-iframe" name="import-iframe"></iframe>
</div>
<div style="$newStyle" id="import-error-panel"><div class="grid-layout layout-normal layout-full"><table>
<tr><td id="import-error-message"></td></tr>
<tr><td id="import-error-stack"></td></tr>
<tr><td><button class="button button-primary" id="import-error-ok-button">OK</button></td></tr>
</table></div></div>
<form style="$oldStyle" id="file-upload-form" method="post" enctype="multipart/form-data" action="/command/core/create-project-from-upload" accept-charset="UTF-8">
<h1>Create a New Project</h1>
<h2 id="project-toggle">
<a class="secondary" href="javascript:showHide('file-upload-form', 'project-upload-form')">or Import an Existing Project</a>
@ -132,7 +183,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</div>
</form>
<form id="project-upload-form" method="post" enctype="multipart/form-data" action="/command/core/import-project" accept-charset="UTF-8" style="display:none;">
<form style="display: none;" id="project-upload-form" method="post" enctype="multipart/form-data" action="/command/core/import-project" accept-charset="UTF-8" style="display:none;">
<h1>Import an Existing Project</h1>
<h2 id="project-toggle">
<a class="secondary" href="javascript:showHide('project-upload-form', 'file-upload-form')">or Create a New Project</a>

View File

@ -0,0 +1,53 @@
/*
Copyright 2011, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var theImportJob = {};
var ui = {};
var Refine = {
};
function resize() {
var header = $("#header");
var leftPanelWidth = 300;
var width = $(window).width();
var top = $("#header").outerHeight();
var height = $(window).height() - top;
}
function onLoad() {
$(window).bind("resize", resize);
}
$(onLoad);

View File

@ -281,7 +281,149 @@ function showVersion() {
);
}
function renderImportPanel() {
var headerContainer = $('#import-panel-tab-headers');
var bodyContainer = $('#import-panel-tab-bodies');
var selectImportSourceTab = function(importSource) {
$('.import-panel-tab-body').hide();
$('.import-panel-tab-header').removeClass('selected');
importSource._divBody.show();
importSource._divHeader.addClass('selected');
importSource._ui.focus();
};
var createImportSourceTab = function(importSource) {
importSource._divBody = $('<div>')
.addClass('import-panel-tab-body')
.appendTo(bodyContainer)
.hide();
importSource._divHeader = $('<div>')
.addClass('import-panel-tab-header')
.text(importSource.label)
.appendTo(headerContainer)
.click(function() { selectImportSourceTab(importSource); });
importSource._ui = new importSource.ui(importSource._divBody);
};
for (var i= 0; i < ImportSources.length; i++) {
createImportSourceTab(ImportSources[i]);
}
selectImportSourceTab(ImportSources[0]);
}
function startImportJob(importSource, form, progressMessage) {
$.post(
"/command/core/create-import-job",
null,
function(data) {
var jobID = data.jobID;
form.attr("method", "post")
.attr("enctype", "multipart/form-data")
.attr("accept-charset", "UTF-8")
.attr("target", "import-iframe")
.attr("action", "/command/core/retrieve-import-content?" + $.param({
"jobID" : jobID,
"source" : importSource
}));
form[0].submit();
var start = new Date();
var timerID = window.setInterval(function() { pollImportJob(start, jobID, timerID); }, 1000);
initializeImportProgressPanel(progressMessage, jobID, timerID);
},
"json"
);
}
function initializeImportProgressPanel(progressMessage, jobID, timerID) {
$('#import-progress-message').text(progressMessage);
$('#import-progress-bar-body').css("width", "0%");
$('#import-progress-message-left').text('Starting');
$('#import-progress-message-center').empty();
$('#import-progress-message-right').empty();
$('#import-panel').hide();
$('#import-progress-panel').show();
$('#import-progress-cancel-button').unbind().click(function() {
$('#import-panel').show();
$('#import-progress-panel').hide();
// stop the iframe
$('#import-iframe')[0].contentWindow.stop();
// stop the timed polling
window.clearInterval(timerID);
// explicitly cancel the import job
$.post("/command/core/cancel-import-job?" + $.param({ "jobID" : jobID }));
});
}
function bytesToString(b) {
if (b >= 1024 * 1024) {
return Math.round(b / (1024 * 1024)) + " MB";
} else if (b >= 1024) {
return Math.round(b / 1024) + " KB";
} else {
return b + " bytes";
}
}
function pollImportJob(start, jobID, timerID) {
$.post(
"/command/core/get-import-job-status?" + $.param({ "jobID" : jobID }),
null,
function(data) {
if (data.code == "error") {
showImportJobError(data.message);
window.clearInterval(timerID);
} else if (data.state == "error") {
showImportJobError(data.message, data.stack);
window.clearInterval(timerID);
} else if (data.state == "retrieving") {
if (data.progress < 0) {
$('#import-progress-message-left').text(bytesToString(data.bytesSaved) + " saved");
} else {
$('#import-progress-bar-body').css("width", data.progress + "%");
$('#import-progress-message-left').text(data.progress + "% saved");
}
} else if (data.state == "ready") {
window.clearInterval(timerID);
// Just so if the user clicks Back the progress panel won't be showing if the DOM is cached.
$('#import-progress-panel').hide();
$('#import-panel').show();
window.location = "/import?" + $.param({ "jobID" : jobID });
}
},
"json"
);
}
function showImportJobError(message, stack) {
$('#import-error-message').text(message);
$('#import-error-stack').text(stack || 'No technical details.');
$('#import-progress-panel').hide();
$('#import-error-panel').show();
$('#import-error-ok-button').unbind().click(function() {
$('#import-error-panel').hide();
$('#import-panel').show();
});
}
function onLoad() {
renderImportPanel();
fetchProjects();
$("#project-file-input").change(function() {

View File

@ -0,0 +1,7 @@
<form bind="form"><div class="grid-layout layout-normal"><table>
<tr><td>File to import:</td></tr>
<tr><td><input type="file" bind="fileInput" name="project-file" />
<input type="hidden" name="project-name" bind="nameInput" />
</td></tr>
<tr><td><button bind="nextButton" class="button button-primary" type="button">Next &raquo;</button></td></tr>
</table></div></form>

View File

@ -0,0 +1,82 @@
/*
Copyright 2011, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var ImportSources = [];
function ThisComputerImportSourceUI(bodyDiv) {
bodyDiv.html(DOM.loadHTML("core", "scripts/index/import-from-computer-form.html"));
var elmts = DOM.bind(bodyDiv);
elmts.nextButton.click(function(evt) {
if (elmts.fileInput[0].files.length === 0) {
window.alert("You must specify a data file to import.");
} else {
elmts.nameInput[0].value = elmts.fileInput[0].files[0].fileName
.replace(/\.\w+/, "").replace(/[_-]/g, " ");
startImportJob("file-upload", elmts.form, "Uploading data file ...");
}
});
}
ImportSources.push({
"label" : "This Computer",
"ui" : ThisComputerImportSourceUI
});
ThisComputerImportSourceUI.prototype.focus = function() {
}
function UrlImportSourceUI(bodyDiv) {
}
ImportSources.push({
"label" : "Web Address (URL)",
"ui" : UrlImportSourceUI
});
UrlImportSourceUI.prototype.focus = function() {
}
function ClipboardImportSourceUI(bodyDiv) {
}
ImportSources.push({
"label" : "Clipboard",
"ui" : ClipboardImportSourceUI
});
ClipboardImportSourceUI.prototype.focus = function() {
}

View File

@ -0,0 +1,77 @@
/*
Copyright 2011, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
@import-less url("theme.less");
#project-title {
position: absolute;
top: 0;
left: 140px;
height: 40px;
padding: 4px 0 0 0;
font-size: 1.6em;
}
#project-name-button {
padding: 3px;
background: @fill_editable;
border: 1px solid #ccc;
border-top: 1px solid #aaa;
}
#project-name-button:hover {
}
#body {
position: relative;
margin: @padding_loose;
background: @fill_primary;
border: 1px solid @chrome_primary;
border-radius: @padding_tight;
}
#top-panel {
padding: @padding_loose;
}
#middle-panel {
border-top: 1px solid @chrome_primary;
border-bottom: 1px solid @chrome_primary;
background: white;
height: 200px;
overflow: auto;
}
#bottom-panel {
height: 200px;
}

View File

@ -165,4 +165,97 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
position: absolute;
top: 12px;
right: 10px;
}
}
#import-panel {
font-size: 1.3em;
}
#import-panel-message {
padding: @padding_loose;
}
#import-panel-layout {
width: 100%;
border-collapse: collapse;
}
#import-panel-layout > tbody > tr > td {
border-top: 1px solid @chrome_primary;
border-bottom: 1px solid @chrome_primary;
}
#import-panel-tab-bodies {
background: white;
}
#import-panel-tab-headers {
width: 15em;
background: @fill_primary;
border-right: 1px solid @chrome_primary;
padding-left: @padding_normal;
padding-bottom: @padding_looser;
}
#import-panel-tab-headers > div {
padding: @padding_tight;
padding-right: @padding_normal;
}
.import-panel-tab-body {
padding: @padding_loose;
}
.import-panel-tab-header {
cursor: pointer;
color: @link_primary;
}
.import-panel-tab-header.selected {
cursor: default;
color: black;
background: white;
font-weight: bold;
position: relative;
left: 1px;
border: 1px solid @chrome_primary;
border-right: none;
}
#import-progress-panel {
display: none;
font-size: 1.3em;
padding: @padding_loose;
background: white;
}
#import-progress-bar-frame {
border: 1px solid @chrome_primary;
padding: @padding_tighter;
}
#import-progress-bar-body {
background: @chrome_primary;
height: 1em;
position: relative;
width: 30%;
}
#import-iframe {
position: fixed;
width: 200px;
height: 200px;
left: -300px;
top: -300px;
}
#import-error-panel {
display: none;
font-size: 1.3em;
padding: @padding_loose;
}
#import-error-message {
}
#import-error-stack {
font-family: monospace;
whitespace: pre;
padding: @padding_normal;
border: 1px solid @chrome_primary;
}