/*******************************************************************************************************
<SCRIPTFILE>
Created: 2012-07-09
Last Updated: 2012-07-09
Version: 1

Author: Klaas Nienhuis, mail@klaasnienhuis.nl, www.klaasnienhuis.nl
Version: 3ds max 2012

Description:
	Zipping struct. Can be used to zip files and folders
Usage:
	Evaluate the struct. Use the methods at the end to initialize the struct and run it single- or multithreaded

Features:
	multithreading
Wishlist:
Changelog:
	date: change
</SCRIPTFILE>
*******************************************************************************************************/

struct zip
(
	--http://forums.cgsociety.org/showthread.php?t=800618
	--zip-struct based on code by Rene Baca, MerlinEl
	
	
	all_dirs = #(),
	zipThread = dotnetobject "CSharpUtilities.SynchronizingBackgroundWorker",
	
	sourceFolderPath,
	targetFolderPath,
	zipFileName = "zippy",
	useSubdirs = true,
	deleteOld = false,
	
	function fn_getDirTree dir =
	(
		/*<FUNCTION>
		Description:
			Recursively gets all directories from a root directory. Doesn't include the root
		Arguments:
			<string> dir: the root directory to recursively get all child-directories from
		Return:
			Stores an array of filepaths in an external array: all_dirs
		</FUNCTION>*/
		
		all_dirs += curr_dirs = getDirectories (dir+"*")
		for i in curr_dirs do fn_getDirTree i
	),
	
	function fn_getAllFiles arrDir =
	(
		/*<FUNCTION>
		Description:
			Gets all files from an array of directories.
		Arguments:
			<array> arrDir: An array of directories
		Return:
			<array> an array of filepaths
		</FUNCTION>*/
		
		local arrFile = #()
		for theDir in arrDir do arrFile += getFiles (theDir + "*")
		arrFile
	),
	
	function fn_deleteOldFiles arrFile =
	(
		/*<FUNCTION>
		Description:
			Delete files.
		Arguments:
			<array> arrfile: an array of files which is to be deleted
		Return:
			<bool> Function returns (anything?).
		</FUNCTION>*/
		
		for i in arrFile do try deleteFile i catch (format "The file: %\tcannot be deleted.\n" i)
	),
	
	function fn_collectFiles sourceFolderPath: useSubdirs: =
	(
		/*<FUNCTION>
		Description:
			Collects files from a folder.
		Arguments:
			<string> sourceFolderPath: the path to the folder from which the contents needs to be zipped
			<boolean> useSubdirs: set to true when you want to get all contents of the sourcefolder and it's subfolders		
		Return:
			<array> An array of filepaths
		</FUNCTION>*/
		
		local all_files = #()

		--collecting source files
		all_files = getFiles (sourceFolderPath + "*") --root dir files
		if useSubdirs do --also collect files from subdirs, recursively
		(
			fn_getDirTree sourceFolderPath --collect all subdirs in dir in to local all_dirs
			if all_dirs.count != 0 do join all_files (fn_getAllFiles all_dirs) --collect files from subdirs
		)
		
		format "Number of files: %\n" all_files.count
		if all_files.count == 0 do (print "No files have been found";return false)
		
		all_files
	),
	
	
	function fn_createFileList arrFile listPath basePath =
	(
		/*<FUNCTION>
		Description:
			Creates a textfile which contains a filelist with paths relative to a basepath
		Arguments:
			<array> arrFile: an array of filepaths
			<string> listPath: the path to the textfile
			<string> basePath: the path to which the files need to be relative
		Return:
		</FUNCTION>*/
		
		--zipping multiple files is done by using a file-list. This list is stored as a text-file on disk
		--generate a file with the list of files to be archived
		if doesFileExist listPath do deleteFile listPath -- delete old tmp file if exist
		makeDir (getFilenamePath  listPath)
		local theTempFile		
		try (theTempFile = createFile listPath) catch (format "% cannot be created\n" listPath; return false)
		for i in arrFile do format "%\n" (substituteString i basePath "") to:theTempFile --slice off the common root. Only possible when working from a drive-letter, not a unc-path. Why? Unknown!
		flush theTempFile
		close theTempFile
	),
	
	
	function fn_zipFiles sourceFolderPath: targetFolderPath: zipFileName: useSubdirs:true deleteOld:false =
	(
		/*<FUNCTION>
		Description:
			packages files together in a zipfile. Uses the max-native maxzip.exe to perform the zipping.
		Arguments:
			<string> sourceFolderPath: the path to the folder from which the contents needs to be zipped
			<string> targetFolderPath: the folder where the zipfile has to be stored
			<string> zipFileName: the name of the zipfile
			<boolean> useSubdirs: set to true when you want to get all contents of the sourcefolder and it's subfolders
			<boolean> deleteOld: set this to true if you want to delete the original files after zipping them
		Return:
			<string> the path of the generated zipfile
		</FUNCTION>*/
		
		--reset the file and dir-containers
		local all_files = #()
		all_dirs = #()
		
		--error check to see if the specified paths exists
		if not doesFileExist sourceFolderPath do (format "% does not exist\n" sourceFolderPath;return false)
		if not doesFileExist targetFolderPath do (format "% does not exist\n" targetFolderPath;return false)
		
		--check if the folderpaths end with a backslash. Add it if needed.
		if substring sourceFolderPath 1 -1 != "\\" do sourceFolderPath += "\\"
		if substring targetFolderPath 1 -1 != "\\" do targetFolderPath += "\\"
			
		--collect the files
		all_Files = fn_collectFiles sourceFolderPath:sourceFolderPath useSubdirs:useSubdirs
		if all_files == false do return false
			
		--create the file-list
		local theTempFilePath = @"C:/temp/maxzip.tmp" --need a path without any spaces!!
		fn_createFileList all_files theTempFilePath sourceFolderPath
		
		-- archive the files stored in the file-list into the zip
		local curDir = sysInfo.currentDir -- store current system dir
		local zipUtil = (getDir #maxRoot) + "maxzip.exe" --this is the actual zipping-program
		local zipFile = targetFolderPath + zipFileName + ".zip"
		local cmd = "" as stringStream --set up a zipping-command
		format "\"%\" \"%\" @%" zipUtil zipFile theTempFilePath to:cmd -- thanks to ofer_z--zipFile needs to be a mapped drive?
		sysInfo.currentDir = sourceFolderPath-- set system folder (zip tree begins here)
		(hiddendosCommand cmd exitcode:&exitcode) --EXECUTE THE ZIPPING
		sysInfo.currentDir = curDir -- restore old system dir
		
		-- delete the temp file list
-- 		deleteFile theTempFilePath
		--delete old files if is true
		if deleteOld do fn_deleteOldFiles all_files
		
		--output the code returned by maxzip. For a list of possible codes, check here: http://www.info-zip.org/mans/zip.html under DIAGNOSTICS
		format "status:%\n" exitcode
		
		zipfile --return the path of the zipfile
	),
	
	function event_workZipThread control arg =
	(
		/*<FUNCTION>
		Description:
			This event executes the zip in a separate thread. The result is passed onto a different event.
		Arguments:
		Return:
		</FUNCTION>*/
		
		zip.fn_zipFiles sourceFolderPath:zip.sourceFolderPath targetFolderPath:zip.targetFolderPath zipFileName:zip.zipFileName useSubdirs:zip.useSubdirs deleteOld:zip.deleteOld
	),
	
	function fn_initZipThread theTread =
	(
		/*<FUNCTION>
		Description:
			initializes the event-handlers for the zip-thread
		Arguments:
			<thread> theThread: the thread which needs to be initialized
		Return:
		</FUNCTION>*/
		
		--Initialise event handlers
		theTread.WorkerSupportsCancellation = true
		theTread.WorkerReportsProgress = true
		dotNet.addEventHandler theTread "DoWork" event_workZipThread
		if theTread.IsBusy do theTread.CancelAsync()		
	),
	
	init = fn_initZipThread zipThread
	
)

/******************************************************************************************************

USAGE

******************************************************************************************************

-- INITIALIZE THE ZIP-STRUCT AND FEED IT THE PATH
zip = zip()
zip.sourceFolderPath = @"C:\Users\klaas\Projecten 3d_script\2012-07-09 Zip\Max huis mariniersweg 3e"
zip.targetFolderPath = @"C:\Users\klaas\Projecten 3d_script\2012-07-09 Zip"

-- EXECUTE SINGLETHREADED
zip.fn_zipFiles sourceFolderPath:zip.sourceFolderPath targetFolderPath:zip.targetFolderPath zipFileName:zip.zipFileName

-- EXECUTE MULTITHREADED
if not zip.zipThread.IsBusy do zip.zipThread.RunWorkerAsync()	
*/