package org.openslx.imagemaster.serverconnection; import java.io.IOException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.log4j.Logger; import org.openslx.imagemaster.Globals; import org.openslx.imagemaster.crcchecker.CRCFile; import org.openslx.imagemaster.db.DbImage; import org.openslx.imagemaster.db.DbUser; import org.openslx.imagemaster.thrift.iface.DownloadInfos; import org.openslx.imagemaster.thrift.iface.ImageData; import org.openslx.imagemaster.thrift.iface.ImageDataError; import org.openslx.imagemaster.thrift.iface.ImageDataException; import org.openslx.imagemaster.thrift.iface.UploadError; import org.openslx.imagemaster.thrift.iface.UploadException; import org.openslx.imagemaster.thrift.iface.UploadInfos; import org.openslx.imagemaster.util.RandomString; /** * Processing the up- and download of images. * Handles who is authorized and knows which blocks are missing / need to be sent */ public class ImageProcessor { private static final Logger log = Logger.getLogger( ImageProcessor.class ); /** * The amount of blocks that is return in UploadInfos (after request of satellite) */ private static final int AMOUNT = 20; /** * The uploading images. * Key: imageUUID, * Value: uploadingImageInfos */ private static Map uploadingImages = new ConcurrentHashMap<>(); /** * The UUIDs of the images that need to be checked by the crc checker. */ private static List imagesToCheck = new LinkedList<>(); /** * The downloading clients. * Key: serverSessionId * Value: downloadingClientInfos */ private static HashMap downloadingClients = new HashMap<>(); /** * Checks if this image is already uploading and returns a new list with missing blocks if so. * Puts the new image into processing list else. * * @param serverSessionId The uploading server * @param imageData The data of the image * @return * @throws UploadException If some error occurred during the process */ public static UploadInfos getUploadInfos( String serverSessionId, ImageData imageData, List crcSums ) throws UploadException, ImageDataException { // check image data if ( DbImage.exists( imageData.uuid ) ) { throw new ImageDataException( ImageDataError.INVALID_DATA, "UUID already existing." ); } else if ( imageData.imageName == null || imageData.imageName.isEmpty() ) { throw new ImageDataException( ImageDataError.INVALID_DATA, "Image name not set." ); } else if ( imageData.imageName == null || imageData.imageOwner.isEmpty() ) { throw new ImageDataException( ImageDataError.INVALID_DATA, "Image owner not set." ); } else if ( imageData.contentOperatingSystem == null || imageData.contentOperatingSystem.isEmpty() ) { throw new ImageDataException( ImageDataError.INVALID_DATA, "Content operating system not set." ); } else if ( imageData.fileSize <= 0 ) { throw new ImageDataException( ImageDataError.INVALID_DATA, "File size is too small." ); } else if ( !DbUser.exists( imageData.imageOwner ) ) { throw new ImageDataException( ImageDataError.INVALID_DATA, "User is not known." ); } String uuid = imageData.uuid; String token; String filepath; int nBlocks; int[] allBlocks; UploadingImage image; synchronized ( uploadingImages ) { // check if image is already uploading if ( ( image = uploadingImages.get( uuid ) ) != null ) { log.debug( "Image is already uploading.. returning some missing blocks" ); List missing = getNMissingBlocks( image, AMOUNT ); if ( missing.isEmpty() ) { uploadDone( uuid ); return new UploadInfos( null, 0, missing ); } return new UploadInfos( image.getToken(), Globals.getSslSocketPort(), missing ); } // insert new image if ( !CRCFile.sumsAreValid( crcSums ) ) throw new UploadException( UploadError.INVALID_CRC, "CRC sums were invalid." ); filepath = Globals.getImageDir() + "/" + uuid + ".vmdk"; token = RandomString.generate( 100, false ); nBlocks = (int)Math.ceil( imageData.fileSize / Globals.blockSize ); allBlocks = new int[ nBlocks ]; // initalize array with all zeros which mean that this block is missing image = new UploadingImage( token, allBlocks, new Timestamp( System.currentTimeMillis() ), uuid, filepath ); uploadingImages.put( uuid, image ); } CRCFile crcFile; try { // try to write crc file ... crcFile = CRCFile.writeCrcFile( crcSums, Globals.getImageDir() + "/" + uuid + ".crc" ); } catch ( IOException e ) { // ... and keep it in ram if it fails crcFile = new CRCFile( crcSums ); } image.setCrcFile( crcFile ); ConnectionHandler.addConnection( token, filepath, Connection.UPLOADING ).image = image; DbImage.insert( imageData, System.currentTimeMillis(), token, nBlocks, serverSessionId, filepath ); imagesToCheck.add( uuid ); log.debug( "Returning UploadInfos. Client should goint to start the upload. " ); return new UploadInfos( token, Globals.getSslSocketPort(), getNMissingBlocks( image, AMOUNT ) ); } public static DownloadInfos getDownloadInfos( String serverSessionId, String uuid, List requestedBlocks ) { // check if server is already downloading if ( downloadingClients.containsKey( serverSessionId ) ) { DownloadingClient client = downloadingClients.get( serverSessionId ); // remove download if done if ( requestedBlocks.isEmpty() ) { downloadDone( serverSessionId, uuid ); return new DownloadInfos(); } if ( client.isDownloading( uuid ) ) { // client was downloading this image // update the requested blocks client.requestBlocks( uuid, requestedBlocks ); return new DownloadInfos( client.getToken( uuid ), Globals.getSslSocketPort() ); } // server was downloading another image and now gets a new connection for this new download String token = RandomString.generate( 100, false ); String filepath = DbImage.getImageByUUID( uuid ).imagePath; ConnectionHandler.addConnection( token, filepath, Connection.DOWNLOADING ); client.addDownload( uuid, requestedBlocks, token ); downloadingClients.put( serverSessionId, client ); return new DownloadInfos( token, Globals.getSslSocketPort() ); } // insert new client and start listener synchronized ( downloadingClients ) { String token = RandomString.generate( 100, false ); String filepath = DbImage.getImageByUUID( uuid ).imagePath; DownloadingClient client = new DownloadingClient(); client.addDownload( uuid, requestedBlocks, token ); downloadingClients.put( serverSessionId, client ); ConnectionHandler.addConnection( token, filepath, Connection.DOWNLOADING ).client = client; return new DownloadInfos( token, Globals.getSslSocketPort() ); } } private static void downloadDone( String serverSessionId, String uuid ) { synchronized ( downloadingClients ) { DownloadingClient client = downloadingClients.get( serverSessionId ); client.removeDownload( uuid ); ConnectionHandler.removeConnection( client.getToken( uuid ) ); if ( !client.hasDownloads() ) { downloadingClients.remove( serverSessionId ); } } } /** * Returns a specified number of missing blocks. * * @param imageUUID The image of which you want to get the missing blocks from * @param amount The amount of blocks that you want to get * @return The missing blocks */ private static List getNMissingBlocks( UploadingImage image, int amount ) { int size = image.getAmountOfMissingBlocks(); if ( amount > size ) amount = size; List result = new ArrayList<>( amount ); int got = 0; for ( int i = 0; i < image.getNumberOfBlocks(); i++ ) { if ( image.needsRequest( i ) ) { result.add( i ); got++; } if ( got == amount ) break; } return result; } /** * Is triggered when an upload of an image is done. * Removes image from process list, updates db entry and moves file on hard drive. * * @param uuid */ private static void uploadDone( String uuid ) { synchronized ( imagesToCheck ) { imagesToCheck.remove( uuid ); } UploadingImage image; synchronized ( uploadingImages ) { image = uploadingImages.remove( uuid ); } image.getDbImage().updateMissingBlocks( null ); // file was already downloaded in the right location by the updownloader class. // remove the connection so that it can be used by a new client ConnectionHandler.removeConnection( image.getToken() ); } public static List getImagesToCheck() { List result = new LinkedList<>(); Iterator iter = imagesToCheck.iterator(); while ( iter.hasNext() ) { result.add( uploadingImages.get( iter.next() ) ); } return result; } public static List getRequestedBlocks( String token ) { // for the downloader return null; } /** * Checks pending uploads in database and adds them to process list again. */ static { List list = DbImage.getUploadingImages(); for ( DbImage image : list ) { String token = image.token; ConnectionHandler.addConnection( token, image.imagePath, Connection.UPLOADING ); UploadingImage infos = new UploadingImage( token, image.blockStatus, image.timestamp, image.uuid, image.imagePath ); CRCFile crcFile = new CRCFile( Globals.getImageDir() + "/" + image.uuid + ".crc" ); try { if ( !crcFile.isValid() ) continue; } catch ( IOException e ) { continue; } infos.setCrcFile( crcFile ); uploadingImages.put( image.uuid, infos ); } log.info( "Added " + list.size() + " pending upload(s) to process list again." ); } public static void init() { } }