package org.rr.jeborker.gui.action;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFrame;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.rr.commons.collection.Pair;
import org.rr.commons.collection.TransformValueList;
import org.rr.commons.log.LoggerFactory;
import org.rr.commons.mufs.IResourceHandler;
import org.rr.commons.mufs.ResourceHandlerFactory;
import org.rr.commons.swing.DesktopUtils;
import org.rr.commons.swing.dialogs.JListSelectionDialog;
import org.rr.commons.utils.ListUtils;
import org.rr.commons.utils.StringUtil;
import org.rr.commons.utils.ThreadUtils;
import org.rr.jeborker.app.preferences.APreferenceStore;
import org.rr.jeborker.app.preferences.PreferenceStoreFactory;
import org.rr.jeborker.gui.MainController;
import org.rr.jeborker.gui.resources.ImageResourceBundle;
import net.iharder.jpushbullet2.Device;
import net.iharder.jpushbullet2.PushbulletClient;
import net.iharder.jpushbullet2.PushbulletException;
public class CopyToPushbulletApiFolderAction extends AbstractAction implements IDoOnlyOnceAction<List<String>> {
private static final long serialVersionUID = -4631469476414229079L;
/** max number of threads which performs parallel pushbullet uploads */
private static final int MAX_UPLOAD_THREADS = 5;
/**
* The name where the api key is stored in the database.
*/
private static final String PUSHBULLET_API_KEY = "pushBulletApiKey";
private static final String PUSHBULLET_DEVICE_SELECTION_KEY = "pushBulletDeviceSelection";
String source;
private List<String> targetDeviceIdentifiers;
CopyToPushbulletApiFolderAction(String text) {
this.source = text;
putValue(Action.NAME, Bundle.getString("CopyToPushbulletAction.name"));
putValue(Action.SMALL_ICON, ImageResourceBundle.getResourceAsImageIcon("pushbullet_16.png"));
putValue(Action.LARGE_ICON_KEY, ImageResourceBundle.getResourceAsImageIcon("pushbullet_22.png"));
}
@Override
public void actionPerformed(ActionEvent e) {
IResourceHandler resource = ResourceHandlerFactory.getResourceHandler(source);
try {
String message = Bundle.getFormattedString("CopyToPushbulletAction.uploading", resource.getName());
MainController.getController().getProgressMonitor().monitorProgressStart(message);
initUpload(resource);
} catch (Throwable ex) {
LoggerFactory.getLogger(this).log(Level.WARNING, "Upload failed", ex);
} finally {
MainController.getController().getProgressMonitor().monitorProgressStop();
}
}
/**
* Uploads the given {@link IResourceHandler} to the desired pushbullet device.
* @param resource The resource to be uploaded.
* @throws IOException
* @throws IllegalStateException
*/
private void initUpload(final IResourceHandler resource) throws IllegalStateException, IOException {
final String pushBulletApiKey = getApiKey();
if(StringUtil.isNotEmpty(pushBulletApiKey)) {
ThreadUtils.loopAndWait(targetDeviceIdentifiers, new ThreadUtils.RunnableImpl<String, Void>() {
@Override
public Void run(String targetDeviceIdentifier) {
try {
PushbulletClient client = new PushbulletClient(pushBulletApiKey);
uploadResource(client, targetDeviceIdentifier, resource);
} catch (Exception e) {
LoggerFactory.getLogger().log(Level.SEVERE, "Failed to uploaded file " + resource + " using pushbullet.", e);
}
return null;
}
private void uploadResource(PushbulletClient client, String targetDeviceIdentifier, IResourceHandler resource) throws Exception {
String name = resource.getName();
if(containsAnyNonAsciiChars(name)) {
String newName = createNonAsciiCharFreeString(name, "_");
IResourceHandler newResourceHandler = ResourceHandlerFactory.getResourceHandler(new File(FileUtils.getTempDirectory(), newName));
try {
resource.copyTo(newResourceHandler, true);
client.sendFile(targetDeviceIdentifier, newResourceHandler.toFile(), null);
} finally {
newResourceHandler.delete();
}
} else {
client.sendFile(targetDeviceIdentifier, resource.toFile(), null);
}
}
private String createNonAsciiCharFreeString(String name, String replacement) {
return name.replaceAll("[^\\x00-\\x7F]", replacement);
}
private boolean containsAnyNonAsciiChars(String name) {
return name.matches("^.*[^\\x00-\\x7F].*$");
}
}, MAX_UPLOAD_THREADS);
} else {
throw new IllegalStateException("No api key specified.");
}
}
private String getApiKey() {
APreferenceStore preferenceStore = PreferenceStoreFactory.getPreferenceStore(PreferenceStoreFactory.DB_STORE);
String pushBulletApiKey = preferenceStore.getGenericEntryAsString(PUSHBULLET_API_KEY);
if(StringUtil.isEmpty(pushBulletApiKey)) {
pushBulletApiKey = askForApiKey();
if(StringUtil.isNotEmpty(pushBulletApiKey)) {
preferenceStore.addGenericEntryAsString(PUSHBULLET_API_KEY, pushBulletApiKey);
}
}
return pushBulletApiKey;
}
/**
* Opens a message dialog and ask the user to which device the ebook should be send to.
* @param client The client which is needed to get the available devices.
* @return A list with Identifier for the selected devices.
* @throws IllegalStateException
* @throws IOException
* @throws PushbulletException
*/
private List<String> askForTargetDeviceIdentifier(PushbulletClient client) throws IllegalStateException, IOException, PushbulletException {
final List<Pair<String, String>> devices = filterUndesiredDevices(transformToIdAndNamePair(client));
JListSelectionDialog<String> dialog = new JListSelectionDialog<>(MainController.getController().getMainWindow());
dialog.centerOnScreen();
dialog.setSelectedIndices(fetchSelectedDeviceIndices());
dialog.setValues(new JListSelectionDialog.DataModel<String>() {
@Override
public String getViewValueAt(int idx) {
return devices.get(idx).getF();
}
@Override
public String getValueAt(int idx) {
return devices.get(idx).getE();
}
@Override
public int getValueCount() {
return devices.size();
}
});
dialog.setMessage(Bundle.getString("CopyToPushbulletAction.deviceSelection.message"));
dialog.setTitle(Bundle.getString("CopyToPushbulletAction.deviceSelection.title"));
if(dialog.ask()) {
List<Integer> selectedIndices = dialog.getSelectedIndices();
storeSelectedDeviceIndices(selectedIndices);
return new TransformValueList<Integer, String>(selectedIndices) {
@Override
public String transform(Integer idx) {
return devices.get(idx.intValue()).getE();
}
};
}
return Collections.emptyList();
}
private TransformValueList<Device, Pair<String, String>> transformToIdAndNamePair(PushbulletClient client) throws PushbulletException {
return new TransformValueList<Device, Pair<String, String>>(client.getDevices()) {
@Override
public Pair<String, String> transform(Device device) {
return new Pair<String, String>(device.getIden(), getDeviceName(device));
}
private String getDeviceName(Device device) {
return StringUtils.trim(StringUtil.capitalize(StringUtils.defaultString(device.getManufacturer())) + " " + StringUtil.capitalize(StringUtils.defaultString(device.getModel())));
}
};
}
private List<Pair<String, String>> filterUndesiredDevices(final List<Pair<String, String>> devices) {
List<Pair<String, String>> result = new ArrayList<>();
for (Pair<String, String> pair : devices) {
if(StringUtils.isNotBlank(pair.getF())) {
result.add(pair);
}
}
return result;
}
private void storeSelectedDeviceIndices(List<Integer> indices) {
APreferenceStore dbPreferenceStore = PreferenceStoreFactory.getPreferenceStore(PreferenceStoreFactory.DB_STORE);
String joinedIndices = ListUtils.join(indices, ";");
dbPreferenceStore.addGenericEntryAsString(PUSHBULLET_DEVICE_SELECTION_KEY, joinedIndices);
}
private List<Integer> fetchSelectedDeviceIndices() {
APreferenceStore dbPreferenceStore = PreferenceStoreFactory.getPreferenceStore(PreferenceStoreFactory.DB_STORE);
String joinedIndices = dbPreferenceStore.getGenericEntryAsString(PUSHBULLET_DEVICE_SELECTION_KEY);
if(StringUtil.isNotEmpty(joinedIndices)) {
List<String> splitIndiceStrings = ListUtils.split(joinedIndices, ";");
return new TransformValueList<String, Integer>(splitIndiceStrings) {
@Override
public Integer transform(String value) {
return Integer.valueOf(value);
}
};
} else {
return Collections.emptyList();
}
}
private String askForApiKey() {
JFrame mainWindow = MainController.getController().getMainWindow();
return DesktopUtils.showInputDialog(mainWindow, Bundle.getString("CopyToPushbulletAction.apikey.dialog.text"),
Bundle.getString("CopyToPushbulletAction.apikey.dialog.title"), null);
}
@Override
public List<String> doOnce() {
try {
PushbulletClient client = new PushbulletClient(getApiKey());
return this.targetDeviceIdentifiers = askForTargetDeviceIdentifier(client);
} catch (Throwable e) {
LoggerFactory.getLogger().log(Level.SEVERE, "Failed to connect to pushbullet service", e);
}
return null;
}
@Override
public void setDoOnceResult(List<String> targetDeviceIdentifiers) {
this.targetDeviceIdentifiers = targetDeviceIdentifiers;
}
@Override
public void prepareFor(int index, int size) {
}
}