tus / tus-java-client Goto Github PK
View Code? Open in Web Editor NEWThe tus client for Java.
Home Page: https://tus.io
License: MIT License
The tus client for Java.
Home Page: https://tus.io
License: MIT License
I tried to implement this tus-client for testing purposes of a Tus-compliant API in my Spring Boot application and had the following issue:
Since the TusUploader is using the HttpUrlConnection class to create a HTTP connection it suffers from a wont-fix bug in the OpenJDK (Ref.: https://stackoverflow.com/a/25163132). Consequently, the implementation sends a POST along with "X-HTTP-Method-Override" -> "PATCH"
when it is running in JREs that do not support PATCH in that particular class.
Indeed the header "emulates" a PATCH request to be compliant with the TUS protocol specifications, but this needs to be specifically implemented on the server-side. E.g. in my case:
@RequestMapping(method = RequestMethod.POST, headers = {"X-HTTP-Method-Override=PATCH"}, value = "/{id}")
This happened to me on Eclipse Termurin 17.0.5 JDK and is probably a wide-spread problem.
My only recommendation would be to specifically mention this in your documentation as this will reduce implementation overhead. And most importantly: It means that this client is not 100% Tus compliant.
Best
The documentation on the front page says
<dependency> <groupId>io.tus.java.client</groupId> <artifactId>tus-java-client</artifactId> <version>0.5.0</version> </dependency>
but on Maven central only 0.4.5 is available https://central.sonatype.com/artifact/io.tus.java.client/tus-java-client/0.4.5/versions
Question
How to set Proxy for TUSClient?
We are using TUSClient to upload a large file to a server in chunks. We are required to go via Proxy server (with authentication).
But when I see the createUpload
method in TUSClient
class, I see this below line:
HttpURLConnection connection = (HttpURLConnection) uploadCreationURL.openConnection();
I could not see any Proxy feature that we can enable or introduce. Please suggest.
Setup details
Please provide following details, if applicable to your situation:
HI,
Tus-java-client is running on Network machine 10.49.9.11:8080 and also REST-API is running on this IP.
Instead of sending file through Postman to REST_API, I am sending only file name. Getting above exception
at line final TusUpload upload = new TusUpload(file);
API end point is
@RequestMapping("uploadDocument")
@CrossOrigin("*")
public Object fileUpload(@RequestPart String fileName) throws IOException, InvalidFormatException, ClassNotFoundException, ProtocolException, ParseException {
final TusUpload upload = new TusUpload(file);
}
When i deploy the API jar to local machine and calling like http://localhost:8090/uploadDocument
then it is working fine. Although Tus-java-client server is running on 10.49.9.11 IP.
public Object fileUpload(@RequestPart String fileName) ---- We cannot use Multipart in place of String.
I want this API to be called from anywhere with oinly file name and then rest of the thing will work by Tus-client (chunking, uploading and resuming).
Bottom Line
How do i send files from local machine (through Postman) to the API which is running on IP.
I do not want to use Multipart.
Hello folks,
as far as I understand the client code, it does not support the "Upload-Defer-Length" which is required according to the "1.0.0" protocol version.
I am also wondering who to support this feature, as the current implementation uses a primitive long data type instead of a Long object. Therefore a "null" check is not possible.
public class TusUpload {
private long size;
...
Greetings
Greetings,
My TUS server is only available through a VPN connection. In order for me to setup said connection, I need to use a provided SDK, this SDK basically enables the VPN connection for either OkHttpClient
or HttpURLConnection
. This library, tus-java-client, current supports the latter. In order for my use case to work correctly, I want to override the createUpload
method. By doing so, I can customize the used HttpURLConnection
as needed.
There are currently 2 issues by doing so:
urlStore
'urlStore' has private access in 'io.tus.java.client.TusClient'
getTusInputStream
'getTusInputStream()' is not public in 'io.tus.java.client.TusUpload'. Cannot be accessed from outside package
Is there perhaps a solution available from this library? If needed, I could help on a PR.
Update
I just came across prepareConnection, this might satisfy my requirements.
remove(String fingerprint)
is never called for TusURLStore after a completed upload
The upload fingerprints are kept at the TusURLStore, this coul be a problem if a persistent URLStore is used as in the Android client where the PreferencesStore is used with this purpose.
Which is the expected behaviour? Should'nt be removed from the URLStore?
Hi friends,
I'm actually new at working with TUS protocol.
I'm just wondering it's possible to send IP camera frames with TUS?
Question
I'm facing this issue as well #66 and I'd like to be able to see detailed information about the ProtocolException
being thrown.
Ideally, I'd like to be able to see the underlying cause of the error 400
, what was the actual response from the Cloudflare servers, etc., instead of this simple message (currently shown): io.tus.java.client.ProtocolException: unexpected status code (400) while creating upload
Of course, if you know of a solution for the linked issue, that'd be awesome!
Also, I've contacted already Cloudflare support -- waiting for a reply.
Setup details
Please provide following details, if applicable to your situation:
The README links to https://tus.github.io/tus-java-client/javadoc/, but seems to be outdated. I am not sure if we have a tool to automatically update it anymore.
Response contains different upload-offset value than expected
this is coming on tus-android-client
when using javascript there is no issue
Field "size" in TusUpload should be mandatory when using streams. I didn't provide it and upload worked BUT on receiver side there was a problem with object UploadInfo (method uploadInfo.isUploadInProgress() in particular). Despite upload being finished isUploadInProgress showed "true". Moreover on client side field is named size, and on server side it's called length. It's a bit misleading.
As a solution I propose to remove no arguments constructor and replace it with constructor, that accepts all required parameters. Or to use builder that throws exceptions if not all mandatory field combinations are provided.
If a server has lost track of a started upload (e.g. sends back a 404), should the client default to creating a new one instead of failing or throwing the exception?
I'm getting "unexpected status code (411) while creating upload" while trying to upload a file using tus client, althogugh I'm setting the header specificaly. Any thoughts?
When use tusClient.setheaders, the serverside get wrongly encoded header values. I know we can use URLEncode to encode the header and server side to decode. But what i wrote is a common way for uploading, some of the header transported doesn't need to URLEncode. If encode will broke the "common"
Hi
How to get uploaded bytes of any chunk ?
when I upload small file for example 5 MB , progress bar wait and 50 percent show
Hello, I am currently using the example android application and when I select a picture, and it gets to the end of the upload, the following error gets thrown:
D/Loop: Error occurred: response contains different Upload-Offset value (1386863) than expected (1391815) Error class: class io.tus.java.client.ProtocolException
So it seems like it is undershooting the number of bytes it needs to upload? This seems strange, and I haven't touched the code from the example application, so I don't know what's wrong. I should say that it did successfully upload on a few occasions, but this error happens 95% of the time!
PS I will be hunting this down as I can, I just felt like posting this to see if anyone had any ideas.
Edit: after some logging, I found it might be mixing up numbers?
D/Loop: Looped! 1391815/1386863 (uploaded/total)
D/Loop: Error occurred: response contains different Upload-Offset value (1386863) than expected (1391815) Error clasS: class io.tus.java.client.ProtocolException
Once i start upload for any file I can't cancel/abort it. How can I do that?
Is your feature request related to a problem? Please describe.
When the upload is interrupted by for example a power outage, the upload must restart. That is because only MemoryURLStore exists.
Describe the solution you'd like
There should be a persistent URL store which could be loaded after a restart of the computer.
Can you provide help with implementing this feature?
I have some code and I will create a pull request.
Additional context
It's a feature that was requested by my boss, but we are moving to AWS cloud, so our old infrastructure will not be updated. Thus I don't want my efforts wasted, the tus team might like this solution. I hope.
Is your feature request related to a problem? Please describe.
Users currently cannot retrieve the response body. This includes successful responses (e.g. after an upload is finished), but also error cases, where the error message only includes the status code. The response body is not included in the message, although it usually includes more details.
Describe the solution you'd like
Major changes:
uploadChunk()
with start and stop methods similar to tus-js-client (remove chunk size as well)HttpURLConnection
with more modern HTTP client (maybe okhttp or HttpClient?)
Additional features:
If you upload a file of size equal to payload size, the upload fails.
Because it tries to make 1 extra attempt to upload the last chunk with 0 file length.
This seems a very generic issue, it shouldn't make a new attempt to upload the last chunk of size 0 byte.
while (uploader.uploadChunk() > 0)
If the server sends back an error code, uploadChunk() will happily keep putting data onto the outputstream.
The current tusd implementation does not close the connection on such an error code (presumably to reuse it efficiently). Is that the more proper thing to do in the HTTP spec?
Hi. I see that request is http, even if I set
client.setUploadCreationURL(new URL("https://....."));
I can't find the root cause of this problem. The upload almost completed at 096.31% and it raises the above exception.
private static final String ENDPOINT = "https://api.cloudflare.com/client/v4/zones/73eee5a625f77d66180a52dd72/media";
try {
File file = getFile(uploadRequest);
log.info("file Size {}",file.length());
if(Validator.isNull(file)){
log.info("FILE NOT FOUND");
return;
}
// When Java's HTTP client follows a redirect for a POST request, it will change the
// method from POST to GET which can be disabled using following system property.
// If you do not enable strict redirects, the tus-java-client will not follow any
// redirects but still work correctly.
Map<String,String> headers = new HashMap<>();
headers.put("X-Auth-Email","");
headers.put("X-Auth-Key","");
// Create a new TusClient instance
final TusClient client = new TusClient();
client.setHeaders(headers);
// Configure tus HTTP endpoint. This URL will be used for creating new uploads
// using the Creation extension
client.setUploadCreationURL(new URL(ENDPOINT));
// Enable resumable uploads by storing the upload URL in memory
client.enableResuming(new TusURLMemoryStore());
// Open a file using which we will then create a TusUpload. If you do not have
// a File object, you can manually construct a TusUpload using an InputStream.
// See the documentation for more information.
final TusUpload upload = new TusUpload(file);
// You can also upload from an InputStream directly using a bit more work:
// InputStream stream = …;
// TusUpload upload = new TusUpload();
// upload.setInputStream(stream);
// upload.setSize(sizeOfStream);
// upload.setFingerprint("stream");
System.out.println("Starting upload...");
// We wrap our uploading code in the TusExecutor class which will automatically catch
// exceptions and issue retries with small delays between them and take fully
// advantage of tus' resumability to offer more reliability.
// This step is optional but highly recommended.
TusExecutor executor = new TusExecutor() {
@Override
protected void makeAttempt() throws ProtocolException, IOException {
// First try to resume an upload. If that's not possible we will create a new
// upload and get a TusUploader in return. This class is responsible for opening
// a connection to the remote server and doing the uploading.
TusUploader uploader = client.resumeOrCreateUpload(upload);
// Upload the file in chunks of 1KB sizes.
uploader.setChunkSize(1024);
// Upload the file as long as data is available. Once the
// file has been fully uploaded the method will return -1
do {
// Calculate the progress using the total size of the uploading file and
// the current offset.
long totalBytes = upload.getSize();
long bytesUploaded = uploader.getOffset();
double progress = (double) bytesUploaded / totalBytes * 100;
System.out.printf("Upload at %06.2f%%.\n", progress);
} while(uploader.uploadChunk() > -1);
// Allow the HTTP connection to be closed and cleaned up
uploader.finish();
System.out.println("Upload finished.");
System.out.format("Upload available at: %s", uploader.getUploadURL().toString());
}
};
executor.makeAttempts();
} catch(Exception e) {
e.printStackTrace();
}
io.tus.java.client.ProtocolException: response contains different Upload-Offset value (1024) than expected (51778)
public static void upload(Context context, Bitmap bitmap, FileUploadInfo fileUploadInfo) throws IOException, ProtocolException {
// Create a new TusClient instance
TusClient client = new TusClient();
Map<String,String> headers = new HashMap<>();
headers.put("Authorization",OkHttpUtils.accessToken==null?"":"Bearer " + OkHttpUtils.accessToken.getToken());
headers.put("uploadType","企业");
headers.put("entityName",fileUploadInfo.getEntityName());
headers.put("fieldName",fileUploadInfo.getFieldName());
headers.put("keyName",fileUploadInfo.getKeyName());
headers.put("keyValue",fileUploadInfo.getKeyValue());
headers.put("fileName",fileUploadInfo.getKeyValue());
headers.put("deviceNo","andersdevice");
headers.put("operId", String.valueOf(App.currentUser.getId()));
client.setHeaders(headers);
// Configure tus HTTP endpoint. This URL will be used for creating new uploads
// using the Creation extension
client.setUploadCreationURL(new URL(API_BASE_URL+"upload"));
// Enable resumable uploads by storing the upload URL in the preferences
// and preserve them after app restarts
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
client.enableResuming(new TusPreferencesURLStore(pref));
// Open a file using which we will then create a TusUpload. If you do not have
// a File object, you can manually construct a TusUpload using an InputStream.
// See the documentation for more information.
final TusUpload upload = new TusUpload();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] bArr = baos.toByteArray();
InputStream inputStream = new ByteArrayInputStream(bArr);
upload.setInputStream(inputStream);
upload.setFingerprint(fileUploadInfo.getKeyValue());
upload.setSize(bArr.length);
System.out.println("Starting upload...");
// We wrap our uploading code in the TusExecutor class which will automatically catch
// exceptions and issue retries with small delays between them and take fully
// advantage of tus' resumability to offer more reliability.
// This step is optional but highly recommended.
final TusExecutor executor = new TusExecutor() {
@Override
protected void makeAttempt() throws ProtocolException, IOException {
// First try to resume an upload. If that's not possible we will create a new
// upload and get a TusUploader in return. This class is responsible for opening
// a connection to the remote server and doing the uploading.
TusUploader uploader = client.resumeOrCreateUpload(upload);
// Upload the file in chunks of 1KB sizes.
uploader.setChunkSize(1024);
// Upload the file as long as data is available. Once the
// file has been fully uploaded the method will return -1
do {
// Calculate the progress using the total size of the uploading file and
// the current offset.
long totalBytes = upload.getSize();
long bytesUploaded = uploader.getOffset();
double progress = (double) bytesUploaded / totalBytes * 100;
System.out.printf("Upload at %06.2f%%.\n", progress);
} while(uploader.uploadChunk() > -1);
// Allow the HTTP connection to be closed and cleaned up
uploader.finish();
System.out.println("Upload finished.");
System.out.format("Upload available at: %s", uploader.getUploadURL().toString());
}
};
Thread t = new Thread(()-> {
try {
executor.makeAttempts();
} catch (ProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
});
t.start();
}
I'm using cloudflare stream tus, but its video id set "stream-media-id" in response header.
How do I get response header stream-media-id??
https://developers.cloudflare.com/stream/uploading-videos/upload-video-file/#resumable-uploads-with-tus-for-large-files
I'm using tus-java-client in android and here is the code snippet:
` TusUpload upload = new TusUpload(new File(filePath));
TusUploader uploader = client.resumeOrCreateUpload(upload);
long totalBytes = upload.getSize();
long uploadedBytes = uploader.getOffset();
// Upload file in 10KB chunks
uploader.setChunkSize(10 * 1024);
MsgUpdloadStatus updloadStatus = new MsgUpdloadStatus(message,
InstantMessage.UploadStatusType.UPLOADING);
while (!shouldStop && uploader.uploadChunk() > 0) {
uploadedBytes = uploader.getOffset();
updloadStatus.setProgress((int) (uploadedBytes / totalBytes));
eventBus.send(updloadStatus);
}
// finish method here can be used for pausing the ongoing upload
uploader.finish();
if (uploadedBytes >= totalBytes) {
updloadStatus.setProgress(100);
updloadStatus.uploadStatus = InstantMessage.UploadStatusType.UPLOADED;
eventBus.send(updloadStatus);
}
}
} catch (InterruptedException | ProtocolException | IOException e) {
e.printStackTrace();
}`
And the server side is using the prebuilt Go binaries, in server for the first Http header I'm getting
[tusd] event="ResponseOutgoing" status="400" method="POST" path="" error="missing or invalid Content-Type header
I searched in sent headers and surprisingly saw this one:
application/x-www-form-urlencoded
And the server is not expecting such header, I found no place in java client that is adding this header to HttpHeaders.
Please add ability to change Http stack used in the implementation. HttpURLConnection is currently hardcoded.
Thanks in advance.
Question
Hello,
I am not familiar with java too much, but when we try integrate java tus client with .net backend - requests are failing with
unexpected status code (411) while creating upload
I'm looking through the source and seems that Content-Length
header is missing (which required by were server that hosts .net core backend). Javascript tus client sends this header by default. And .NET HTTP client too.
Setup details
This Exception occurs when uploading finished in "uploader.finish()". also some times EPIPE Broken error occurs.
f.txt
I need your clients to be able to receive a json structure, after an exception or when finishing the file upload job. It's possible??
Describe the solution you'd like
Developers may wish to use an http client other than HttpURLConnection
as the http client API. How about providing an interface to easily replace it?
Question
Hi,
I'm getting this error when try to upload a file to Cloudflare servers:
java.io.IOException: Server returned HTTP response code: 400
It seems that the root cause is the following code, at TusUploader.java file
try {
connection.setRequestMethod("PATCH");
// Check whether we are running on a buggy JRE
} catch (java.net.ProtocolException pe) {
connection.setRequestMethod("POST");
connection.setRequestProperty("X-HTTP-Method-Override", "PATCH");
}
Cloudflare does not support the X-HTTP-Method-Override header.
I have found the same error at:
#29
Is there any workaround does not depends on server configuration?
Regards
Marcos
Setup details
Please provide following details, if applicable to your situation:
If one file is upload fail, how can i handle it? ReUpload? how? Where is the judging condition? PLZ, help me!
my host is using pure ip address, and it is like this http://ip:1080, it deployed on docker.
io.tus.java.client.ProtocolException: unexpected status code (301) while creating upload
at io.tus.java.client.TusClient.createUpload(TusClient.java:151)
at io.tus.java.client.TusClient.resumeOrCreateUpload(TusClient.java:232)
at cc.tavan.dao.AttachmentDaoTest$1.makeAttempt(AttachmentDaoTest.java:51)
at io.tus.java.client.TusExecutor.makeAttempts(TusExecutor.java:83)
at cc.tavan.dao.AttachmentDaoTest.findByDeletedAtIsNull(AttachmentDaoTest.java:75)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
This will allow authorization headers. I know authorization is not part of the protocol, but the protocol documentation suggest that you should perform authorization via additional headers. The current api does not allow this.
Describe the bug
The comment above the setChunkSize
line in the readme says you can pass a KB value (with 1024 as value).
But the method take the value and create Byte array with that value, without multiplicating it.
In the constructor however, it is written 2 x 1024 x 1024
which is 2 MB as Bytes
https://github.com/tus/tus-java-client/blob/master/src/main/java/io/tus/java/client/TusUploader.java
The compiled client has a ProtocolException class with shouldRetry, even though master does not.
when i s3 and tus upload file, i don't know if i used it wrong
tus unable to access url, here is my code
public class TusTest {
public static void main(String[] args) throws IOException, ProtocolException {
AmazonS3 s3 = getMinioClient();
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest("test1", "1313", HttpMethod.PUT);
URL url = s3.generatePresignedUrl(generatePresignedUrlRequest);
System.setProperty("http.strictPostRedirect", "true");
System.out.println(url.toString());
final TusClient tusClient = new TusClient();
// System.out.println(s3.getUrl("test1", "1212_1.mp4"));
// TODO
tusClient.setUploadCreationURL(/url);
// 通过在内存中存储上传URL来启用可恢复的上传
tusClient.enableResuming(new TusURLMemoryStore());
File file = new File("F:\\test\\1212_" + 1 + ".mp4");
System.out.println(file.length());
final TusUpload upload = new TusUpload(file);
// upload.setInputStream(new FileInputStream(file));
TusExecutor executor = new TusExecutor() {
@Override
protected void makeAttempt() throws ProtocolException, IOException {
/**
* 1、URL 是可以连接的,并且可以上传成功
* 2、但是生成的 GeneratePresignedUrlRequest httpMethod 类型为PUT , 而tus 会先设置一个请求POST
*
*/
TusUploader uploader = tusClient.resumeOrCreateUpload(upload);
// 设置块大小5Mb 上传
uploader.setChunkSize(5 * 1024 * 1024);
do {
long totalBytes = upload.getSize();
long bytesUploaded = uploader.getOffset();
double progress = (double) bytesUploaded / totalBytes * 100;
System.out.printf("Upload at %06.2f%%.\n", progress);
} while (uploader.uploadChunk() > -1);
// 允许关闭连接
uploader.finish();
System.out.println("Upload finished.");
System.out.format("Upload available at: %s", uploader.getUploadURL().toString());
}
};
executor.makeAttempts();
}
private static AmazonS3 getMinioClient() {
BasicAWSCredentials credentials = new BasicAWSCredentials("minioadmin", "minioadmin");
ClientConfiguration clientConfig = new ClientConfiguration();
AmazonS3 s3 = AmazonS3ClientBuilder.standard()
//.withRegion(Regions.DEFAULT_REGION)
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withClientConfiguration(clientConfig)
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration("http://xx.xxx.xxx.xxx:9000", " us-west-1"))
.withPathStyleAccessEnabled(true)
.build();
List<Bucket> buckets = s3.listBuckets();
for (Bucket b : buckets) {
System.out.println(b.getName());
}
return s3;
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.