﻿// copyright 2010 Adobe Systems, Inc. All rights reserved.
// Written by Tai Luxon / edited by Barkin Aygun

/*
@@@BUILDINFO@@@ WorkspaceImportExport.jsx 1.0.5
*/
/*


A few notes about this script:

Exporting:
- Allows exporting custom workspaces, but not presets
- Can choose to export either the original state that the workspace was created in or the
  current/last saved state (will save the current workspace before export if you're exporting it)

Importing:
- Prevents importing workspaces with the same names as preset workspaces (filename or display name)
- Allows you to overwrite custom workspaces after asking you
- Handled importing over the current workspace
- Reloads the workspace list after importing
- Does not verify that files are valid or compatible workspace files when importing

Localization
- Should work in localized builds, but UI of the script is not localized itself
- In localized builds, we can't prevent importing a workspace that conflicts with the localized
  name of a "deleted" preset workspace

Version Compatibility
- Written for CS5. Should mostly work in CS4 (maybe earlier), but has some shortcomings (e.g. won't
  reload workspace list after import - tells you to relaunch instead)

*/

// enable double clicking from the Macintosh Finder or the Windows Explorer
var gAppMajorVersion = 0;


// Prompts the user for files to import as workspaces, then imports them
function ImportWorkspaces(files)
	{

	// user may have canceled the open dialog
	if (files == null)
		return;
	
	var userConflicts = new Array();
	var presetConflicts = new Array();
	
	for (var i = files.length - 1; i >= 0 ; --i)
		{
	 /*     var file = files[i];
            var msg = "absoluteURI: " + file.absoluteURI +
                            "\ndisplayName: " + file.displayName +
                            "\nfsName: " + file.fsName +
                            "\nfullName: " + file.fullName +
                            "\nlocalizedName: " + file.localizedName +
                            "\nname: " + file.name +
                            "\npath: " + file.path +
                            "\nrelativeURI: " +  file.relativeURI;
            alert (msg);*/
            
		// If the workspace conflicts with a preset or user workspace, push it onto the beginning
		// of the relevant array, and remove it from 'files'.
		// Pass non-URI formatted names
		if (PresetWorkspaceWithNameExists (decodeURI(files[i].name)))
			{
			presetConflicts.unshift(files[i]);
			files.splice(i, 1);
			}
		else if (UserWorkspaceWithNameExists(decodeURI(files[i].name)))
			{
			userConflicts.unshift(files[i]);
			files.splice(i, 1);
			}
		}
		

	// If any of the files conflict with preset workspaces, we notify the user that they can't be
	// imported. If any of the files conflict with user workspaces, we also notify the user but give
	// them the option to cancel the import. Detailed in the truth table below.
	
// Files   User C   Preset C     Behavior
//  T       T         T          Prompt to replace, Conditional Import, notify can't be imported
//  T       T         F          Prompt to replace, Conditional Import,
//  T       F         T    							Import, 			notify can't be imported
//  T		F		  F								Import
//  F		T		  T			 Prompt to replace, Conditional Import, notify can't be imported
//  F		T		  F			 Prompt to replace, Conditional Import
//  F		F		  T													notify can't be imported
//  F		F		  F			 Shouldn't happen
	

	if (presetConflicts.length > 0)
		{
		var haveOthers = (files.length > 0) || (userConflicts.length > 0);
		
		if (haveOthers)
			alert ("File Conflict\n" + 
					"The following workspaces cannot be imported because they conflict with " +
					"existing workspaces:\n\n" +
					ConcatFileNamesFromFileArray(presetConflicts));
		else
			alert ("None of the selected file(s) can be imported because they all conflict with " +
					"existing workspaces.");
		
		}
	
	var importingUserConflicts = false;
	
	if (userConflicts.length > 0)
		{
		var confirmMsg = "File Conflict\n" +
						"The following workspaces conflict with " +
						"existing custom workspaces:\n\n" +
						ConcatFileNamesFromFileArray(userConflicts) + "\n\n" +
						"If you continue, the existing workspaces will be replaced " +
						"with the workspaces you are importing.\n\n" +
						"Continue?";
		
		// If the user wants to import the user conflicts, add them back to the 'files' array
		if (confirm(confirmMsg))
			files = files.concat(userConflicts);
		}
	
	
	
	// All files canceled or conflicting
	if (files.length == 0)
		return;
		

	// Ok, let's import!
	ImportScreenedWorkspaceFiles(files);
	}

// Import the provided files as workspaces. All files in 'files' should already have been
// verified as not conflicting with preset workspaces and either not conflicting with user
// workspaces or ok'd to overwrite user workspaces.
function ImportScreenedWorkspaceFiles(files)
	{
	var userFolder = GetUserWorkspacesFolder();
	var modifiedFolder = (gAppMajorVersion >= 12) ? GetModifiedWorkspacesFolder() : null;
	
	var failures = new Array();
	
	for (var i = files.length - 1; i >= 0 ; --i)
		{
		var decodedFilename = decodeURI(files[i].name);
		var targetFile = new File(userFolder.fsName + "/" + decodedFilename);
		
        alert ("Copying to " + targetFile.fsName);
		var copySucceeded = files[i].copy(targetFile);
		
		if (copySucceeded)
			{
			// The copy succeeded. If we are copying over an existing workspace, reset it.
			// This does two things:
			// 1. Make sure we delete any "modified" file that may be around so we load the newly
			//	  imported version of a workspace next time it is selected.
			// 2. If we imported over the current workspace, reload it now.
			// If it is not an existing workspace, just make sure we delete any modified version
			// that may be lying around.
			
			// Older versions don't have "modified" workspace files and we can't get the workspace
			// list, so we just tell the user they need to relaunch Photoshop.
			if (gAppMajorVersion >= 12)
				{
				// The filename is the displayname for user workspaces, which is what we are creating,
				// so it works for resetting the workspace.
				if (IsLoadedWorkspace(decodedFilename))
					ResetWorkspace (decodedFilename);
				else
					{
					var modifiedFile = new File (modifiedFolder.fsName + "/" + decodedFilename);
					
					if (modifiedFile.exists)
						modifiedFile.remove();
					}
				}
			}
		else
			{
			// The copy failed, push it onto the beginning of the failures array
			// and remove it from 'files'
			failures.unshift(files[i]);
			files.splice(i, 1);
			}
		}
	
	// Report the results
	var msg = files.length + " file(s) imported successfully.";
	
	if (failures.length > 0)
		msg += " " + failures.length + " file(s) could not be imported.";
	
	msg += "\n"; // 1st paragraph is bold on Mac
		
	// Make PS reload the workspaces so they show up, or warn if we can't
	if (files.length > 0)
		{
		if (gAppMajorVersion >= 12)
			ReloadWorkspaces();
		else
			msg += "You must relaunch Photoshop to see the new workspaces which you've imported.\n\n";
		}
	
	
	// Summarize what was imported and what failed to import
	msg += "The following file(s) were imported:\n\n";
	
	if (files.length > 0)
		msg += ConcatFileNamesFromFileArray(files);
	else
		msg += "\t<none>";
	
	if (failures.length > 0)
		msg += "\n\n" +
				"The following file(s) failed to import:\n\n" +
				ConcatFileNamesFromFileArray(failures);
	
	//alert (msg);
	}

// Concatenates all the filenames in an array so that they can be output for the user.
function ConcatFileNamesFromFileArray(files)
	{
	var rv = '';
	
	for (var i = 0; i < files.length; ++i)
		{
		if (i > 0)
			rv += "\n";
		
		// decode URI to make spaces, etc user friendly
		rv += "\t" + decodeURI(files[i].name);
		}
	
	return rv;
	}

// Determines if there is a preset workspace with the given filename
function PresetWorkspaceWithNameExists(filename)
	{
	// special-case Essentials
	if (filename == "Essentials")
		return true;
	
	// Test against preset workspace files
	var presetFolder = GetPresetWorkspacesFolder();

	if (FolderContainsFile(presetFolder, filename))
		return true;
		
	// If we are in a localized build, check against the display names of loaded presets
	// which will be different than the filenames, but which we'd also like to avoid
	// conflicts with. We can't check for conflicts with display names of "deleted" presets.
	if ((app.locale != 'en_US') && (gAppMajorVersion >= 12))
		{
		// Get loaded preset workspaces
		var wsList = GetWorkspaceList(false, true);
		
		for (var i = 0; i < wsList.length; ++i)
			{
			if (wsList[i].displayName.toLowerCase() == filename.toLowerCase())
				return true;
			}
		}
	
	// We couldn't find one!
	return false;
	}

// Determines if there is a user workspace with the given filename
function UserWorkspaceWithNameExists(filename)
	{
	var userFolder = GetUserWorkspacesFolder();

	return FolderContainsFile(userFolder, filename);
	
	}

// Returns true if Photoshop has a workspace in its list of loaded workspaces with the given filename
function IsLoadedWorkspace(filename)
	{
	if (gAppMajorVersion < 12)
		{
		// Warn that I need to do something differently in old version.
		alert ("Only supported in CS5 and newer.");
		return;
		}
		
	var wsList = GetWorkspaceList(true, true);
	
	for (var i = 0; i < wsList.length; ++i)
		{
		if (decodeURI(wsList[i].filename).toLowerCase() == filename.toLowerCase())
			return true;
		}
	
	return false;
	}

// filename should NOT be in URI format
function FolderContainsFile(folder, filename)
	{
	var matchingFiles = folder.getFiles(filename);
	
	// Base Case.
	// We got back a match in this folder, confirm we have a matching File, not just a Folder or
	// a file that contains the filename but isn't the same
	if (matchingFiles.length > 0)
		{
		for (var i = 0; i < matchingFiles.length; ++i)
			{
			if ((matchingFiles[i] instanceof File) &&
				(decodeURI(matchingFiles[i].name).toLowerCase() == filename.toLowerCase()))
				return true;
			}
		}
	
	// Recursive Case
	// Search subfolders.
	var subfolders = folder.getFiles(IsFolder);
	
	
	for (var i = 0; i < subfolders.length; ++i)
		{
		if (FolderContainsFile(subfolders[i], filename))
			return true;
		}
	
	return false;
	}

// Gets all the files in a folder and it's subfolders.
function GetFilesInFolderRecursive(folder)
	{
	// Base Case.
	// Get the files from this folder
	var files = folder.getFiles(IsFile);
	
	// Recursive Case
	// Search subfolders.
	var subfolders = folder.getFiles(IsFolder);
	
	for (var i = 0; i < subfolders.length; ++i)
		{
		// add in all the files from the subfolder.
		files = files.concat (GetFilesInFolderRecursive(subfolders[i]));
		}
	
	return files;
	}
	

// Returns true if the provided object is a Folder object
function IsFolder(object)
	{
	return object instanceof Folder;
	}

// Returns true if the provided object is a File object
function IsFile(object)
	{
	return object instanceof File;
	}

// Gets the folder where preset workspaces live
function GetPresetWorkspacesFolder()
	{
	var platform = $.os.charAt(0) == 'M' ? 'Mac' : 'Win';
	var folder = new Folder (app.path + "/Locales/" + app.locale + "/Additional Presets/" +
								platform + "/Workspaces");
	
	
	return folder;
	}

// Gets the folder where the 'original' versions of user workspace files live.
function GetUserWorkspacesFolder()
	{
	var settingsFolder = new Folder (app.preferencesFolder);
	
	if (!settingsFolder.exists)
		settingsFolder.create ();
	
	var folder = new Folder (settingsFolder.fsName + "/WorkSpaces");
	
	if (!folder.exists)
		folder.create();
	
	return folder;
	}

// Gets the folder where the 'modified' versions of workspace files live.
function GetModifiedWorkspacesFolder()
	{
	if (gAppMajorVersion < 12)
		{
		// Warn that I need to do something differently in old version.
		alert ("Only supported in CS5 and newer.");
		return;
		}
		
	var settingsFolder = new Folder (app.preferencesFolder);
	
	if (!settingsFolder.exists)
		settingsFolder.create ();
	
	var folder = new Folder (settingsFolder.fsName + "/WorkSpaces (Modified)");
	
	if (!folder.exists)
		folder.create();
	
	return folder;
	}

// Gets the list of Photoshop workspaces
function GetWorkspaceList(includeUser, includePreset)
	{
    var wsList = new Array();
    
	if (gAppMajorVersion >= 12)
		{
		// Request the workspace list from Photoshop
		var workspaceList = stringIDToTypeID( "workspaceList" );
		var typeOrdinal = charIDToTypeID( "Ordn" );
		var enumTarget = charIDToTypeID( "Trgt" );
		var classProperty = charIDToTypeID( "Prpr" );
		var classApplication = charIDToTypeID( "capp" );
		var ref = new ActionReference();
		ref.putProperty( classProperty, workspaceList );
		ref.putEnumerated( classApplication, typeOrdinal, enumTarget );
		var desc = executeActionGet( ref );
		var descList = desc.getList( workspaceList );
		
		// Convert to a friendly list
		var displayName = stringIDToTypeID( "displayName" );
		var filename = stringIDToTypeID( "name" );
		var userWorkspace = stringIDToTypeID( "user" );
		
		for (var i = 0; i < descList.count; ++i)
			{
			var wsDesc = descList.getObjectValue( i );
			var workspace = new Object;
			
			workspace.displayName = wsDesc.getString( displayName );
			workspace.filename = wsDesc.getString( filename );
			workspace.user = wsDesc.getBoolean( userWorkspace );
			
			if ((includeUser && workspace.user) || (includePreset && !workspace.user))
				wsList.push (workspace);
			}
		}
	else
		{
		// We have to fall back to seeing what files are in the workspace
		// folders, which is less reliable but allows us to reasonably support
		// older versions.
		if (includeUser)
			{
			var wsFiles = GetFilesInFolderRecursive(GetUserWorkspacesFolder());
			
			for (var i = 0; i < wsFiles.length; ++i)
				{
				var workspace = new Object;
				
				workspace.displayName = decodeURI(wsFiles[i].name);
				workspace.filename = decodeURI(wsFiles[i].name);
				workspace.user = true;
				
				wsList.push (workspace);
				}
			}
		
		if (includePreset)
			{
			var wsFiles = GetFilesInFolderRecursive(GetPresetWorkspacesFolder());
			
			for (var i = 0; i < wsFiles.length; ++i)
				{
				var workspace = new Object;
				
				workspace.displayName = decodeURI(wsFiles[i].name);
				workspace.filename = decodeURI(wsFiles[i].name);
				workspace.user = false;
				
				wsList.push (workspace);
				}
			}
		}
		
	return wsList;
	}

// Resets the named workspace.
function ResetWorkspace(displayName)
	{
	if (gAppMajorVersion < 12)
		{
		// Warn that I need to do something differently in old version.
		alert ("Only supported in CS5 and newer.");
		return;
		}
		
	var idRset = charIDToTypeID( "Rset" );
	var desc4 = new ActionDescriptor();
	var idnull = charIDToTypeID( "null" );	
		var ref3 = new ActionReference();
		var idworkspace = stringIDToTypeID( "workspace" );
		ref3.putName( idworkspace, displayName );
	desc4.putReference( idnull, ref3 );
	executeAction( idRset, desc4, DialogModes.NO );
	}


// Makes PS reload the workspaces so they show up in the switcher and menus.
function ReloadWorkspaces()
	{
	if (gAppMajorVersion < 12)
		{
		// Warn that I need to do something differently in old version.
		alert ("Only supported in CS5 and newer.");
		return;
		}
		
	var idDlt = stringIDToTypeID( "load" );
		var desc3 = new ActionDescriptor();
		var idnull = charIDToTypeID( "null" );
			var ref2 = new ActionReference();
			var idworkspace = stringIDToTypeID( "workspace" );
			ref2.putClass( idworkspace );
		desc3.putReference( idnull, ref2 );
	executeAction( idDlt, desc3, DialogModes.NO );
	}

// Calls 'callee' from within a try-catch block and reports any exceptions.
function TCWrap(callee, arg1, arg2, arg3)
	{
	try
		{
		callee(arg1, arg2, arg3);
		}
	catch(e)
		{
		alert (e + ":" + e.line);
		}
	}
