All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.bertramlabs.plugins.karman.differential.DifferentialCloudFile.groovy Maven / Gradle / Ivy

package com.bertramlabs.plugins.karman.differential

import com.bertramlabs.plugins.karman.CloudFile
import com.bertramlabs.plugins.karman.CloudFileACL
import com.bertramlabs.plugins.karman.CloudFileInterface
import com.bertramlabs.plugins.karman.StorageProvider
import com.bertramlabs.plugins.karman.util.Mimetypes
import groovy.transform.CompileStatic
import groovy.util.logging.Commons
import org.tukaani.xz.LZMA2Options
import org.tukaani.xz.XZ
import org.tukaani.xz.XZOutputStream

import java.util.zip.GZIPOutputStream


@Commons
public class DifferentialCloudFile extends CloudFile {
	CloudFileInterface sourceFile
	DifferentialDirectory parent
	InputStream rawSourceStream = null
	private Long internalContentLength;
	private boolean internalContentLengthSet = false;
	private DifferentialCloudFile linkedFile = null

	DifferentialCloudFile(String name, DifferentialDirectory parent, CloudFileInterface sourceFile) {
		this.name = name
		this.provider = parent.provider
		this.parent = parent
		this.sourceFile = sourceFile
	}

	@Override
	@CompileStatic
	InputStream getInputStream() {
		CloudFile manifestFile = parent.sourceDirectory[sourceFile.name + "/karman.diff"]
		if(manifestFile.exists()) {
			return new DifferentialInputStream(sourceFile, manifestFile.getInputStream())
		} else {
			return sourceFile.getInputStream()
		}
	}

	@Override
	Boolean isDirectory() {
		CloudFile manifestFile = parent.sourceDirectory[sourceFile.name + "/karman.diff"]
		if(manifestFile.exists()) {
			return false
		}
		return sourceFile.isDirectory()
	}

	@Override
	void setInputStream(InputStream is) {
		rawSourceStream = is
		//differential store...we gotta do something special here
	}

	@Override
	OutputStream getOutputStream() {
		return null
	}

	@Override
	String getText(String encoding) {
		return getInputStream().getText(encoding)
	}

	@Override
	String getText() {
		return getInputStream().text
	}


	@Override
	byte[] getBytes() {
		return getInputStream().bytes
	}

	@Override
	void setText(String text) {
		setInputStream(new ByteArrayInputStream(text.bytes))
	}

	@Override
	void setBytes(bytes) {
		setInputStream(new ByteArrayInputStream(bytes))
	}

	@Override
	Long getContentLength() {
		CloudFile manifestFile = parent[sourceFile.name + "/karman.diff"]
		if(manifestFile.exists()) {
			DifferentialInputStream is = null
			try {
				is = new DifferentialInputStream(sourceFile, manifestFile.getInputStream())
				if(is.manifestData.fileSize != null) {
					return is.manifestData.fileSize
				} else {
					long contentLength = 0
					ManifestData.BlockData currentBlock = is.getNextBlockData()
					while(currentBlock != null) {
						contentLength += currentBlock.blockSize
						currentBlock = is.getNextBlockData()
					}
					return contentLength
				}
			} finally {
				try {
					if(is != null) {
						is.close()
					}
				} catch(ignore) {
					//ignore
				}
			}
		} else {
			return sourceFile.getContentLength()
		}
	}

	Long getOnDeviceContentLength() {
		CloudFile manifestFile = parent.sourceDirectory[sourceFile.name + "/karman.diff"]

		if(manifestFile.exists()) {
			DifferentialInputStream is = null
			try {
				long contentLength = manifestFile.contentLength
				is = new DifferentialInputStream(sourceFile, manifestFile.getInputStream())
				ManifestData.BlockData currentBlock = is.getNextBlockData()
				while(currentBlock != null) {
//					contentLength += currentBlock.blockSize //this is the uncompressed size and is not accurate
					if(currentBlock.fileIndex == 0 && !currentBlock.zeroFilled) {
						String blockFilePath = ManifestData.BlockData.getBlockPath(sourceFile, currentBlock.block, 0, is.manifestData);
						CloudFile blockFile = parent.sourceDirectory[blockFilePath]
						contentLength += blockFile.contentLength
					}
					currentBlock = is.getNextBlockData()
				}
				return contentLength
			} finally {
				try {
					if(is != null) {
						is.close()
					}

				} catch(ignore) {
					//ignore
				}
			}



		} else {
			return sourceFile.getContentLength()
		}
	}

	@Override
	String getContentType() {
		return Mimetypes.instance.getMimetype(name)
	}

	@Override
	Date getDateModified() {
		CloudFile manifestFile = parent[sourceFile.name + "/karman.diff"]
		if(manifestFile.exists()) {
			return manifestFile.getDateModified()
		} else {
			return sourceFile.getDateModified()
		}
	}

	@Override
	void setContentType(String contentType) {
		// Content Type is not implemented in most file system stores
	}

	@Override
	void setContentLength(Long length) {
		internalContentLength = length
		internalContentLengthSet = true
		CloudFile manifestFile = parent[sourceFile.name + "/karman.diff"]
		if(!manifestFile.exists()) {
			sourceFile.setContentLength(length)
		}

	}

	@Override
	Boolean exists() {
		try {
			CloudFile manifestFile = parent.sourceDirectory[sourceFile.name + "/karman.diff"]
			if(manifestFile.exists()) {
				return true
			}
		} catch(Exception e) {
			//ignore
		}

		return sourceFile.exists()
	}

	@Override
	@CompileStatic
	def save(acl) {
		CloudFileInterface manifestFile = parent.sourceDirectory[sourceFile.name + "/karman.diff"]
		DifferentialInputStream diffInput = null
		if(!sourceFile.exists() || manifestFile.exists()) {
			try {
				if(manifestFile.exists())
					manifestFile.delete()
				//todo: cleanup all sub files since we are overwriting the file

				ManifestData manifestData = new ManifestData()
				manifestData.fileSize = internalContentLength
				manifestData.fileName = sourceFile.name
				manifestData.blockSize = ((DifferentialStorageProvider) provider).blockSize
				if(linkedFile != null) {
//				log.info("Linked File Detected: looking for manifest:" + linkedFile.name + "/karman.diff")
					CloudFileInterface linkedManifest = parent.sourceDirectory[linkedFile.name + "/karman.diff"]
					if(linkedManifest.exists()) {
//					log.info("Linked Manifest Found")
						diffInput = new DifferentialInputStream(linkedFile, linkedManifest.getInputStream())

						manifestData.sourceFiles = diffInput.manifestData.sourceFiles
						if(manifestData.sourceFiles == null) {
							manifestData.sourceFiles = []
						}
						manifestData.sourceFiles = [linkedFile.name] + manifestData.sourceFiles
					}

				}
				String headerString = manifestData.getHeader()
				Long calculateDiffSize = 0
				calculateDiffSize += headerString.getBytes().size()
				if(internalContentLength) {
					calculateDiffSize += ((long)((long)internalContentLength/(long)manifestData.blockSize)*44l)
					if(((long)internalContentLength%(long)manifestData.blockSize) > 0) {
						calculateDiffSize += 44l
					}
				}
				PipedOutputStream pos = new PipedOutputStream()
				PipedInputStream pis = new PipedInputStream(pos)
				def saveThread = Thread.start {
//				if(internalContentLength) {
//					manifestFile.setContentLength(calculateDiffSize)
//				}
					manifestFile.setInputStream(pis)
					manifestFile.save()
				}
				pos.write(headerString.getBytes())

				BlockDigestStream dataStream = new BlockDigestStream(rawSourceStream, pos, manifestData.blockSize, diffInput)
				byte[] buffer = new byte[manifestData.blockSize]
				int bytesRead = 0
				long blockNumber = 0


				while((bytesRead = dataStream.read(buffer)) != -1) {
					//confirm the buffer is not full of zero byte arrays

					boolean allZero = true
					for(byte b : buffer) {
						if(b != 0) {
							allZero = false
							break
						}
					}

					if(!allZero && dataStream.lastBlockDifferent) {

						ByteArrayOutputStream compressedBuffer = new ByteArrayOutputStream()
						GZIPOutputStream xz = new GZIPOutputStream(compressedBuffer)
						//XZOutputStream xz = new XZOutputStream(compressedBuffer, new LZMA2Options(0), XZ.CHECK_NONE)
						xz.write(buffer, 0, bytesRead)
						xz.finish()


						String blockFilePath = ManifestData.BlockData.getBlockPath(sourceFile, blockNumber, 0, manifestData);
						int attempts=0
						while(attempts < 5) {
							try {
								CloudFileInterface blockFile = parent.sourceDirectory[blockFilePath]
								byte[] compressedBufferArray = compressedBuffer.toByteArray()
								blockFile.setContentLength(compressedBufferArray.size())
								blockFile.setInputStream(new ByteArrayInputStream(compressedBufferArray));
								blockFile.save()
								break
							} catch(Exception e) {
								attempts++
								sleep(5000l*attempts + 5000l)

								if(attempts == 5) {
									log.error("Error saving block file...Max Attempts Reached...",e)
									throw new Exception("Error saving block file...Max Attempts Reached...",e)
								} else {
									log.error("Error saving block file...sleeping and trying again shortly...",e)
								}
							}
						}


					}
					blockNumber++
				}
				pos.flush()
				pos.close()
				saveThread.join()
			} finally {
				if(diffInput != null) {
					try {
						diffInput.close()
					} catch(Exception ignore) {

					}

				}
			}


		} else {
			sourceFile.save(acl)
		}
	}

	@Override
	def delete() {
		CloudFile manifestFile = parent.sourceDirectory[sourceFile.name + "/karman.diff"]
		if(manifestFile.exists()) {
			parent.sourceDirectory.listFiles(prefix: sourceFile.name + "/", delimiter: "/")?.each { CloudFileInterface file ->
				file.delete()
			}
			if(sourceFile.exists()) {
				try {
					sourceFile.delete()
				} catch(Exception ex) {
					//trying to delete but could have recursively cleaned from the previous loop
					log.debug("Unable to delete root directory of differential file...this may have already been cleaned up from recursion...ignoring",ex)
				}

			}

		} else {
			if(sourceFile.exists()) {
				sourceFile.delete()
			}
		}

	}

	@Override
	void setMetaAttribute(key, value) {
		CloudFile manifestFile = parent.sourceDirectory[sourceFile.name + "/karman.diff"]
		if(manifestFile.exists()) {
			manifestFile.setMetaAttribute(key, value)
		} else {
			sourceFile.setMetaAttribute(key, value)
		}
	}

	@Override
	String getMetaAttribute(key) {
		CloudFile manifestFile = parent.sourceDirectory[sourceFile.name + "/karman.diff"]
		if(manifestFile.exists()) {
			return manifestFile.getMetaAttribute(key)
		} else {
			return sourceFile.getMetaAttribute(key)
		}
	}

	@Override
	Map getMetaAttributes() {
		CloudFile manifestFile = parent.sourceDirectory[sourceFile.name + "/karman.diff"]
		if(manifestFile.exists()) {
			return manifestFile.getMetaAttributes()
		} else {
			return sourceFile.getMetaAttributes()
		}
	}

	@Override
	void removeMetaAttribute(key) {
		CloudFile manifestFile = parent.sourceDirectory[sourceFile.name + "/karman.diff"]
		if(manifestFile.exists()) {
			manifestFile.removeMetaAttribute(key)
		} else {
			sourceFile.removeMetaAttribute(key)
		}
	}

	void setLinkedFile(DifferentialCloudFile linkedFile) {
		this.linkedFile = linkedFile
	}

	void flatten(List children = null) {
		CloudFile manifestFile = parent.sourceDirectory[sourceFile.name + "/karman.diff"]
		List sourceFilesToUnlink = []
		//only do this if it is indeed a differential file
		if(manifestFile.exists()) {
			DifferentialInputStream unflattenedStream = null
			PipedOutputStream pos
			PipedInputStream pis
			try {
					CloudFile originalManifest = parent.sourceDirectory[sourceFile.name + "/karman.diff2"]
					originalManifest.setInputStream(manifestFile.getInputStream())
					originalManifest.save()
					unflattenedStream = new DifferentialInputStream(sourceFile, originalManifest.getInputStream())
					pos = new PipedOutputStream()
					pis = new PipedInputStream(pos)
					Thread.start {
						manifestFile.setInputStream(pis)
						manifestFile.save()
					}

					ManifestData manifestData = new ManifestData()
					manifestData.fileSize = unflattenedStream.manifestData.fileSize
					manifestData.fileName = unflattenedStream.manifestData.fileName
					manifestData.blockSize = unflattenedStream.manifestData.blockSize
					sourceFilesToUnlink = unflattenedStream.manifestData.sourceFiles
					manifestData.sourceFiles = null //clear source files

					String headerString = manifestData.getHeader()
					pos.write(headerString.getBytes())

					ManifestData.BlockData currentBlock = unflattenedStream.getNextBlockData()
					while(currentBlock != null) {
						if(currentBlock.fileIndex != 0) {
							if(!currentBlock.zeroFilled) {
								//block file index is not 0 so we need to grab it and pull it in
								String originalBlockFilePath = ManifestData.BlockData.getBlockPath(sourceFile, currentBlock.block, currentBlock.fileIndex, unflattenedStream.manifestData)
								String newBlockFilePath = ManifestData.BlockData.getBlockPath(sourceFile, currentBlock.block, 0, manifestData)

								CloudFileInterface blockFile = parent.sourceDirectory[originalBlockFilePath]
								CloudFileInterface destBlockFile = parent.sourceDirectory[newBlockFilePath]
								destBlockFile.setInputStream(blockFile.getInputStream())
								destBlockFile.save()
							}
							currentBlock.fileIndex = 0
						}
						pos.write(currentBlock.generateBytes())
						currentBlock = unflattenedStream.getNextBlockData()
					}
					pos.flush()
					pos.close()
					pos = null //clear it for finally block unless exception occurs
					originalManifest.delete()

					//we gotta correct any children from this file based on the child list passed in
					if(children) {
						for(DifferentialCloudFile childrenFile in children) {
							DifferentialInputStream unflattenedChildStream = null
							PipedOutputStream childPos = null
							PipedInputStream childPis = null
							try {
								CloudFile childManifestFile = parent.sourceDirectory[childrenFile.name + "/karman.diff"]
								if(childManifestFile.exists()) {
									CloudFile originalChildManifest = parent.sourceDirectory[childrenFile.name + "/karman.diff2"]
									originalChildManifest.setInputStream(childManifestFile.getInputStream())
									originalChildManifest.save()
									unflattenedChildStream = new DifferentialInputStream(childrenFile.sourceFile, originalChildManifest.getInputStream())
									childPos = new PipedOutputStream()
									childPis = new PipedInputStream(childPos)
									Thread.start {
										childManifestFile.setInputStream(childPis)
										childManifestFile.save()
									}

									ManifestData childManifestData = new ManifestData()
									childManifestData.fileSize = unflattenedChildStream.manifestData.fileSize
									childManifestData.fileName = unflattenedChildStream.manifestData.fileName
									childManifestData.blockSize = unflattenedChildStream.manifestData.blockSize
									childManifestData.sourceFiles = unflattenedChildStream.manifestData.sourceFiles
									def fileIndicesToUnlink = []

									if(sourceFilesToUnlink) {
										sourceFilesToUnlink.each { sourceFileToUnlink ->
											Integer idx = childManifestData.sourceFiles?.indexOf(sourceFileToUnlink)
											if(idx != null && idx >= 0) {
												fileIndicesToUnlink << idx + 1
											}
										}
										childManifestData.sourceFiles?.removeAll(sourceFilesToUnlink)

									}
									int targetIndex = childManifestData.sourceFiles.indexOf(sourceFile.name) + 1
									String childHeaderString = childManifestData.getHeader()
									childPos.write(childHeaderString.getBytes())
									ManifestData.BlockData childCurrentBlock = unflattenedChildStream.getNextBlockData()
									while(childCurrentBlock != null) {
										if(fileIndicesToUnlink.contains(childCurrentBlock.fileIndex)) {
											childCurrentBlock.fileIndex = targetIndex
										}
										childPos.write(childCurrentBlock.generateBytes())
										childCurrentBlock = unflattenedChildStream.getNextBlockData()
									}


									originalChildManifest.delete()

								}
							} finally {
								if(childPos != null) {
									try {
										childPos.flush()
										childPos.close()
									} catch(ignore) {

									}
								}
								if(unflattenedStream != null) {
									try {
										unflattenedChildStream.close()
									} catch(ignore) {

									}
								}
							}

						}
					}

			} finally {
					try {
						unflattenedStream.close()
					} catch(ignore) {

					}
					try {
						if(pos != null) {
							pos.flush()
							pos.close()
						}
					} catch(ignore) {

					}
				}
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy