/* * Funambol is a mobile platform developed by Funambol, Inc. * Copyright (C) 2010 Funambol, Inc. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation with the addition of the following permission * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program; if not, see http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. * * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License * version 3, these Appropriate Legal Notices must retain the display of the * "Powered by Funambol" logo. If the display of the logo is not reasonably * feasible for technical reasons, the Appropriate Legal Notices must display * the words "Powered by Funambol". */ package com.funambol.client.test.media; import java.util.Enumeration; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import com.funambol.client.source.AppSyncSource; import com.funambol.client.source.AppSyncSourceManager; import com.funambol.client.test.BasicScriptRunner; import com.funambol.client.test.Robot; import com.funambol.client.test.util.TestFileManager; import com.funambol.client.test.basic.BasicUserCommands; import com.funambol.sapisync.SapiSyncHandler; import com.funambol.sapisync.source.JSONFileObject; import com.funambol.sapisync.source.JSONSyncItem; import com.funambol.sapisync.source.JSONSyncSource; import com.funambol.sapisync.source.FileSyncSource; import com.funambol.sync.SyncConfig; import com.funambol.sync.SyncItem; import com.funambol.sync.SyncSource; import com.funambol.platform.FileAdapter; import com.funambol.util.Log; import com.funambol.util.StringUtil; import com.funambol.util.ConnectionManager; import com.funambol.org.json.me.JSONArray; import com.funambol.org.json.me.JSONException; import com.funambol.org.json.me.JSONObject; public abstract class MediaRobot extends Robot { private static final String TAG_LOG = "MediaRobot"; protected AppSyncSourceManager appSourceManager; protected TestFileManager fileManager; protected SapiSyncHandler sapiSyncHandler = null; public MediaRobot(AppSyncSourceManager appSourceManager, TestFileManager fileManager) { this.appSourceManager = appSourceManager; this.fileManager = fileManager; } public MediaRobot(TestFileManager fileManager) { this.fileManager = fileManager; } public void deleteMedia(String type, String filename) throws Exception { deleteFile(type, filename); } public void deleteFile(String type, String filename) throws Exception { SyncSource ss = getAppSyncSource(type).getSyncSource(); if(ss instanceof FileSyncSource) { String dirname = ((FileSyncSource)ss).getDirectory(); FileAdapter file = new FileAdapter(getFileFullName(dirname, filename)); if (file.exists()) { file.delete(); } } else { throw new IllegalArgumentException("Invalid SyncSource type: " + ss); } } public void deleteAllMedia(String type) throws Exception { deleteAllFiles(type); } public void deleteAllFiles(String type) throws Exception { SyncSource ss = getAppSyncSource(type).getSyncSource(); if(ss instanceof FileSyncSource) { String dirname = ((FileSyncSource)ss).getDirectory(); FileAdapter dir = new FileAdapter(dirname, true); Enumeration files = dir.list(false, false /* Filters hidden files */); dir.close(); while(files.hasMoreElements()) { String filename = (String)files.nextElement(); FileAdapter file = new FileAdapter(getFileFullName(dirname, filename)); file.delete(); file.close(); } } else { throw new IllegalArgumentException("Invalid SyncSource type: " + ss); } } public void overrideMediaContent(String type, String targetFileName, String sourceFileName) throws Throwable { FileAdapter file = getMediaOutputStream(type, targetFileName); OutputStream os = file.openOutputStream(); getMediaFile(sourceFileName, os); } public void addMedia(String type, String filename) throws Throwable { FileAdapter file = getMediaOutputStream(type, filename); OutputStream os = file.openOutputStream(); getMediaFile(filename, os); } private FileAdapter getMediaOutputStream(String type, String filename) throws Throwable { SyncSource source = getAppSyncSource(type).getSyncSource(); String fullname = null; if (source instanceof FileSyncSource) { FileSyncSource fss = (FileSyncSource)source; filename = getFileNameFromFullName(filename); fullname = fss.getFileFullName(filename); } else { return null; } fullname = StringUtil.simplifyFileName(fullname); FileAdapter f = new FileAdapter(fullname); return f; } public void checkMediaCount(String type, int count) throws Throwable { int localCount = getFilesCount(type); if (count != localCount) { Log.error(TAG_LOG, "Expected " + count + " -- found " + localCount); } assertTrue(count == localCount, "Local media items count mismatch"); } /** * Add a media to the server. Media content is read based on specified * file name (a file in application's assets or an online resource). * * @param type sync source type * @param filename a relative path to the resource. BaseUrl parameter on test * configuration defines if the resource is a local file or is * an URL. In both cases, file is copied locally and then * uploaded to the server. * @throws Throwable */ public void addMediaOnServer(String type, String filename) throws Throwable { //make file available on local storage ByteArrayOutputStream os = new ByteArrayOutputStream(); String contentType = getMediaFile(filename, os); byte[] fileContent = os.toByteArray(); int size = fileContent.length; InputStream is = new ByteArrayInputStream(fileContent); addMediaOnServerFromStream(type, filename, is, size, contentType, null); } public void overrideMediaContentOnServer(String type, String targetFileName, String sourceFileName) throws Throwable { String itemId = findMediaIdOnServer(type, targetFileName); ByteArrayOutputStream os = new ByteArrayOutputStream(); String contentType = getMediaFile(sourceFileName, os); byte[] fileContent = os.toByteArray(); int size = fileContent.length; InputStream is = new ByteArrayInputStream(fileContent); addMediaOnServerFromStream(type, targetFileName, is, size, contentType, itemId); } /** * Add a media to the server. Media content is read from a stream. * * @param type sync source type * @param itemName name of the item to add * @param contentStream stream to the content of the item * @param contentSize size of the item * @param contentType mimetype of the content to add * * @throws JSONException */ protected void addMediaOnServerFromStream(String type, String itemName, InputStream contentStream, long contentSize, String contentType, String guid) throws JSONException { itemName = getFileNameFromFullName(itemName); if (Log.isLoggable(Log.DEBUG)) { Log.debug(TAG_LOG, "Adding/updating media on server for source " + getRemoteUri(type) + " with name " + itemName + " of type " + contentType + " and size " + contentSize); } // Prepare json item to upload JSONFileObject jsonFileObject = new JSONFileObject(); jsonFileObject.setName(itemName); jsonFileObject.setSize(contentSize); jsonFileObject.setCreationdate(System.currentTimeMillis()); jsonFileObject.setLastModifiedDate(System.currentTimeMillis()); jsonFileObject.setMimetype(contentType); MediaSyncItem item = new MediaSyncItem("fake_key", "fake_type", guid != null ? SyncItem.STATE_UPDATED : SyncItem.STATE_NEW, null, jsonFileObject, contentStream, contentSize); if (guid != null) { item.setGuid(guid); } SapiSyncHandler sapiHandler = getSapiSyncHandler(); sapiHandler.login(null); String remoteKey = sapiHandler.prepareItemUpload(item, getRemoteUri(type)); item.setGuid(remoteKey); sapiHandler.uploadItem(item, getRemoteUri(type), null); sapiHandler.logout(); } public void deleteMediaOnServer(String type, String filename) throws Throwable { SapiSyncHandler sapiHandler = getSapiSyncHandler(); String itemId = findMediaIdOnServer(type, filename); sapiHandler.login(null); sapiHandler.deleteItem(itemId, getRemoteUri(type), getDataTag(type)); sapiHandler.logout(); } private String findMediaIdOnServer(String type, String filename) throws Throwable { JSONObject item = findMediaJSONObjectOnServer(type, filename); if(item != null) { return item.getString("id"); } return null; } private JSONObject findMediaJSONObjectOnServer(String type, String filename) throws Throwable { SapiSyncHandler sapiHandler = getSapiSyncHandler(); sapiHandler.login(null); try { SapiSyncHandler.FullSet itemsSet = sapiHandler.getItems( getRemoteUri(type), getDataTag(type), null, null, null, null); JSONArray items = itemsSet.items; for (int i = 0; i < items.length(); ++i) { JSONObject item = items.getJSONObject(i); String aFilename = item.getString("name"); if (filename.equals(aFilename)) { return item; } } } finally { sapiHandler.logout(); } return null; } public void checkMediaCountOnServer(String type, int count) throws Throwable { SapiSyncHandler sapiHandler = getSapiSyncHandler(); sapiHandler.login(null); int actualCount = sapiHandler.getItemsCount(getRemoteUri(type), null); sapiHandler.logout(); assertTrue(actualCount == count, "Items count on server mismatch for " + type); } public void deleteAllMediaOnServer(String type) throws Throwable { SapiSyncHandler sapiHandler = getSapiSyncHandler(); sapiHandler.login(null); sapiHandler.deleteAllItems(getRemoteUri(type)); sapiHandler.logout(); } /** * Fills the server with some data in order to leave no more space for a * further upload of the same file specified as parameter * * @param type * @param fileName name of the file to use as a reference * @throws Throwable */ public void leaveNoFreeServerQuota(String type, String fileName) throws Throwable { // get free quota for the current user SapiSyncHandler sapiHandler = getSapiSyncHandler(); sapiHandler.login(null); long availableSpace = sapiHandler .getUserAvailableServerQuota(getRemoteUri(type)); sapiHandler.logout(); //make file available on local storage ByteArrayOutputStream os = new ByteArrayOutputStream(); String contentType = getMediaFile(fileName, os); byte[] fileContent = os.toByteArray(); int pictureSize = fileContent.length; long repetitions = availableSpace / pictureSize; if (repetitions > 0) { if (Log.isLoggable(Log.DEBUG)) { Log.debug(TAG_LOG, "Available user quota is " + availableSpace + ", while picture size is " + pictureSize); Log.debug(TAG_LOG, "Filling the user quota, please wait..."); } for (long i=1; i <= repetitions; i++) { String newFileName = i + fileName; Log.trace(TAG_LOG, "Upload file " + newFileName + " on server [" + i + "/" + repetitions + "]"); InputStream is = new ByteArrayInputStream(fileContent); addMediaOnServerFromStream(type, i + newFileName, is, pictureSize, contentType, null); } } else { if (Log.isLoggable(Log.DEBUG)) { Log.debug(TAG_LOG, "Available user quota is " + availableSpace + ", less that the required. Nothing to do."); } } } public void interruptItem(String phase, String itemKey, int pos, int itemIdx) { ConnectionManager.getInstance().setBreakInfo(phase, itemKey, pos, itemIdx); } protected AppSyncSourceManager getAppSyncSourceManager() { return appSourceManager; } /** * Returns the AppSyncSource related to the given data type * * @param type * @return */ protected AppSyncSource getAppSyncSource(String type) { if (StringUtil.equalsIgnoreCase( BasicUserCommands.SOURCE_NAME_PICTURES, type)) { return getAppSyncSourceManager().getSource( AppSyncSourceManager.PICTURES_ID); } else if (StringUtil.equalsIgnoreCase( BasicUserCommands.SOURCE_NAME_VIDEOS, type)) { return getAppSyncSourceManager().getSource( AppSyncSourceManager.VIDEOS_ID); } else if (StringUtil.equalsIgnoreCase( BasicUserCommands.SOURCE_NAME_FILES, type)) { return getAppSyncSourceManager().getSource( AppSyncSourceManager.FILES_ID); } else { throw new IllegalArgumentException("Invalid type: " + type); } } /** * Returns the SAPI data tag related to the given data type. * * @param type * @return */ private String getRemoteUri(String type) { return getAppSyncSource(type).getSyncSource().getConfig() .getRemoteUri(); } private String getDataTag(String type) { SyncSource src = getAppSyncSource(type).getSyncSource(); String dataTag = null; if (src instanceof JSONSyncSource) { JSONSyncSource jsonSyncSource = (JSONSyncSource) src; dataTag = jsonSyncSource.getDataTag(); } return dataTag; } private SapiSyncHandler getSapiSyncHandler() { if (sapiSyncHandler == null) { SyncConfig syncConfig = getSyncConfig(); sapiSyncHandler = new SapiSyncHandler( StringUtil.extractAddressFromUrl(syncConfig.getSyncUrl()), syncConfig.getUserName(), syncConfig.getPassword()); } return sapiSyncHandler; } /** * This is used to override the item input stream */ private class MediaSyncItem extends JSONSyncItem { private InputStream stream; private long size; public MediaSyncItem(String key, String type, char state, String parent, JSONFileObject jsonFileObject, InputStream stream, long size) throws JSONException { super(key, type, state, parent, jsonFileObject); this.stream = stream; this.size = size; } public InputStream getInputStream() throws IOException { return stream; } public long getObjectSize() { return size; } } protected String getMediaFile(String filename, OutputStream output) throws Throwable { String baseUrl = BasicScriptRunner.getBaseUrl(); String url = baseUrl + "/" + filename; return fileManager.getFile(url, output); } private String getFileFullName(String directory, String name) { StringBuffer fullname = new StringBuffer(); fullname.append(directory); if(!directory.endsWith("/")) { fullname.append("/"); } fullname.append(name); return fullname.toString(); } private String getFileNameFromFullName(String fullName) { int idx = StringUtil.lastIndexOf(fullName, '/'); String fileName; if (idx != -1) { fileName = fullName.substring(idx+1); } else { fileName = fullName; } return fileName; } protected abstract void fillLocalStorage(); protected abstract void restoreLocalStorage(); protected abstract SyncConfig getSyncConfig(); /** * Creates a temporary file of specified size * * @param byteSize size of the file * @param header header of the file * @param footer footer of the file * * @return name of the file created * @throws IOException */ protected abstract void createFileWithSizeOnDevice(long byteSize, String header, String footer) throws IOException; /** * Creates a file in mediahub directory * * @param fileName * @param fileSize * @throws IOException */ public abstract void createFile(String fileName, long fileSize) throws Exception; /** * Renames a file in the server * * @param oldFileName * @param newFileName * @throws IOException */ public void renameFileOnServer(String type, String oldFileName, String newFileName) throws Throwable { SapiSyncHandler sapiHandler = getSapiSyncHandler(); String itemId = findMediaIdOnServer(type, oldFileName); sapiHandler.login(null); sapiHandler.updateItemName(getRemoteUri(type), itemId, newFileName); sapiHandler.logout(); } /** * Checks the integrity of a file content on both client and server * @param filename * @throws Throwable */ public void checkFileContentIntegrity(String type, String fileNameClient, String fileNameServer) throws Throwable { JSONObject localItem = findMediaJSONObject(type, fileNameClient); JSONObject serverItem = findMediaJSONObjectOnServer(type, fileNameServer); assertTrue(localItem != null, "Item not found on client"); assertTrue(serverItem != null, "Item not found on server"); assertTrue(localItem.getLong("size"), serverItem.getLong("size"), "Item size mismatch"); } public void renameFile(String type, String oldFileName, String newFileName) throws Exception { SyncSource ss = getAppSyncSource(type).getSyncSource(); assertTrue(ss instanceof FileSyncSource, "Invalid source type"); if(ss instanceof FileSyncSource) { String dirname = ((FileSyncSource)ss).getDirectory(); FileAdapter oldFile = new FileAdapter(getFileFullName(dirname, oldFileName)); assertTrue(oldFile.exists(), "File not found: " + getFileFullName(dirname, oldFileName)); String newFileFullName = getFileFullName(dirname, newFileName); oldFile.rename(newFileFullName); } } private int getFilesCount(String type) throws Exception { SyncSource ss = getAppSyncSource(type).getSyncSource(); if(ss instanceof FileSyncSource) { int count = 0; String dirname = ((FileSyncSource)ss).getDirectory(); FileAdapter dir = new FileAdapter(dirname, true); Enumeration files = dir.list(false, false /* Filters hidden files */); dir.close(); while(files.hasMoreElements()) { String file = (String)files.nextElement(); if (doesMediaBelongToSource(type, file)) { count++; } } return count; } else { throw new IllegalArgumentException("Invalid SyncSource type: " + ss); } } private boolean doesMediaBelongToSource(String type, String file) { if (BasicUserCommands.SOURCE_NAME_FILES.equals(type)) { return true; } else if (BasicUserCommands.SOURCE_NAME_PICTURES.equals(type)) { // These are all extensions for this type, not only the one we // support in upload String extensions[] = {"jpg","jpeg","bmp","gif","png","tiff"}; return isSupportedExtension(extensions, file); } else if (BasicUserCommands.SOURCE_NAME_VIDEOS.equals(type)) { // These are all extensions for this type, not only the one we // support in upload String extensions[] = {"3gp","mp4","mpeg","flv","swf","avi"}; return isSupportedExtension(extensions, file); } throw new IllegalArgumentException("Unknown source type " + type); } private boolean isSupportedExtension(String extensions[], String name) { // If there are no valid extensions defined, then the source does not // apply any filter if (extensions == null || extensions.length == 0) { return true; } name = name.toLowerCase(); for(int i=0;i<extensions.length;++i) { String ext = extensions[i].toLowerCase(); if (name.endsWith(ext)) { return true; } } return false; } protected JSONObject findMediaJSONObject(String type, String filename) throws Exception { JSONObject result = new JSONObject(); SyncSource ss = getAppSyncSource(type).getSyncSource(); assertTrue(ss instanceof FileSyncSource, "Invalid source type"); if(ss instanceof FileSyncSource) { String dirname = ((FileSyncSource)ss).getDirectory(); FileAdapter file = new FileAdapter(getFileFullName(dirname, filename)); assertTrue(file.exists(), "File not found: " + getFileFullName(dirname, filename)); result.put("name", getFileNameFromFullName(file.getName())); result.put("modificationdate", file.lastModified()); result.put("size", file.getSize()); } return result; } }