public void Handle(FileChunkResponse message)
        {
            DownloadInfo downloadInfo;

            lock (this.downloadInfos)
            {
                if (!this.downloadInfos.TryGetValue(message.DownloadId, out downloadInfo))
                    // Unknown, ignore
                    return;

                string chunkFile = Path.Combine(downloadInfo.TempFolder, string.Format("chunk_{0}.bin", message.ChunkStart));
                Directory.CreateDirectory(Path.GetDirectoryName(chunkFile));

                File.WriteAllBytes(chunkFile, message.Chunk);

                downloadInfo.ReceivedChunks++;
                downloadInfo.LastReceive = DateTime.Now;

                // Request next chunk
                if (downloadInfo.RequestedChunks < downloadInfo.Chunks)
                {
                    this.log.Trace("Requesting chunk {0} of file {1}", downloadInfo.RequestedChunks, downloadInfo.FileName);

                    SendMessage(new FileChunkRequest
                    {
                        DownloadId = message.DownloadId,
                        FileName = downloadInfo.FileName,
                        Type = downloadInfo.FileType,
                        ChunkSize = ChunkSize,
                        ChunkStart = downloadInfo.RequestedChunks * ChunkSize
                    });

                    downloadInfo.RequestedChunks++;
                }
                else
                {
                    if (downloadInfo.ReceivedChunks >= downloadInfo.Chunks)
                    {
                        // Check if we have everything

                        long chunkStart = 0;
                        long fileSize = 0;
                        string assembleFile = Path.Combine(Path.GetDirectoryName(downloadInfo.TempFolder), downloadInfo.FileName);

                        try
                        {
                            using (var fsOutput = File.Create(assembleFile))
                            {
                                while (fileSize != downloadInfo.FileSize)
                                {
                                    chunkFile = Path.Combine(downloadInfo.TempFolder, string.Format("chunk_{0}.bin", chunkStart));

                                    long expectedChunkSize = Math.Min(downloadInfo.FileSize - fileSize, ChunkSize);

                                    if (!File.Exists(chunkFile) || new FileInfo(chunkFile).Length != expectedChunkSize)
                                    {
                                        this.log.Trace("Re-requesting chunk {0} of file {1}", chunkStart / ChunkSize, downloadInfo.FileName);

                                        // Re-request
                                        SendMessage(new FileChunkRequest
                                        {
                                            DownloadId = message.DownloadId,
                                            FileName = downloadInfo.FileName,
                                            Type = downloadInfo.FileType,
                                            ChunkSize = ChunkSize,
                                            ChunkStart = chunkStart
                                        });

                                        // Not done yet
                                        return;
                                    }

                                    var fi = new FileInfo(chunkFile);
                                    chunkStart += fi.Length;
                                    fileSize += fi.Length;

                                    using (var fsInput = File.OpenRead(chunkFile))
                                        fsInput.CopyTo(fsOutput);
                                }

                                fsOutput.Flush();
                            }

                            // Delete all chunks
                            downloadInfo.Cleanup();

                            // Check signature
                            byte[] sign = CalculateSignatureSha1(assembleFile);
                            if (!sign.SequenceEqual(downloadInfo.SignatureSha1))
                            {
                                // Invalid signature, request the file again
                                downloadInfo.Restart();

                                SendMessage(new FileRequest
                                {
                                    DownloadId = downloadInfo.Id,
                                    Type = downloadInfo.FileType,
                                    FileName = downloadInfo.FileName
                                });

                                return;
                            }

                            // Match, move to final directory
                            File.Move(assembleFile, downloadInfo.FinalFilePath);
                            assembleFile = null;
                            this.downloadInfos.Remove(downloadInfo.Id);

                            if (downloadInfo.TriggerMessage != null)
                            {
                                // Invoke
                                var method = typeof(MonoExpanderClient).GetMethods()
                                    .Where(x => x.Name == "Handle" && x.GetParameters().Any(p => p.ParameterType == downloadInfo.TriggerMessage.GetType()))
                                    .ToList();

                                method.SingleOrDefault()?.Invoke(this, new object[] { downloadInfo.TriggerMessage });
                            }
                        }
                        finally
                        {
                            // Delete the temporary assemble file
                            if (!string.IsNullOrEmpty(assembleFile))
                                File.Delete(assembleFile);
                        }
                    }
                }
            }
        }