/* Copyright (c) 2008 Google Inc.
*
* 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.gdata.client.media;
import com.google.gdata.util.common.base.Preconditions;
import com.google.gdata.data.IEntry;
import com.google.gdata.data.media.MediaFileSource;
import com.google.gdata.util.ContentType;
import com.google.gdata.util.ResumableUploadException;
import com.google.gdata.util.ServiceException;
import com.google.gdata.client.uploader.ProgressListener;
import com.google.gdata.client.uploader.ResumableHttpFileUploader;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.annotation.Nullable;
/**
* Provides Google Data API specific capabilities to
* {@link ResumableHttpFileUploader}.
*
*
*/
public class ResumableGDataFileUploader extends ResumableHttpFileUploader {
public static final String RESUMABLE_EDIT_MEDIA_REL = "resumable-edit-media";
/**
* Types of resumable update request types.
* INSERT - add new media
* UPDATE - update an existing media with metadata if available.
* UPDATE_MEDIA_ONLY - update only media (not metadata).
*/
public enum RequestType {
INSERT, UPDATE, UPDATE_MEDIA_ONLY
}
private final MediaService service;
/**
* Constructor to update a media with progress update. Use {@link Builder}
* to construct this instance.
*
* @param uploadUrl resumable media upload url.
* @param file media file to upload.
* @param service {@link MediaService} for target service.
* @param chunkSize max chunk size in bytes to include in each request.
* @param executor resource pool to execute upload task.
* @param progressListener callback to listen to progress updates.
* @param progressIntervalMillis how often to notify about update progress.
* @throws IOException any read/write error
* @throws ServiceException any service specific error
*/
private ResumableGDataFileUploader(URL uploadUrl, MediaFileSource file,
MediaService service, long chunkSize, ExecutorService executor,
ProgressListener progressListener, long progressIntervalMillis)
throws IOException, ServiceException {
super(uploadUrl, file.getMediaFile(), executor, progressListener, chunkSize,
progressIntervalMillis);
this.service = service;
}
/**
* Returns the upload response as an instance of parsed entryClass.
*
* @param entryClass the class that will be used to represent the resulting
* entry.
* @return the inserted media Entry returned by the service.
* @throws IOException error communicating with the GData Service.
* @throws ServiceException insert request failed due to service error.
*/
public <E extends IEntry> E getResponse(Class<E> entryClass)
throws IOException, ServiceException {
InputStream response = super.getResponse().getInputStream();
if (getUploadState() == UploadState.CLIENT_ERROR) {
throw new ResumableUploadException("client error");
} else if (response == null) {
System.out.println("No response found");
return null;
}
return service.parseResumableUploadResponse(response, ContentType.ATOM,
entryClass);
}
/**
* Builder to construct a {@link ResumableGDataFileUploader}.
*/
public static class Builder {
/* resumable create or edit media url */
private URL mediaUrl;
/* media entry to update */
private IEntry mediaEntry;
/* service instance for authenticated requests */
private MediaService mediaService;
/* Media file to upload */
private MediaFileSource mediaFile;
/* Title for the media to upload */
private String mediaTitle;
/* Max size in bytes for a single media resumable upload request */
private long chunkSize = ResumableHttpFileUploader.DEFAULT_MAX_CHUNK_SIZE;
/* Executor service to execute asynchronous upload tasks */
private ExecutorService executor = Executors.newSingleThreadExecutor();
/* Callback to receive upload progress notifications */
private ProgressListener listener;
/* Interval between progress updates in millis */
private long progressInterval = 100;
/* Resumable media request type */
RequestType requestType = RequestType.INSERT;
/**
* Builds a {@link ResumableGDataFileUploader} to upload new media.
*
* @param service media service
* @param mediaUrl resumable-create-media url.
* @param mediaFile file to upload.
* @param mediaEntry metadata for the uploaded file.
*/
public Builder(MediaService service, URL mediaUrl,
MediaFileSource mediaFile, IEntry mediaEntry) {
this.mediaService = service;
this.mediaUrl = mediaUrl;
this.mediaFile = mediaFile;
this.mediaEntry = mediaEntry;
}
/**
* Builds a {@link ResumableGDataFileUploader} to update an existing media.
*
* @param service media service.
* @param mediaFile updated file to upload.
* @param entryToUpdate updated metadata associated with the existing entry.
*/
public Builder(
MediaService service, MediaFileSource mediaFile, IEntry entryToUpdate) {
this.mediaEntry = entryToUpdate;
this.mediaFile = mediaFile;
this.mediaService = service;
}
/**
* Sets title for uploaded media. This value passed as Slug header in
* media requests.
*
* @param mediaTitle title for new media.
* @return builder.
*/
public Builder title(String mediaTitle) {
this.mediaTitle = mediaTitle;
return this;
}
/**
* Max content size for media upload request.
*
* @param chunkSize max upload size in bytes.
* @return builder.
*/
public Builder chunkSize(long chunkSize) {
this.chunkSize = chunkSize;
return this;
}
/**
* Sets {@link ExecutorService} to execute asynchronous tasks.
*
* @param executor executor service to use.
* @return builder.
*/
public Builder executor(ExecutorService executor) {
this.executor = executor;
return this;
}
/**
* Sets parameters to track upload progress.
*
* @param listener {@link ProgressListener} callback for tracking progress.
* @param progressInterval time interval in millis for progress
* notifications.
* @return builder.
*/
public Builder trackProgress(
ProgressListener listener, long progressInterval) {
this.listener = listener;
this.progressInterval = progressInterval;
return this;
}
/**
* Sets the media upload request type.
*
* @param requestType one of INSERT, UPDATE, UPDATE_MEDIA_ONLY
* @return builder.
*/
public Builder requestType(RequestType requestType) {
this.requestType = requestType;
return this;
}
/**
* Creates a {@link ResumableGDataFileUploader} instance.
*
* @return uploader.
* @throws IOException any read/write error.
* @throws ServiceException any server error.
*/
public ResumableGDataFileUploader build()
throws IOException, ServiceException {
Preconditions.checkState(mediaUrl != null || mediaEntry != null);
Preconditions.checkNotNull(mediaService);
Preconditions.checkNotNull(mediaFile);
// make GData request to create resumable upload session
URL uploadUrl = null;
switch(requestType) {
case INSERT:
if (mediaEntry != null) {
uploadUrl = mediaService.createResumableUploadSession(
mediaUrl, mediaEntry, mediaFile);
} else {
uploadUrl = mediaService.createResumableUploadSession(
mediaUrl, mediaTitle, mediaFile);
}
break;
case UPDATE:
uploadUrl = mediaService.createResumableUpdateSession(
new URL(mediaEntry.getResumableEditMediaLink().getHref()),
mediaEntry, mediaFile, false);
break;
case UPDATE_MEDIA_ONLY:
if (mediaEntry != null) {
mediaUrl = new URL(
mediaEntry.getResumableEditMediaLink().getHref());
}
uploadUrl = mediaService.createResumableUpdateSession(
mediaUrl, mediaEntry, mediaFile, true);
break;
}
ResumableGDataFileUploader uploader = new ResumableGDataFileUploader(
uploadUrl, mediaFile, mediaService, chunkSize, executor, listener,
progressInterval);
// Set additional headers
uploader.addHeader("Content-Type", mediaFile.getContentType());
return uploader;
}
}
}