summaryrefslogtreecommitdiffstats
path: root/src/main/java/org/openslx/imagemaster/serverconnection/ImageProcessor.java
blob: 94d55edcd23b5260da1b75550618779833f63d5a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
package org.openslx.imagemaster.serverconnection;

import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.apache.log4j.Logger;
import org.openslx.imagemaster.Globals;
import org.openslx.imagemaster.db.DbImage;
import org.openslx.imagemaster.thrift.iface.DownloadInfos;
import org.openslx.imagemaster.thrift.iface.ImageData;
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 HashMap<String, UploadingImageInfos> uploadingImages = new HashMap<>();
	
	/**
	 * The UUIDs of the images that need to be checked by the crc checker.
	 */
	private static List<String> imagesToCheck = new LinkedList<>();

	/**
	 * The downloading clients.
	 * Key: serverSessionId
	 * Value: downloadingClientInfos
	 */
	private static HashMap<String, DownloadingClientInfos> 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
	 */
	public static UploadInfos getUploadInfos( String serverSessionId, ImageData imageData )
	{
		// check image data
		// TODO: do security checks

		String uuid = imageData.uuid;

		// check if image is already uploading TODO: what if two clients call this at the same time? -> Good question.. (Thought about not sending the last requested. But then the upload will never finish...)
		if ( uploadingImages.containsKey( uuid ) ) {
			List<Integer> missing = getMissingBlocks( uuid, AMOUNT );
			if ( missing.isEmpty() ) {
				uploadDone( uuid );
				return new UploadInfos( null, 0, missing );
			}
			uploadingImages.get( uuid ).addNotCheckedBlocks( missing );
			UploadingImageInfos image = uploadingImages.get( uuid );
			return new UploadInfos( image.getToken(), image.getPort(), missing );
		}

		// insert new image and start listener
		synchronized ( uploadingImages ) {
			int nBlocks = (int)Math.ceil( imageData.fileSize / Globals.blockSize );
			List<Integer> allBlocks = new LinkedList<>();
			for ( int i = 0; i < nBlocks; i++ ) {			// fill empty list with all block numbers
				allBlocks.add( i );
			}
			String token = RandomString.generate( 100, false );
			String filepath = Globals.getImageDir() + "/" + uuid + ".vmdk";
			int port = ConnectionHandler.addConnection( token, filepath, ConnectionData.UPLOADING );
			// TODO: proper synchronization, interface is multi threaded.
			// should synchronize operations on the map (use concurrent map) and then synchronize on the uploading image
			// when handing the missing blocks etc...
			uploadingImages.put( uuid, new UploadingImageInfos( token, port, allBlocks, serverSessionId, new Timestamp( System.currentTimeMillis() ), uuid, filepath, "CRCFILE" ) );
			DbImage.insert( imageData, System.currentTimeMillis(), token, allBlocks, serverSessionId, filepath );
			imagesToCheck.add( uuid );
			
			List<Integer> missing = getMissingBlocks( uuid, AMOUNT );
			if ( missing.isEmpty() ) {
				// TODO: if this is empty, check if there are pending blocks and if so, request them again
				uploadDone( uuid );
			}
			uploadingImages.get( uuid ).addNotCheckedBlocks( missing );
			return new UploadInfos( token, port, missing );
		}
	}

	public static DownloadInfos getDownloadInfos( String serverSessionId, String uuid, List<Integer> requestedBlocks )
	{
		// check if server is already downloading
		if ( downloadingClients.containsKey( serverSessionId ) ) {
			DownloadingClientInfos 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 ), client.getPort( uuid ) );
			}

			// 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;
			int port = ConnectionHandler.addConnection( token, filepath, ConnectionData.DOWNLOADING );

			client.addDownload( uuid, port, requestedBlocks, token );
			downloadingClients.put( serverSessionId, client );
			return new DownloadInfos( token, port );
		}

		// insert new client and start listener
		synchronized ( downloadingClients ) {
			DownloadingClientInfos client = new DownloadingClientInfos();
			String token = RandomString.generate( 100, false );
			String filepath = DbImage.getImageByUUID( uuid ).imagePath;
			int port = ConnectionHandler.addConnection( token, filepath, ConnectionData.DOWNLOADING );

			client.addDownload( uuid, port, requestedBlocks, token );
			downloadingClients.put( serverSessionId, client );
			return new DownloadInfos( token, port );
		}
	}

	private static void downloadDone( String serverSessionId, String uuid )
	{
		synchronized ( downloadingClients ) {
			DownloadingClientInfos 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<Integer> getMissingBlocks( String imageUUID, int amount )
	{
		UploadingImageInfos image = uploadingImages.get( imageUUID );
		List<Integer> list = image.getMissingBlocks();
		List<Integer> result = new LinkedList<>();

		if ( amount > list.size() )
			amount = list.size();

		for ( int i = 0; i < amount; i++ ) {
			result.add( list.get( i ) );
		}

		synchronized ( image ) {
			image.addNotCheckedBlocks( result );
		}

		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 );
		}
		UploadingImageInfos 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<UploadingImageInfos> getImagesToCheck()
	{
		List<UploadingImageInfos> result = new LinkedList<>();
		Iterator<String> iter = imagesToCheck.iterator();
		while ( iter.hasNext() ) {
			result.add( uploadingImages.get( iter.next() ) );
		}
		
		return result;
	}

	/**
	 * Checks pending uploads in database and adds them to process list again.
	 */
	static
	{
		List<DbImage> list = DbImage.getUploadingImages();
		for ( DbImage image : list ) {
			String token = image.token;
			int port = ConnectionHandler.addConnection( token, image.imagePath, ConnectionData.UPLOADING );
			UploadingImageInfos infos = new UploadingImageInfos( token, port, image.missingBlocks, image.serverSessionId, image.timestamp, image.uuid, image.imagePath, "CRCFILE" );
			uploadingImages.put( image.uuid, infos );
		}
		log.info( "Added " + list.size() + " pending upload(s) to process list again." );
	}

	public static void init()
	{
	}
}