diff --git a/src/main/groovy/com/sysgears/seleniumbundle/core/data/cloud/AbstractCloudService.groovy b/src/main/groovy/com/sysgears/seleniumbundle/core/data/cloud/AbstractCloudService.groovy new file mode 100644 index 0000000..5c41b3e --- /dev/null +++ b/src/main/groovy/com/sysgears/seleniumbundle/core/data/cloud/AbstractCloudService.groovy @@ -0,0 +1,5 @@ +package com.sysgears.seleniumbundle.core.data.cloud + +abstract class AbstractCloudService implements ICloudService { + +} diff --git a/src/main/groovy/com/sysgears/seleniumbundle/core/data/cloud/ICloudService.groovy b/src/main/groovy/com/sysgears/seleniumbundle/core/data/cloud/ICloudService.groovy new file mode 100644 index 0000000..c64f0b6 --- /dev/null +++ b/src/main/groovy/com/sysgears/seleniumbundle/core/data/cloud/ICloudService.groovy @@ -0,0 +1,12 @@ +package com.sysgears.seleniumbundle.core.data.cloud + +interface ICloudService { + + void downloadFile(String remotePath, String localPath) + + void downloadFiles(String remotePath, String localPath) + + void uploadFile(String localPath, String remotePath) + + void uploadFiles(String localPath, String remotePath) +} \ No newline at end of file diff --git a/src/main/groovy/com/sysgears/seleniumbundle/core/dropbox/DropboxClient.groovy b/src/main/groovy/com/sysgears/seleniumbundle/core/data/cloud/dropbox/DropboxCloudService.groovy similarity index 53% rename from src/main/groovy/com/sysgears/seleniumbundle/core/dropbox/DropboxClient.groovy rename to src/main/groovy/com/sysgears/seleniumbundle/core/data/cloud/dropbox/DropboxCloudService.groovy index 5081086..9dcc489 100644 --- a/src/main/groovy/com/sysgears/seleniumbundle/core/dropbox/DropboxClient.groovy +++ b/src/main/groovy/com/sysgears/seleniumbundle/core/data/cloud/dropbox/DropboxCloudService.groovy @@ -1,4 +1,4 @@ -package com.sysgears.seleniumbundle.core.dropbox +package com.sysgears.seleniumbundle.core.data.cloud.dropbox import com.dropbox.core.BadRequestException import com.dropbox.core.DbxException @@ -6,6 +6,8 @@ import com.dropbox.core.DbxRequestConfig import com.dropbox.core.v2.DbxClientV2 import com.dropbox.core.v2.files.DeleteErrorException import com.sysgears.seleniumbundle.core.conf.Config +import com.sysgears.seleniumbundle.core.data.cloud.AbstractCloudService +import com.sysgears.seleniumbundle.core.utils.FileHelper import com.sysgears.seleniumbundle.core.utils.PathHelper import groovy.util.logging.Slf4j @@ -13,7 +15,7 @@ import groovy.util.logging.Slf4j * Client for Dropbox. Provides methods to work with Dropbox API. */ @Slf4j -class DropboxClient { +class DropboxCloudService extends AbstractCloudService { /** * Project properties. @@ -32,75 +34,109 @@ class DropboxClient { private final DbxClientV2 client /** - * Creates an instance of a custom DropboxClient. + * Creates an instance of a custom DropboxCloudService. */ - DropboxClient() { + DropboxCloudService() { client = new DbxClientV2(config, conf.dropbox.accessToken as String) } /** * Downloads a file from Dropbox. * - * @param dropboxPath path to a file on Dropbox - * @param downloadPath path where to download the file + * @param remotePath path to a file on Dropbox + * @param localPath path to download the file to * * @throws IOException if any error occurs while downloading file from Dropbox */ - void downloadFile(String dropboxPath, String downloadPath) throws IOException { - File file = new File(downloadPath) + @Override + void downloadFile(String remotePath, String localPath) throws IOException { + File file = new File(localPath) + file.getParentFile().mkdirs() try { - client.files().download(dropboxPath).download(new FileOutputStream(file)) + client.files().download(remotePath).download(new FileOutputStream(file)) } catch (BadRequestException e) { - log.error("Unable to download a file from $dropboxPath.", e) - throw new IOException("Unable to download a file from $dropboxPath, invalid request", e) + log.error("Unable to download a file from $remotePath.", e) + throw new IOException("Unable to download a file from $remotePath, invalid request", e) } catch (IOException | DbxException e) { - log.error("Unable to download a file from $dropboxPath.", e) - throw new IOException("Unable to download a file from $dropboxPath.", e) + log.error("Unable to download a file from $remotePath.", e) + throw new IOException("Unable to download a file from $remotePath.", e) + } + } + + /** + * Downloads all files stored in remote path on Dropbox. + * + * @param remotePath path to files on Dropbox + * @param localPath local path to download the files to + */ + @Override + void downloadFiles(String remotePath, String localPath) { + getDropboxPaths().each { + downloadFile(it, localPath + it - remotePath) } } /** * Uploads a file to Dropbox. * - * @param dropboxPath path for saving a file on Dropbox - * @param uploadPath local path to a file + * @param localPath local path to a file + * @param remotePath path for saving a file on Dropbox * * @throws IOException if any error occurs while uploading file to Dropbox */ - void uploadFile(String dropboxPath, String uploadPath) throws IOException { - dropboxPath = PathHelper.convertToUnixLike(dropboxPath) - log.info("uploading $dropboxPath") + @Override + void uploadFile(String localPath, String remotePath) throws IOException { + remotePath = PathHelper.convertToUnixLike(remotePath) + + // delete is a workaround due to issues with "withMode(WriteMode.OVERWRITE)" + deleteFile(remotePath) + try { - client.files().uploadBuilder("$dropboxPath").uploadAndFinish(new FileInputStream(uploadPath)) + client.files().uploadBuilder("/$remotePath").uploadAndFinish(new FileInputStream(localPath)) } catch (BadRequestException e) { - log.error("Unable to upload a file from $uploadPath.", e) - throw new IOException("Unable to upload a file from $uploadPath, invalid request", e) + log.error("Unable to upload a file from $localPath.", e) + throw new IOException("Unable to upload a file from $localPath, invalid request", e) } catch (IOException | DbxException e) { - log.error("Unable to upload a file from $uploadPath.", e) - throw new IOException("Unable to upload a file from $uploadPath.", e) + log.error("Unable to upload a file from $localPath.", e) + throw new IOException("Unable to upload a file from $localPath.", e) + } + } + + /** + * Uploads all files stored in local path to Dropbox. + * + * @param localPath path to local directory + * @param remotePath path for saving files on Dropbox + */ + @Override + void uploadFiles(String localPath, String remotePath) { + + FileHelper.getFiles(localPath).each { + uploadFile(it.path, remotePath + it.path - localPath) } } /** * Deletes file saved in Dropbox path. * - * @param dropboxPath path to the file to be deleted + * @param remotePath path to the file to be deleted * * @throws IOException if any error occurs while deleting file on Dropbox */ - void deleteFile(String dropboxPath) throws IOException { - dropboxPath = PathHelper.convertToUnixLike(dropboxPath) + void deleteFile(String remotePath) throws IOException { + remotePath = PathHelper.convertToUnixLike(remotePath) + try { - client.files().deleteV2("$dropboxPath") + client.files().deleteV2("/$remotePath") } catch (DeleteErrorException ignored) { // exception is thrown if there is no file of given path } catch (BadRequestException e) { - log.error("Unable to delete a file from $dropboxPath.", e) - throw new IOException("Unable to delete a file from $dropboxPath, invalid request", e) + log.error("Unable to delete a file from $remotePath.", e) + throw new IOException("Unable to delete a file from $remotePath, invalid request", e) } catch (DbxException e) { - log.error("Unable to delete a file from $dropboxPath.", e) - throw new IOException("Unable to delete a file from $dropboxPath.", e) + log.error("Unable to delete a file from $remotePath.", e) + throw new IOException("Unable to delete a file from $remotePath.", e) } } @@ -112,6 +148,7 @@ class DropboxClient { * @throws IOException if any error occurs while getting Dropbox paths */ List getDropboxPaths() throws IOException { + try { client.files().listFolderBuilder("").withRecursive(true).start().getEntries().collect { it.getPathLower() diff --git a/src/main/groovy/com/sysgears/seleniumbundle/core/uicomparison/commands/DownloadFromCommand.groovy b/src/main/groovy/com/sysgears/seleniumbundle/core/uicomparison/commands/DownloadFromCommand.groovy new file mode 100644 index 0000000..793602d --- /dev/null +++ b/src/main/groovy/com/sysgears/seleniumbundle/core/uicomparison/commands/DownloadFromCommand.groovy @@ -0,0 +1,56 @@ +package com.sysgears.seleniumbundle.core.uicomparison.commands + +import com.sysgears.seleniumbundle.core.command.AbstractCommand +import com.sysgears.seleniumbundle.core.conf.Config +import com.sysgears.seleniumbundle.core.data.cloud.ICloudService +import com.sysgears.seleniumbundle.core.implicitinit.annotations.ImplicitInit +import com.sysgears.seleniumbundle.core.utils.ClassFinder +import org.apache.commons.io.FilenameUtils + +/** + * Class which provides the method to download screenshots from Dropbox. + */ +class DownloadFromCommand extends AbstractCommand { + + /** + * Name of cloud service to be used. + */ + @ImplicitInit(pattern = "googledrive|dropbox", isRequired = true) + private String service + + /** + * Category of the downloaded screenshots. + */ + @ImplicitInit(pattern = "baseline|difference|actual", isRequired = true) + private List categories + + /** + * Instance of Cloud Client to be used by the command. + */ + private ICloudService serviceInstance + + /** + * Creates an instance of DownloadFromCommand. + * + * @param arguments map that contains command arguments + * @param conf project properties + * + * @throws IllegalArgumentException is thrown in case a value is missing for a mandatory parameter or + * the value doesn't match the validation pattern + */ + DownloadFromCommand(Map> arguments, Config conf) throws IllegalArgumentException { + super(arguments, conf) + + serviceInstance = ClassFinder.findCloudService(service, conf) + } + + /** + * Executes the command. + */ + @Override + void execute() { + categories.each { category -> + serviceInstance.downloadFiles(category, FilenameUtils.separatorsToSystem(conf.ui.path."$category")) + } + } +} \ No newline at end of file diff --git a/src/main/groovy/com/sysgears/seleniumbundle/core/uicomparison/commands/DownloadFromDropboxCommand.groovy b/src/main/groovy/com/sysgears/seleniumbundle/core/uicomparison/commands/DownloadFromDropboxCommand.groovy deleted file mode 100644 index af9e1d7..0000000 --- a/src/main/groovy/com/sysgears/seleniumbundle/core/uicomparison/commands/DownloadFromDropboxCommand.groovy +++ /dev/null @@ -1,65 +0,0 @@ -package com.sysgears.seleniumbundle.core.uicomparison.commands - -import com.sysgears.seleniumbundle.core.command.AbstractCommand -import com.sysgears.seleniumbundle.core.conf.Config -import com.sysgears.seleniumbundle.core.dropbox.DropboxClient -import com.sysgears.seleniumbundle.core.implicitinit.annotations.ImplicitInit -import groovy.util.logging.Slf4j -import org.apache.commons.io.FilenameUtils - -/** - * Class which provides the method to download screenshots from Dropbox. - */ -@Slf4j -class DownloadFromDropboxCommand extends AbstractCommand { - - /** - * Category of the downloaded screenshots. - */ - @ImplicitInit(pattern = "baseline|difference|actual", isRequired = true) - private List categories - - /** - * Instance of Dropbox Client to be used by the command. - */ - private DropboxClient client = new DropboxClient() - - /** - * Creates an instance of DownloadFromDropboxCommand. - * - * @param arguments map that contains command arguments - * @param conf project properties - * - * @throws IllegalArgumentException is thrown in case a value is missing for a mandatory parameter or - * the value doesn't match the validation pattern - */ - DownloadFromDropboxCommand(Map> arguments, Config conf) throws IllegalArgumentException { - super(arguments, conf) - } - - /** - * Executes the command. - * - * @throws IOException in case Dropbox client operations produced an error - */ - @Override - void execute() throws IOException { - categories.each { category -> - def categoryPath = FilenameUtils.separatorsToSystem(conf.ui.path."$category") - def remotePaths = client.dropboxPaths.findAll { - it.matches(/^\/$category\/(.*).png\$/) - } - - if (!remotePaths) { - log.error("No $category files found on Dropbox.") - throw new IOException("No $category files found on Dropbox.") - } - - remotePaths.each { remotePath -> - def localPath = "${categoryPath.substring(0, categoryPath.lastIndexOf('/'))}${remotePath}" - - client.downloadFile(remotePath, localPath) - } - } - } -} \ No newline at end of file diff --git a/src/main/groovy/com/sysgears/seleniumbundle/core/uicomparison/commands/UploadToCommand.groovy b/src/main/groovy/com/sysgears/seleniumbundle/core/uicomparison/commands/UploadToCommand.groovy new file mode 100644 index 0000000..eac202d --- /dev/null +++ b/src/main/groovy/com/sysgears/seleniumbundle/core/uicomparison/commands/UploadToCommand.groovy @@ -0,0 +1,57 @@ +package com.sysgears.seleniumbundle.core.uicomparison.commands + +import com.sysgears.seleniumbundle.core.command.AbstractCommand +import com.sysgears.seleniumbundle.core.conf.Config +import com.sysgears.seleniumbundle.core.data.cloud.ICloudService +import com.sysgears.seleniumbundle.core.implicitinit.annotations.ImplicitInit +import com.sysgears.seleniumbundle.core.utils.ClassFinder +import org.apache.commons.io.FilenameUtils + +/** + * Class which provides the method to upload screenshots to Dropbox. + */ +class UploadToCommand extends AbstractCommand { + + /** + * Name of cloud service to be used. + */ + @ImplicitInit(pattern = "googledrive|dropbox", isRequired = true) + String service + + /** + * Category of the uploaded screenshots. + */ + @ImplicitInit(pattern = "baseline|difference|actual", isRequired = true) + private List categories + + /** + * Instance of Cloud Client to be used by the command. + */ + private ICloudService serviceInstance + + /** + * Creates an instance of UploadToCommand. + * + * @param @param arguments the map with arguments of the command + * @param conf project properties + * + * @throws IllegalArgumentException is thrown in case a value is missing for a mandatory parameter or + * the value doesn't match the validation pattern + */ + UploadToCommand(Map> arguments, Config conf) throws IllegalArgumentException { + super(arguments, conf) + + serviceInstance = ClassFinder.findCloudService(service, conf) + } + + /** + * Executes the command. + */ + @Override + void execute() throws IOException { + categories.each { category -> + + serviceInstance.uploadFiles(category, FilenameUtils.separatorsToSystem(conf.ui.path."$category")) + } + } +} diff --git a/src/main/groovy/com/sysgears/seleniumbundle/core/uicomparison/commands/UploadToDropboxCommand.groovy b/src/main/groovy/com/sysgears/seleniumbundle/core/uicomparison/commands/UploadToDropboxCommand.groovy deleted file mode 100644 index 0055ddf..0000000 --- a/src/main/groovy/com/sysgears/seleniumbundle/core/uicomparison/commands/UploadToDropboxCommand.groovy +++ /dev/null @@ -1,66 +0,0 @@ -package com.sysgears.seleniumbundle.core.uicomparison.commands - -import com.sysgears.seleniumbundle.core.command.AbstractCommand -import com.sysgears.seleniumbundle.core.conf.Config -import com.sysgears.seleniumbundle.core.dropbox.DropboxClient -import com.sysgears.seleniumbundle.core.implicitinit.annotations.ImplicitInit -import com.sysgears.seleniumbundle.core.utils.FileHelper -import groovy.util.logging.Slf4j -import org.apache.commons.io.FilenameUtils - -/** - * Class which provides the method to upload screenshots to Dropbox. - */ -@Slf4j -class UploadToDropboxCommand extends AbstractCommand { - - /** - * Category of the uploaded screenshots. - */ - @ImplicitInit(pattern = "baseline|difference|actual", isRequired = true) - private List categories - - /** - * Instance of Dropbox Client to be used by the command. - */ - private DropboxClient client = new DropboxClient() - - /** - * Creates an instance of UploadToDropboxCommand. - * - * @param @param arguments the map with arguments of the command - * @param conf project properties - * - * @throws IllegalArgumentException is thrown in case a value is missing for a mandatory parameter or - * the value doesn't match the validation pattern - */ - UploadToDropboxCommand(Map> arguments, Config conf) throws IllegalArgumentException { - super(arguments, conf) - } - - /** - * Executes the command. - * - * @throws IOException in case Dropbox client operations produce an error. - */ - @Override - void execute() throws IOException { - categories.each { category -> - def categoryPath = FilenameUtils.separatorsToSystem(conf.ui.path."$category") - def localPaths = FileHelper.getFiles(categoryPath).collect { it.path } - - if (!localPaths) { - log.error("No $category files found.") - throw new IOException("No $category files found.") - } - - localPaths.each { localPath -> - def remotePath = localPath - categoryPath.substring(0, categoryPath.lastIndexOf(File.separator)) - - // delete is a workaround due to issues with "withMode(WriteMode.OVERWRITE)" - client.deleteFile(remotePath) - client.uploadFile(remotePath, localPath) - } - } - } -} diff --git a/src/main/groovy/com/sysgears/seleniumbundle/core/utils/ClassFinder.groovy b/src/main/groovy/com/sysgears/seleniumbundle/core/utils/ClassFinder.groovy new file mode 100644 index 0000000..e025333 --- /dev/null +++ b/src/main/groovy/com/sysgears/seleniumbundle/core/utils/ClassFinder.groovy @@ -0,0 +1,104 @@ +package com.sysgears.seleniumbundle.core.utils + +import com.sysgears.seleniumbundle.core.command.AbstractCommand +import com.sysgears.seleniumbundle.core.command.CommandArgs +import com.sysgears.seleniumbundle.core.conf.Config +import com.sysgears.seleniumbundle.core.data.cloud.AbstractCloudService +import groovy.util.logging.Slf4j +import org.apache.commons.io.FilenameUtils + +/** + * Provides methods to find classes by criteria. + */ +@Slf4j +class ClassFinder { + + /** + * The path to main sources. + */ + private final static String GROOVY_SOURCE_PATH = "src/main/groovy/" + + /** + * The root package to start searching from. + */ + private final static String ROOT_DIR = "${GROOVY_SOURCE_PATH}com/sysgears/seleniumbundle" + + /** + * Finds a command by a given name which extends Abstract command. + * + * @param args parsed command arguments which have command name and arguments + * @param instance of config + * + * @return an instance of the found command initialized with the given arguments and the config + */ + static T findCommand(CommandArgs args, Config conf) { + find(args.name, AbstractCommand, "Command", "commands") + .newInstance(args.arguments, conf) as T + } + + /** + * Finds a cloud service by a given name which extends AbstractCloudService. + * + * @param className name of a class + * @param conf instance of config + * + * @return an instance of the found cloud service initialized with the config + */ + static T findCloudService(String className, Config conf) { + find(className, AbstractCloudService, "Service") + .newInstance(conf) as T + } + + /** + * Finds a class by the given name which has the given sub name and is stored in the given sub folder. + * + * @param className name of a class + * @param parentClass class which the sought class extends + * @param subName sub name of the class + * @param subFolder name of the sub folder to search for the class in + * + * @return class of the found groovy class + * + * @throws IllegalArgumentException if command has not been found + */ + static Class find(String className, Class parentClass, String subName, String subFolder = "") + throws IllegalArgumentException { + def clazz = FileHelper.getFiles(ROOT_DIR, "groovy").findAll { + it.path.matches(/^(\w*${File.separator})*$subFolder(${File.separator}\w*)*$subName\.groovy$/) + }.findAll { File file -> + getClassName(file, subName).equalsIgnoreCase(className) + }.findResult { + def clazz = Class.forName(getClassPath(it.path)) + + (hasParent(clazz, parentClass)) ? clazz : null + } + + clazz ?: { + log.error("Class [$className] wasn't found.") + throw new IllegalArgumentException("Class [$className] wasn't found.") + }() + } + + private static String getClassName(File file, String subName) { + file.name - "${subName}.groovy" + } + + private static String getClassPath(String filePath) { + (filePath - FilenameUtils.separatorsToSystem(GROOVY_SOURCE_PATH) - ".groovy") + .split(File.separator).join(".") + } + + /** + * Checks if a class or any of its superclasses has a target class as parent. + * + * @param clazz class to start the check from + * @param targetClass expected parent + * + * @return true if class has a target class as one of the superclasses, false otherwise + */ + private static Boolean hasParent(Class clazz, Class targetClass) { + def parent = clazz.getSuperclass() + + parent ? parent == targetClass ?: hasParent(parent, targetClass) : false + } +}