package cloudsync.connector;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.security.GeneralSecurityException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import cloudsync.exceptions.FileIOException;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import cloudsync.exceptions.CloudsyncException;
import cloudsync.helper.CmdOptions;
import cloudsync.helper.Handler;
import cloudsync.helper.Helper;
import cloudsync.model.options.NetworkErrorType;
import cloudsync.model.Item;
import cloudsync.model.ItemType;
import cloudsync.model.RemoteItem;
import cloudsync.model.LocalStreamData;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.googleapis.media.MediaHttpUploader;
import com.google.api.client.googleapis.media.MediaHttpUploader.UploadState;
import com.google.api.client.googleapis.media.MediaHttpUploaderProgressListener;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.InputStreamContent;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.JsonGenerator;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.Drive.Files.Insert;
import com.google.api.services.drive.Drive.Files.Update;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;
import com.google.api.services.drive.model.ParentReference;
import com.google.api.services.drive.model.Property;
public class RemoteGoogleDriveConnector implements RemoteConnector
{
private final static Logger LOGGER = Logger.getLogger(RemoteGoogleDriveConnector.class.getName());
private final static String SEPARATOR = "/";
private final static String REDIRECT_URL = "urn:ietf:wg:oauth:2.0:oob";
private final static String FOLDER = "application/vnd.google-apps.folder";
private final static String FILE = "application/octet-stream";
private final static int MIN_SEARCH_BREAK = 5000;
private final static int MIN_SEARCH_RETRIES = 12;
private final static int CHUNK_COUNT = 4; // *
// 256kb
private final static int MAX_RESULTS = 1000;
private final static long MIN_TOKEN_REFRESH_TIMEOUT = 600;
private GoogleTokenResponse clientToken;
private GoogleCredential credential;
private Drive service;
private Path clientTokenPath;
private Map<String, File> cacheFiles;
private Map<String, File> cacheParents;
private String basePath;
private String backupName;
private String historyName;
private Integer historyCount;
private long lastValidate = 0;
private boolean showProgress;
private NetworkErrorType networkErrorBehavior;
private int retries;
private int waitretry;
private Charset charset;
public RemoteGoogleDriveConnector()
{
}
@Override
public void init(String backupName, CmdOptions options) throws CloudsyncException
{
RemoteGoogleDriveOptions googleDriveOptions = new RemoteGoogleDriveOptions(options, backupName);
Integer history = options.getHistory();
showProgress = options.showProgress();
retries = options.getRetries();
waitretry = options.getWaitRetry() * 1000;
networkErrorBehavior = options.getNetworkErrorBehavior();
charset = options.getCharset();
cacheFiles = new HashMap<>();
cacheParents = new HashMap<>();
this.basePath = Helper.trim(googleDriveOptions.getClientBasePath(), SEPARATOR);
this.backupName = backupName;
this.historyCount = history;
this.historyName = history > 0 ? backupName + " " + new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss").format(new Date()) : null;
final HttpTransport httpTransport = new NetHttpTransport();
final JsonFactory jsonFactory = new JacksonFactory();
if (StringUtils.isNotEmpty(googleDriveOptions.getServiceAccountUser())) {
// Service Account auth - https://developers.google.com/api-client-library/java/google-api-java-client/oauth2#service_accounts
try {
credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(googleDriveOptions.getServiceAccountEmail())
.setServiceAccountScopes(Collections.singletonList(DriveScopes.DRIVE))
.setServiceAccountUser(googleDriveOptions.getServiceAccountUser())
.setServiceAccountPrivateKeyFromP12File(new java.io.File(googleDriveOptions.getServiceAccountPrivateKeyP12Path()))
.build();
} catch (GeneralSecurityException | IOException e) {
throw new CloudsyncException("Can't init remote google drive connector", e);
}
} else {
// Installed Applications auth - https://developers.google.com/api-client-library/java/google-api-java-client/oauth2#installed_applications
final GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(httpTransport, jsonFactory, googleDriveOptions.getClientID(),
googleDriveOptions.getClientSecret(), Collections.singletonList(DriveScopes.DRIVE)).setAccessType("offline").setApprovalPrompt("auto").build();
this.clientTokenPath = Paths.get(googleDriveOptions.getClientTokenPath());
try
{
final String clientTokenAsJson = Files.exists(this.clientTokenPath) ? FileUtils.readFileToString(this.clientTokenPath.toFile(), charset) : null;
credential = new GoogleCredential.Builder().setTransport(new NetHttpTransport()).setJsonFactory(new GsonFactory())
.setClientSecrets(googleDriveOptions.getClientID(), googleDriveOptions.getClientSecret()).build();
if (StringUtils.isEmpty(clientTokenAsJson))
{
final String url = flow.newAuthorizationUrl().setRedirectUri(REDIRECT_URL).build();
System.out.println("Please open the following URL in your browser, copy the authorization code and enter below.");
System.out.println("\n" + url + "\n");
final String code = new BufferedReader(new InputStreamReader(System.in)).readLine().trim();
clientToken = flow.newTokenRequest(code).setRedirectUri(REDIRECT_URL).execute();
storeClientToken(jsonFactory);
LOGGER.log(Level.INFO, "client token stored in '" + this.clientTokenPath + "'");
}
else
{
clientToken = jsonFactory.createJsonParser(clientTokenAsJson).parse(GoogleTokenResponse.class);
}
credential.setFromTokenResponse(clientToken);
}
catch (final IOException e)
{
throw new CloudsyncException("Can't init remote google drive connector", e);
}
}
}
private void storeClientToken(final JsonFactory jsonFactory) throws IOException
{
final StringWriter jsonTrWriter = new StringWriter();
final JsonGenerator generator = jsonFactory.createJsonGenerator(jsonTrWriter);
generator.serialize(clientToken);
generator.flush();
generator.close();
FileUtils.writeStringToFile(clientTokenPath.toFile(), jsonTrWriter.toString(), charset);
}
@Override
public void upload(final Handler handler, final Item item) throws CloudsyncException, FileIOException
{
initService(handler);
String title = handler.getLocalProcessedTitle(item);
File parentDriveItem = null;
File driveItem;
int retryCount = 0;
do
{
try
{
refreshCredential();
parentDriveItem = _getDriveItem(item.getParent());
final ParentReference parentReference = new ParentReference();
parentReference.setId(parentDriveItem.getId());
driveItem = new File();
driveItem.setTitle(title);
driveItem.setParents(Collections.singletonList(parentReference));
final LocalStreamData data = _prepareDriveItem(driveItem, item, handler, true);
if (data == null)
{
driveItem = service.files().insert(driveItem).execute();
}
else
{
final InputStreamContent params = new InputStreamContent(FILE, data.getStream());
params.setLength(data.getLength());
Insert inserter = service.files().insert(driveItem, params);
MediaHttpUploader uploader = inserter.getMediaHttpUploader();
prepareUploader(uploader, data.getLength());
driveItem = inserter.execute();
}
if (driveItem == null)
{
throw new CloudsyncException("Couldn't create item '" + item.getPath() + "'");
}
_addToCache(driveItem, null);
item.setRemoteIdentifier(driveItem.getId());
return;
}
catch (final IOException e)
{
if (parentDriveItem != null)
{
for (int i = 0; i < MIN_SEARCH_RETRIES; i++)
{
driveItem = _searchDriveItem(item.getParent(), title);
if (driveItem != null)
{
LOGGER.log(Level.WARNING, "Google Drive IOException: " + getExceptionMessage(e) + " - found partially uploaded item - try to update");
item.setRemoteIdentifier(driveItem.getId());
update(handler, item, true);
return;
}
LOGGER.log(Level.WARNING, "Google Drive IOException: " + getExceptionMessage(e) + " - item not uploaded - retry " + (i + 1) + "/" + MIN_SEARCH_RETRIES + " - wait "
+ MIN_SEARCH_BREAK + " ms");
sleep(MIN_SEARCH_BREAK);
}
}
retryCount = validateException("remote upload", item, e, retryCount);
if( retryCount == -1 ) // ignore a failing item (workaround for now)
return;
}
}
while (true);
}
@Override
public void update(final Handler handler, final Item item, final boolean with_filedata) throws CloudsyncException, FileIOException
{
initService(handler);
int retryCount = 0;
do
{
try
{
refreshCredential();
if (item.isType(ItemType.FILE))
{
final File _parentDriveItem = _getHistoryFolder(item);
if (_parentDriveItem != null)
{
final File copyOfdriveItem = new File();
final ParentReference _parentReference = new ParentReference();
_parentReference.setId(_parentDriveItem.getId());
copyOfdriveItem.setParents(Collections.singletonList(_parentReference));
// copyOfdriveItem.setTitle(driveItem.getTitle());
// copyOfdriveItem.setMimeType(driveItem.getMimeType());
// copyOfdriveItem.setProperties(driveItem.getProperties());
final File _copyOfDriveItem = service.files().copy(item.getRemoteIdentifier(), copyOfdriveItem).execute();
if (_copyOfDriveItem == null)
{
throw new CloudsyncException("Couldn't make a history snapshot of item '" + item.getPath() + "'");
}
}
}
File driveItem = new File();
final LocalStreamData data = _prepareDriveItem(driveItem, item, handler, with_filedata);
if (data == null)
{
driveItem = service.files().update(item.getRemoteIdentifier(), driveItem).execute();
}
else
{
final InputStreamContent params = new InputStreamContent(FILE, data.getStream());
params.setLength(data.getLength());
Update updater = service.files().update(item.getRemoteIdentifier(), driveItem, params);
MediaHttpUploader uploader = updater.getMediaHttpUploader();
prepareUploader(uploader, data.getLength());
driveItem = updater.execute();
}
if (driveItem == null)
{
throw new CloudsyncException("Couldn't update item '" + item.getPath() + "'");
}
else if (driveItem.getLabels().getTrashed())
{
throw new CloudsyncException("Remote item '" + item.getPath() + "' [" + driveItem.getId() + "] is trashed\ntry to run with --nocache");
}
_addToCache(driveItem, null);
return;
}
catch (final IOException e)
{
retryCount = validateException("remote update", item, e, retryCount);
if(retryCount < 0) // TODO workaround - fix this later
retryCount = 0;
}
}
while (true);
}
@Override
public void remove(final Handler handler, final Item item) throws CloudsyncException
{
initService(handler);
int retryCount = 0;
do
{
try
{
refreshCredential();
final File _parentDriveItem = _getHistoryFolder(item);
if (_parentDriveItem != null)
{
final ParentReference parentReference = new ParentReference();
parentReference.setId(_parentDriveItem.getId());
File driveItem = new File();
driveItem.setParents(Collections.singletonList(parentReference));
driveItem = service.files().patch(item.getRemoteIdentifier(), driveItem).execute();
if (driveItem == null)
{
throw new CloudsyncException("Couldn't make a history snapshot of item '" + item.getPath() + "'");
}
}
else
{
service.files().delete(item.getRemoteIdentifier()).execute();
}
_removeFromCache(item.getRemoteIdentifier());
return;
}
catch (final IOException e)
{
retryCount = validateException("remote remove", item, e, retryCount);
if(retryCount < 0) // TODO workaround - fix this later
retryCount = 0;
}
}
while (true);
}
@Override
public InputStream get(final Handler handler, final Item item) throws CloudsyncException
{
initService(handler);
int retryCount = 0;
do
{
try
{
refreshCredential();
final File driveItem = _getDriveItem(item);
final String downloadUrl = driveItem.getDownloadUrl();
final HttpResponse resp = service.getRequestFactory().buildGetRequest(new GenericUrl(downloadUrl)).execute();
return resp.getContent();
}
catch (final IOException e)
{
retryCount = validateException("remote get", item, e, retryCount);
if(retryCount < 0) // TODO workaround - fix this later
retryCount = 0;
}
}
while (true);
}
@Override
public List<RemoteItem> readFolder(final Handler handler, final Item parentItem) throws CloudsyncException
{
initService(handler);
int retryCount = 0;
do
{
try
{
refreshCredential();
final List<RemoteItem> child_items = new ArrayList<>();
final List<File> childDriveItems = _readFolder(parentItem.getRemoteIdentifier());
for (final File child : childDriveItems)
{
child_items.add(_prepareBackupItem(parentItem, child, handler));
}
return child_items;
}
catch (final IOException e)
{
retryCount = validateException("remote fetch", parentItem, e, retryCount);
if(retryCount < 0) // TODO workaround - fix this later
retryCount = 0;
}
}
while (true);
}
@Override
public void cleanHistory(final Handler handler) throws CloudsyncException
{
initService(handler);
final File backupDriveFolder = _getBackupFolder();
final File parentDriveItem = _getDriveFolder(basePath);
try
{
refreshCredential();
final List<File> child_items = new ArrayList<>();
for (File file : _readFolder(parentDriveItem.getId()))
{
if (backupDriveFolder.getId().equals(file.getId()) || !file.getTitle().startsWith(backupDriveFolder.getTitle()))
{
continue;
}
child_items.add(file);
}
if (child_items.size() > historyCount)
{
Collections.sort(child_items, new Comparator<File>()
{
@Override
public int compare(final File o1, final File o2)
{
final long v1 = o1.getCreatedDate().getValue();
final long v2 = o2.getCreatedDate().getValue();
if (v1 < v2) return 1;
if (v1 > v2) return -1;
return 0;
}
});
for (File file : child_items.subList(historyCount, child_items.size()))
{
LOGGER.log(Level.FINE, "cleanup history folder '" + file.getTitle() + "'");
service.files().delete(file.getId()).execute();
}
}
}
catch (final IOException e)
{
throw new CloudsyncException("Unexpected error during history cleanup", e);
}
}
private List<File> _readFolder(final String id) throws IOException
{
final List<File> child_items = new ArrayList<>();
final String q = "'" + id + "' in parents and trashed = false";
final Drive.Files.List request = service.files().list();
request.setQ(q);
request.setMaxResults(MAX_RESULTS);
do
{
FileList files = request.execute();
final List<File> result = files.getItems();
for (final File file : result)
{
child_items.add(file);
}
request.setPageToken(files.getNextPageToken());
}
while (request.getPageToken() != null && request.getPageToken().length() > 0);
return child_items;
}
private LocalStreamData _prepareDriveItem(final File driveItem, final Item item, final Handler handler, final boolean with_filedata) throws FileIOException
{
LocalStreamData data = null;
if (with_filedata)
{
// "getLocalEncryptedBinary" should be called before "getMetadata"
// to generate the needed checksum
data = handler.getLocalProcessedBinary(item);
}
final String metadata = handler.getLocalProcessedMetadata(item);
final List<Property> properties = new ArrayList<>();
final int length = metadata.length();
int partCounter = 0;
// max 118 bytes (key+value)
for (int i = 0; i < length; i += 100, partCounter++)
{
final String part = metadata.substring(i, Math.min(length, i + 100));
final Property property = new Property();
property.setKey("metadata" + partCounter);
property.setValue(part);
property.setVisibility("PRIVATE");
properties.add(property);
}
final Property property = new Property();
property.setKey("metadataParts");
property.setValue(Integer.toString(partCounter));
property.setVisibility("PRIVATE");
properties.add(property);
driveItem.setProperties(properties);
driveItem.setMimeType(item.isType(ItemType.FOLDER) ? FOLDER : FILE);
return data;
}
private RemoteItem _prepareBackupItem(final Item parentItem, final File driveItem, final Handler handler) throws CloudsyncException
{
final List<Property> properties = driveItem.getProperties();
final Map<Integer, String> metadataMap = new HashMap<>();
int metadataPartCount = -1;
if (properties != null)
{
for (final Property property : properties)
{
final String key = property.getKey();
if (!key.startsWith("metadata"))
{
continue;
}
if (key.equals("metadataParts"))
{
metadataPartCount = Integer.parseInt(property.getValue());
}
else
{
metadataMap.put(Integer.parseInt(key.substring(8)), property.getValue());
}
}
}
if (metadataPartCount == -1) metadataPartCount = metadataMap.size();
final List<String> parts = new ArrayList<>();
for (int i = 0; i < metadataPartCount; i++)
{
parts.add(i, metadataMap.get(i));
}
try
{
String title = handler.getProcessedText(driveItem.getTitle());
String metadata = null;
try
{
if (parts.size() > 0)
{
metadata = handler.getProcessedText(StringUtils.join(parts.toArray()));
}
else
{
ItemType type = driveItem.getMimeType().equals(FOLDER) ? ItemType.FOLDER : ItemType.FILE;
LOGGER.log(Level.WARNING, "Found no metadata of " + type.getName() + " '" + parentItem.getPath() + "/" + title + "'");
}
}
catch (CloudsyncException e)
{
ItemType type = driveItem.getMimeType().equals(FOLDER) ? ItemType.FOLDER : ItemType.FILE;
LOGGER.log(Level.WARNING, "Can't decrypt metadata of " + type.getName() + " '" + parentItem.getPath() + "/" + title + "'");
}
return handler.initRemoteItem(driveItem.getId(), driveItem.getMimeType().equals(FOLDER), title, metadata, driveItem.getFileSize(),
FileTime.fromMillis(driveItem.getCreatedDate().getValue()));
}
catch (CloudsyncException e)
{
ItemType type = driveItem.getMimeType().equals(FOLDER) ? ItemType.FOLDER : ItemType.FILE;
throw new CloudsyncException("Can't decrypt name of " + type.getName() + " '" + driveItem.getId() + "' in '" + parentItem.getPath() + "/'");
}
}
private File _searchDriveItem(final Item parentItem, String title) throws CloudsyncException
{
int retryCount = 0;
do
{
try
{
final String q = "title='" + title + "' and '" + parentItem.getRemoteIdentifier() + "' in parents and trashed = false";
final Drive.Files.List request = service.files().list();
request.setQ(q);
final List<File> result = request.execute().getItems();
return result.size() == 0 ? null : result.get(0);
}
catch (final IOException e)
{
retryCount = validateException("remote search", parentItem, e, retryCount);
if(retryCount < 0) // TODO workaround - fix this later
retryCount = 0;
}
}
while (true);
}
private File _getDriveItem(final Item item) throws CloudsyncException, IOException
{
final String id = item.getRemoteIdentifier();
if (cacheFiles.containsKey(id))
{
return cacheFiles.get(id);
}
File driveItem;
try
{
driveItem = service.files().get(id).execute();
}
catch (HttpResponseException e)
{
if (e.getStatusCode() == 404)
{
throw new CloudsyncException("Couldn't find remote item '" + item.getPath() + "' [" + id + "]\ntry to run with --nocache");
}
throw e;
}
if (driveItem.getLabels().getTrashed())
{
throw new CloudsyncException("Remote item '" + item.getPath() + "' [" + id + "] is trashed\ntry to run with --nocache");
}
_addToCache(driveItem, null);
return driveItem;
}
private File _getHistoryFolder(final Item item) throws CloudsyncException, IOException
{
if (historyName == null)
{
return null;
}
final File driveRoot = _getBackupFolder();
final List<String> parentDriveTitles = new ArrayList<>();
Item parentItem = item;
do
{
parentItem = parentItem.getParent();
if (parentItem.getRemoteIdentifier().equals(driveRoot.getId()))
{
break;
}
final File parentDriveItem = _getDriveItem(parentItem);
parentDriveTitles.add(0, parentDriveItem.getTitle());
}
while (true);
return _getDriveFolder(basePath + SEPARATOR + historyName + SEPARATOR + StringUtils.join(parentDriveTitles, SEPARATOR));
}
private File _getBackupFolder() throws CloudsyncException
{
return _getDriveFolder(basePath + SEPARATOR + backupName);
}
private File _getDriveFolder(final String path) throws CloudsyncException
{
int retryCount = 0;
do
{
try
{
File parentItem = service.files().get("root").execute();
final String[] folderNames = StringUtils.split(path, SEPARATOR);
for (final String name : folderNames)
{
if (cacheParents.containsKey(parentItem.getId() + ':' + name))
{
parentItem = cacheParents.get(parentItem.getId() + ':' + name);
}
else
{
final String q = "title='" + name + "' and '" + parentItem.getId() + "' in parents and trashed = false";
final Drive.Files.List request = service.files().list();
request.setQ(q);
request.setMaxResults(MAX_RESULTS);
do
{
FileList files = request.execute();
final List<File> result = files.getItems();
// array('q' => q))
File _parentItem;
if (result.size() == 0)
{
final File folder = new File();
folder.setTitle(name);
folder.setMimeType(FOLDER);
final ParentReference parentReference = new ParentReference();
parentReference.setId(parentItem.getId());
folder.setParents(Collections.singletonList(parentReference));
_parentItem = service.files().insert(folder).execute();
if (_parentItem == null)
{
throw new CloudsyncException("Couldn't create folder '" + name + "'");
}
}
else if (result.size() == 1)
{
_parentItem = result.get(0);
}
else
{
throw new CloudsyncException("base path '" + path + "' not unique");
}
if (!_parentItem.getMimeType().equals(FOLDER))
{
throw new CloudsyncException("No folder found at '" + path + "'");
}
_addToCache(_parentItem, parentItem);
parentItem = _parentItem;
request.setPageToken(files.getNextPageToken());
}
while (request.getPageToken() != null && request.getPageToken().length() > 0);
}
}
return parentItem;
}
catch (final IOException e)
{
retryCount = validateException("remote get of '" + path + "'", null, e, retryCount);
if(retryCount < 0) // TODO workaround - fix this later
retryCount = 0;
}
}
while (true);
}
private void _removeFromCache(final String id)
{
cacheFiles.remove(id);
}
private void _addToCache(final File driveItem, final File parentDriveItem)
{
if (driveItem.getMimeType().equals(FOLDER))
{
cacheFiles.put(driveItem.getId(), driveItem);
}
if (parentDriveItem != null)
{
cacheParents.put(parentDriveItem.getId() + ':' + driveItem.getTitle(), driveItem);
}
}
private void sleep(long duration)
{
try
{
Thread.sleep(duration);
}
catch (InterruptedException ex)
{
}
}
private int validateException(String name, Item item, IOException e, int count) throws CloudsyncException
{
if( e instanceof GoogleJsonResponseException)
{
StringBuilder info = new StringBuilder("Unexpected error during ");
info.append(name);
if (item != null)
{
info.append( " of " );
info.append( item.getInfo() );
}
info.append(". Remote item not found.\ntry to run with --nocache");
switch( ((GoogleJsonResponseException)e).getStatusCode() )
{
case 404:
throw new CloudsyncException( info.toString(), e);
}
}
if (count < retries)
{
long currentValidate = System.currentTimeMillis();
long current_retry_break = (currentValidate - lastValidate);
if (lastValidate > 0 && current_retry_break < waitretry)
{
sleep(waitretry - current_retry_break);
}
lastValidate = currentValidate;
count++;
LOGGER.log(Level.WARNING, "Google Drive IOException: " + getExceptionMessage(e) + " - " + name + " - retry " + count + "/" + retries);
return count;
}
if (e instanceof UnknownHostException)
{
LOGGER.log(Level.WARNING, "Connecting to RemoteHost " + getExceptionMessage(e) + " failed.");
if( NetworkErrorType.ASK.equals(networkErrorBehavior) )
{
String answer = null;
while (answer == null || (!"Y".equals(answer) && !"n".equals(answer)))
{
System.out.print("Retry again (Y/n) ");
try
{
answer = new BufferedReader(new InputStreamReader(System.in)).readLine().trim();
}
catch (IOException _e)
{
break;
}
}
if ("Y".equals(answer))
{
lastValidate = System.currentTimeMillis();
return 0;
}
}
else if( NetworkErrorType.CONTINUE.equals(networkErrorBehavior) )
{
lastValidate = System.currentTimeMillis();
return -1;
}
}
if (item != null)
{
throw new CloudsyncException("Unexpected error during " + name + " of " + item.getTypeName() + " '" + item.getPath() + "'", e);
}
else
{
throw new CloudsyncException("Unexpected error during " + name, e);
}
}
private String getExceptionMessage(IOException e)
{
String msg = e.getMessage();
if (msg.contains("\n")) msg = msg.split("\n")[0];
return "'" + msg + "'";
}
public void initService(Handler handler) throws CloudsyncException
{
if (service != null) return;
final HttpTransport httpTransport = new NetHttpTransport();
final JsonFactory jsonFactory = new JacksonFactory();
service = new Drive.Builder(httpTransport, jsonFactory, null)
.setApplicationName("Backup")
.setHttpRequestInitializer(credential)
.build();
if (StringUtils.isEmpty(credential.getServiceAccountId())) {
credential.setExpiresInSeconds(MIN_TOKEN_REFRESH_TIMEOUT);
}
try
{
refreshCredential();
}
catch (IOException e)
{
throw new CloudsyncException("couldn't refresh google drive token");
}
handler.getRootItem().setRemoteIdentifier(_getBackupFolder().getId());
}
private void refreshCredential() throws IOException
{
if (StringUtils.isNotEmpty(credential.getServiceAccountId())) return;
if (credential.getExpiresInSeconds() > MIN_TOKEN_REFRESH_TIMEOUT) return;
if (credential.refreshToken())
{
clientToken.setAccessToken(credential.getAccessToken());
clientToken.setExpiresInSeconds(credential.getExpiresInSeconds());
clientToken.setRefreshToken(credential.getRefreshToken());
final JsonFactory jsonFactory = new JacksonFactory();
storeClientToken(jsonFactory);
LOGGER.log(Level.INFO, "refreshed client token stored in '" + clientTokenPath + "'");
}
}
private void prepareUploader(MediaHttpUploader uploader, long length)
{
int chunkSize = MediaHttpUploader.MINIMUM_CHUNK_SIZE * CHUNK_COUNT;
int chunkCount = (int) Math.ceil(length / (double) chunkSize);
if (showProgress && chunkCount > 1)
{
uploader.setDirectUploadEnabled(false);
uploader.setChunkSize(chunkSize);
uploader.setProgressListener(new RemoteGoogleDriveProgress(this, length));
}
else
{
uploader.setDirectUploadEnabled(true);
}
}
private class RemoteGoogleDriveProgress implements MediaHttpUploaderProgressListener
{
private final long length;
private final DecimalFormat df;
private long lastBytes;
private long lastTime;
private final RemoteGoogleDriveConnector connector;
public RemoteGoogleDriveProgress(RemoteGoogleDriveConnector connector, long length)
{
this.length = length;
this.connector = connector;
df = new DecimalFormat("00");
lastBytes = 0;
lastTime = System.currentTimeMillis();
}
@Override
public void progressChanged(MediaHttpUploader mediaHttpUploader) throws IOException
{
if (mediaHttpUploader == null) return;
switch ( mediaHttpUploader.getUploadState() )
{
case INITIATION_COMPLETE:
break;
case INITIATION_STARTED:
case MEDIA_IN_PROGRESS:
this.connector.refreshCredential();
double percent = mediaHttpUploader.getProgress() * 100;
long currentTime = System.currentTimeMillis();
String msg = "\r " + df.format(Math.ceil(percent)) + "% (" + convertToKB(mediaHttpUploader.getNumBytesUploaded()) + " of "
+ convertToKB(length) + " kb)";
if (mediaHttpUploader.getUploadState().equals(UploadState.MEDIA_IN_PROGRESS))
{
long speed = convertToKB((mediaHttpUploader.getNumBytesUploaded() - lastBytes) / ((currentTime - lastTime) / 1000.0));
msg += " - " + speed + " kb/s";
}
LOGGER.log(Level.FINEST, msg, true);
lastTime = currentTime;
lastBytes = mediaHttpUploader.getNumBytesUploaded();
break;
case MEDIA_COMPLETE:
// System.out.println("Upload is complete!");
default:
break;
}
}
private long convertToKB(double size)
{
return (long) Math.ceil(size / 1024);
}
}
}