HomeForumSourceResearchGuide
Sign in to contribute to source. how it works
Component composition.ObjectWriter by barry
expand copy to clipboardexpand
//magic header of a Dana object matching the below format
const byte danaMagic[] = new int[](0x44, 0x41, 0x4E, 0x41, 0x0, 0x1)

//a Dana object header
data SourceHeader{
	//basic identification; this part is always the same total size
	byte magic[6]
	byte checksum[4]
	byte objectType[2]
	byte hostType[2]
	byte vpuType[2]
	//the below fields are each the size of the target host machine's native address width
	//machine code
	int textOffset
	int textSize
	//local relocations; a list of addresses to add the start address of this object to
	int lrTableOffset
	int lrTableSize
	int lrTableCount
	//info sections (if one of these is hardlinks then this object file is not fit to be loaded)
	int infoSectionsOffset
	int infoSectionsSize
	int infoSectionsCount
	}

//an info section header
data DanaInfoSection{
	byte sectionType[4] //type code identifying the section's purpose (0.x.x.x, D.N.x.x, F.R.x.x are reserved)
	byte contentType[4] //type code identifying the kind of content (json, xml, jpeg, etc.)
	int size //size of the section's content, excluding this header
	}

component provides ObjectWriter requires io.File, io.FileSystem fileSystem, data.StringUtil stringUtil, data.IntUtil iu, io.Output out, System system {
	
	ObjectInfo info
	int lastInfoIndex
	int infoSectionsOffset
	
	ObjectWriter:ObjectWriter(char path[])
		{
		File fd = new File(path, File.READ)
		
		if (fd == null)
			throw new Exception("File not found")
		
		SourceHeader hdr = new SourceHeader()
		byte serialHDR[] = dana.serial(hdr)
		
		byte buf[]
		
		if ((buf = fd.read(serialHDR.arrayLength)).arrayLength != serialHDR.arrayLength)
			{
			throw new Exception("Error: File is not a Dana object")
			}
		
		serialHDR =[] buf
		
		if (hdr.magic != danaMagic)
			{
			throw new Exception("Error: File is not a Dana object")
			}
		
		if (hdr.hostType != system.getHostType())
			{
			throw new Exception("Error: File was compiled for a different architecture")
			}
		
		if (hdr.vpuType != system.getVMType())
			{
			throw new Exception("Error: File was compiled for a different VPU version")
			}
		
		info = new ObjectInfo(hdr.textSize, hdr.infoSectionsCount, hdr.lrTableCount)
		infoSectionsOffset = hdr.infoSectionsOffset
		
		fd.close()
		
		objectPath = path
		}
	
	ObjectInfo ObjectWriter:getInfo()
		{
		return info
		}
	
	InfoSection[] ObjectWriter:getInfoSections()
		{
		InfoSection result[]
		
		File fd = new File(objectPath, File.READ)
		
		if (fd == null)
			{
			throw new Exception("Object file $objectPath is no longer readable")
			}
		
		fd.setPos(infoSectionsOffset)
		for (int i = 0; i < info.infoSectionCount; i++)
			{
			byte serialData[]
			
			DanaInfoSection is = new DanaInfoSection()
			serialData = dana.serial(is)
			serialData =[] fd.read(serialData.arrayLength)
			
			fd.read(is.size)
			
			result = new InfoSection[](result, new InfoSection(is.sectionType, is.contentType))
			}
		
		fd.close()
		
		return result
		}
	
	InfoSection ObjectWriter:getInfoSection(char type[], char cType[])
		{
		File fd = new File(objectPath, File.READ)
		
		if (fd == null)
			{
			throw new Exception("Object file $objectPath is no longer readable")
			}
		
		fd.setPos(infoSectionsOffset)
		for (int i = 0; i < info.infoSectionCount; i++)
			{
			byte serialData[]
			
			DanaInfoSection is = new DanaInfoSection()
			is.size = 0
			serialData = dana.serial(is)
			serialData =[] fd.read(serialData.arrayLength)
			
			byte contents[] = fd.read(is.size)
			
			if (is.sectionType == type && (cType == null || is.contentType == cType))
				{
				InfoSection section = new InfoSection(is.sectionType, is.contentType, contents)
				fd.close()
				return section
				}
			}
		
		fd.close()
		
		return null
		}
	
	char[] getDirectoryOf(char path[])
		{
		//strip off what's after the last trailing "/", if anything
		String parts[] = clone stringUtil.explode(path, "/\\")
		
		if (parts.arrayLength > 1)
			{
			parts[parts.arrayLength-1] = null
			char result[] = new char[](stringUtil.implode(parts, "/"), "/")
			return result
			}
		
		return ""
		}
	
	char[] getTmpFile(char relativePath[])
		{
		if (fileSystem.getInfo(relativePath).type == FileInfo.TYPE_FILE)
			relativePath = getDirectoryOf(relativePath)
		
		int tn = 1
		char path[] = new char[](relativePath, iu.makeString(tn), ".tmp")
		
		while (fileSystem.exists(path))
			{
			tn ++
			path = new char[](relativePath, iu.makeString(tn), ".tmp")
			}
		
		return path
		}
	
	void ObjectWriter:addInfoSection(InfoSection newSection)
		{
		DanaInfoSection dis = new DanaInfoSection()
		
		//find an unused temporary filename
		char tmp[] = getTmpFile(objectPath)
		
		//write the existing file with an updated header, and the new infosection at the end
		File output = new File(tmp, File.WRITE)
		File input = new File(objectPath, File.WRITE)
		
		// - write an updated header (editing the count and size of infosections)
		SourceHeader hdr = new SourceHeader()
		byte serialHDR[] = dana.serial(hdr)
		
		byte buf[]
		
		if ((buf = input.read(serialHDR.arrayLength)).arrayLength != serialHDR.arrayLength)
			{
			throw new Exception("Error: File is not a Dana object")
			}
		
		serialHDR =[] buf
		
		// - we'll be using the originals of these values shortly, so cache them
		int iso = hdr.infoSectionsOffset
		int iss = hdr.infoSectionsSize
		
		hdr.infoSectionsCount ++
		hdr.infoSectionsSize += newSection.content.arrayLength + dana.serial(dis).arrayLength
		
		info.infoSectionCount ++
		
		output.write(serialHDR)
		
		// - write everything up to the last infosection
		buf = input.read((iso + iss) - serialHDR.arrayLength)
		output.write(buf)
		
		// - write the new infosection
		dis.sectionType =[] newSection.sectionType
		dis.contentType =[] newSection.contentType
		dis.size = newSection.content.arrayLength
		
		buf = dana.serial(dis)
		
		output.write(buf)
		
		output.write(newSection.content)
		
		// - write anything else from the input file
		buf = input.read(input.getSize() - input.getPos())
		output.write(buf)
		
		output.close()
		input.close()
		
		//delete the original file and rename the temporary one to it
		fileSystem.delete(objectPath)
		fileSystem.move(tmp, objectPath)
		}
	
	void ObjectWriter:deleteInfoSection(char sectionType[], char cType[])
		{
		InfoSection is = getInfoSection(sectionType, cType)
		
		if (is == null)
			return
		
		//find an unused temporary filename
		char tmp[] = getTmpFile(objectPath)
		
		//write the existing file with an updated header, and exclude the selected infosection from that
		File output = new File(tmp, File.WRITE)
		File input = new File(objectPath, File.WRITE)
		
		// - write an updated header (editing the count and size of infosections)
		SourceHeader hdr = new SourceHeader()
		byte serialHDR[] = dana.serial(hdr)
		
		byte buf[]
		
		if ((buf = input.read(serialHDR.arrayLength)).arrayLength != serialHDR.arrayLength)
			{
			throw new Exception("Error: File is not a Dana object")
			}
		
		serialHDR =[] buf
		
		// - we'll be using the originals of these values shortly, so cache them
		int iso = hdr.infoSectionsOffset
		int isc = hdr.infoSectionsCount
		int iss = hdr.infoSectionsSize
		
		hdr.infoSectionsCount --
		hdr.infoSectionsSize -= is.content.arrayLength
		
		info.infoSectionCount --
		
		output.write(serialHDR)
		
		// - write everything up to the first infosection
		buf = input.read(iso - serialHDR.arrayLength)
		output.write(buf)
		
		// - write each infosection in turn, unless it's the one to exclude
		bool deleted = false
		for (int i = 0; i < isc; i++)
			{
			byte serialData[]
			
			DanaInfoSection dis = new DanaInfoSection()
			serialData = dana.serial(dis)
			serialData =[] input.read(serialData.arrayLength)
			
			byte contents[] = input.read(dis.size)
			
			if (dis.sectionType != sectionType || (cType != null && dis.contentType != cType) || deleted)
				{
				output.write(serialData)
				output.write(contents)
				}
				else
				{
				deleted = true
				}
			}
		
		// - write anything else from the input file
		buf = input.read(input.getSize() - input.getPos())
		output.write(buf)
		
		output.close()
		input.close()
		
		//delete the original file and rename the temporary one to it
		fileSystem.delete(objectPath)
		fileSystem.move(tmp, objectPath)
		}
	
	}
Revision history
To propose a new revision to this entity, use dana source put -uc your/new/version.dn -n composition.ObjectWriter -m "reason for update" -u yourUsername
Version 1 (this version) by barry
Notes for this version: Standard Library Initialisation