moveForMon.applescript, from darwin13

moveForMon.applescript, from darwin13

property DEF_ALL : false
property DEF_DEBUG_LEVEL : 1

on moveForMon(pars)

	set {all, debug_level} to {DEF_ALL, DEF_DEBUG_LEVEL}
	try
		set all to all of pars
	end try
	try
		set debug_level to debug_level of pars
	end try

	-- debug mode
	if debug_level > 0 then
		log "debug mode = " & debug_level
		if debug_level > 1 then
			display dialog "debug mode = " & debug_level
		end if
	end if

	-- set monitoring tools' positions
	-- SimpleFloatingClock
	set w_sfcCEST to 1
	set w_sfcJST to 2

	-- XRG
	set w_XRG to 1

	-- set monitoring tool's name
	-- GeekTool
	set s_gtCal to "gcalCal"
	set s_gtGcal to "gcalList"
	set s_gtTask to "gtasklist"
	set s_gtPs to "myps"

	-- x posiiotn for monitoring
	set ledge to -160
	set ledgeSFCoffset to 20

	-- y positions
	set y_sfcCEST to 30
	set y_sfcJST to y_sfcCEST + 100
	set y_gtCal to y_sfcJST + 105
	set y_gtGcal to y_gtCal + 115
	set y_gtTask to y_gtGcal + 175
	set y_gtPs to y_gtTask + 85
	set y_XRG to y_gtPs + 75

	-- app to be excepted
	set expApp to {"XRG"}

	-- app to be half size, in left monitor (0.7 times full)
	set halfSizeApp_L to {}

	-- app to be half size, in right monitor (0.7 times full)
	set halfSizeApp_R to {"AdobeReader"}

	-- app only to be moved, in left monitor
	set noResizeApp_L to {}

	-- app only to be moved, in right monitor
	set noResizeApp_R to {"Finder", "Skype", "Microsoft Word", "Microsoft Excel", "Microsoft PowerPoint"}

	-- app to be moved left
	set app_L to {"firefox", "Evernote", "thunderbird", "thunderbird-bin", "Mail"}

	-- app to be moved right
	set app_R to {"iTerm"}

	-- get screen size
	tell application "Finder"
		set scriptPath to (path to me)'s folder as text
	end tell
	set windowSizeScpt to scriptPath & "windowSize.scpt"
	set windowSize to load script file windowSizeScpt

	-- main screen
	set svs to windowSize's getVisibleFrame(1, 1) --+1 is used to avoid edge, especially needed for y position
	set dPosX to (item 1 of svs) + 1
	set dPosY to (item 2 of svs) + 1
	set dWidth to item 3 of svs
	set dHeight to item 4 of svs

	-- try to get left screen
	set dPosX_L to dPosX + 1
	set dPosY_L to dPosY + 1
	try
		set svsL to windowSize's getVisibleFrame(-1, 1)
		set dPosX_L to (item 1 of svsL) + 1
		set dPosY_L to (item 2 of svsL) + 1
	end try

	-- try to get right screen
	set dPosX_R to dPosX + 1
	set dPosY_R to dPosY + 1
	try
		set svsR to windowSize's getVisibleFrame(dPosX + dWidth + 1, 1)
		set dPosX_R to (item 1 of svsR) + 1
		set dPosY_R to (item 2 of svsR) + 1
	end try

	-- move monitoring tools
	if dPosX_L < 0 then
		if debug_level > 0 then
			log "ledge to ledgeDual"
		end if
	else
		set ledge to dWidth + ledge
		if debug_level > 0 then
			log "ledge to ledgeSingle"
		end if
	end if

	tell application "System Events"
		-- SimpleFloatingClock
		try
			set appName to "SimpleFloatingClock"
			tell process appName
				tell window w_sfcCEST
					set size to {1000, 1000}
					set position to {ledge + ledgeSFCoffset, y_sfcCEST}
				end tell
				tell window w_sfcJST
					set size to {1000, 1000}
					set position to {ledge + ledgeSFCoffset, y_sfcJST}
				end tell
			end tell
		end try

		---- GeekTool
		--try
		--	--set appName to "GeekTool"
		--	set appName to "GeekTool Helper" -- for GeekTool 3
		--
		--	tell process appName
		--		set nW to number of windows
		--		--display dialog appName & " in process, nWindows=" & nW
		--		tell window w_gtCal
		--		end tell
		--		tell window w_gtGcal
		--			-- set size to {1000, 1000}
		--			set position to {ledge, y_gtGcal}
		--		end tell
		--		tell window w_gtTask
		--			-- set size to {1000, 1000}
		--			set position to {ledge, y_gtTask}
		--		end tell
		--		tell window w_gtPs
		--			-- set size to {1000, 1000}
		--			set position to {ledge, y_gtPs}
		--		end tell
		--	end tell
		--	--on error errMsg
		--	--	display dialog "ERROR: " & errMsg
		--end try

		-- XRG
		try
			set appName to "XRG"
			tell process appName
				tell window w_XRG
					set size to {1000, 2000}
					set position to {ledge, y_XRG}
				end tell
			end tell
		end try
	end tell

	-- GeekTool (should be outside of application "System Events", and use application "GeekTool Helper" ?)
	-- can't use position to geeklets
	tell application "GeekTool Helper" -- can not use appName (variable), which shows error at id?
		--geeklets
		repeat with g in every «class gLet»
			tell g
				name
				if name is s_gtCal then
					set «class xpos» to ledge
					set «class ypos» to y_gtCal
				else if name is s_gtGcal then
					set «class xpos» to ledge
					set «class ypos» to y_gtGcal
				else if name is s_gtTask then
					set «class xpos» to ledge
					set «class ypos» to y_gtTask
				else if name is s_gtPs then
					set «class xpos» to ledge
					set «class ypos» to y_gtPs
				end if
			end tell
		end repeat
	end tell

	-- Move/Resize other windows
	if all then
		-- get application name
		tell application "System Events"
			set appList to (get name of every application process whose visible is true)
		end tell

		-- repeat for all app
		repeat with appName in appList
			if appName is not in expApp then
				tell application "System Events"
					try
						tell process appName
							set nW to number of windows
							repeat with i from 1 to nW
								if debug_level > 0 then
									log appName & " " & i
									if debug_level > 1 then
										display dialog appName & " " & i
									end if
								end if
								if appName is in halfSizeApp_L then
									if debug_level > 0 then
										log "half size left"
										if debug_level > 1 then
											display dialog "half size left"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_L, monPosY:dPosY_L, xsize:0.7, ysize:0.7, moveForMon:false, debug_level:debug_level})
								else if appName is in halfSizeApp_R then
									if debug_level > 0 then
										log "half size right"
										if debug_level > 1 then
											display dialog "half size right"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_R, monPosY:dPosY_R, xsize:0.7, ysize:0.7, moveForMon:false, debug_level:debug_level})
								else if appName is in noResizeApp_L then
									if debug_level > 0 then
										log "half size left"
										if debug_level > 1 then
											display dialog "half size left"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_L, monPosY:dPosY_L, resize:0, moveForMon:false, debug_level:debug_level})
								else if appName is in noResizeApp_R then
									if debug_level > 0 then
										log "move right"
										if debug_level > 1 then
											display dialog "move right"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_R, monPosY:dPosY_R, resize:0, moveForMon:false, debug_level:debug_level})
								else if appName is in app_L then
									if debug_level > 0 then
										log "move left full size"
										if debug_level > 1 then
											display dialog "move left full size"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_L, monPosY:dPosY_L, moveForMon:false, debug_level:debug_level})
								else if appName is in app_R then
									if debug_level > 0 then
										log "move right full size"
										if debug_level > 1 then
											display dialog "move right full size"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_R, monPosY:dPosY_R, moveForMon:false, debug_level:debug_level})
								end if
							end repeat
						end tell
					end try
				end tell
			end if
		end repeat
	end if
end moveForMon

on run
	moveForMon({})
end run

property DEF_ALL : false
property DEF_DEBUG_LEVEL : 1

on moveForMon(pars)

	set {all, debug_level} to {DEF_ALL, DEF_DEBUG_LEVEL}
	try
		set all to all of pars
	end try
	try
		set debug_level to debug_level of pars
	end try

	-- debug mode
	if debug_level > 0 then
		log "debug mode = " & debug_level
		if debug_level > 1 then
			display dialog "debug mode = " & debug_level
		end if
	end if

	-- set monitoring tools' positions
	-- SimpleFloatingClock
	set w_sfcCEST to 1
	set w_sfcJST to 2

	-- XRG
	set w_XRG to 1

	-- set monitoring tool's name
	-- GeekTool
	set s_gtCal to "gcalCal"
	set s_gtGcal to "gcalList"
	set s_gtTask to "gtasklist"
	set s_gtPs to "myps"

	-- x posiiotn for monitoring
	set ledge to -160
	set ledgeSFCoffset to 20

	-- y positions
	set y_sfcCEST to 30
	set y_sfcJST to y_sfcCEST + 100
	set y_gtCal to y_sfcJST + 105
	set y_gtGcal to y_gtCal + 115
	set y_gtTask to y_gtGcal + 175
	set y_gtPs to y_gtTask + 85
	set y_XRG to y_gtPs + 75

	-- app to be excepted
	set expApp to {"XRG"}

	-- app to be half size, in left monitor (0.7 times full)
	set halfSizeApp_L to {}

	-- app to be half size, in right monitor (0.7 times full)
	set halfSizeApp_R to {"AdobeReader"}

	-- app only to be moved, in left monitor
	set noResizeApp_L to {}

	-- app only to be moved, in right monitor
	set noResizeApp_R to {"Finder", "Skype", "Microsoft Word", "Microsoft Excel", "Microsoft PowerPoint"}

	-- app to be moved left
	set app_L to {"firefox", "Evernote", "thunderbird", "thunderbird-bin", "Mail"}

	-- app to be moved right
	set app_R to {"iTerm"}

	-- get screen size
	tell application "Finder"
		set scriptPath to (path to me)'s folder as text
	end tell
	set windowSizeScpt to scriptPath & "windowSize.scpt"
	set windowSize to load script file windowSizeScpt

	-- main screen
	set svs to windowSize's getVisibleFrame(1, 1) --+1 is used to avoid edge, especially needed for y position
	set dPosX to (item 1 of svs) + 1
	set dPosY to (item 2 of svs) + 1
	set dWidth to item 3 of svs
	set dHeight to item 4 of svs

	-- try to get left screen
	set dPosX_L to dPosX + 1
	set dPosY_L to dPosY + 1
	try
		set svsL to windowSize's getVisibleFrame(-1, 1)
		set dPosX_L to (item 1 of svsL) + 1
		set dPosY_L to (item 2 of svsL) + 1
	end try

	-- try to get right screen
	set dPosX_R to dPosX + 1
	set dPosY_R to dPosY + 1
	try
		set svsR to windowSize's getVisibleFrame(dPosX + dWidth + 1, 1)
		set dPosX_R to (item 1 of svsR) + 1
		set dPosY_R to (item 2 of svsR) + 1
	end try

	-- move monitoring tools
	if dPosX_L < 0 then
		if debug_level > 0 then
			log "ledge to ledgeDual"
		end if
	else
		set ledge to dWidth + ledge
		if debug_level > 0 then
			log "ledge to ledgeSingle"
		end if
	end if

	tell application "System Events"
		-- SimpleFloatingClock
		try
			set appName to "SimpleFloatingClock"
			tell process appName
				tell window w_sfcCEST
					set size to {1000, 1000}
					set position to {ledge + ledgeSFCoffset, y_sfcCEST}
				end tell
				tell window w_sfcJST
					set size to {1000, 1000}
					set position to {ledge + ledgeSFCoffset, y_sfcJST}
				end tell
			end tell
		end try

		---- GeekTool
		--try
		--	--set appName to "GeekTool"
		--	set appName to "GeekTool Helper" -- for GeekTool 3
		--
		--	tell process appName
		--		set nW to number of windows
		--		--display dialog appName & " in process, nWindows=" & nW
		--		tell window w_gtCal
		--		end tell
		--		tell window w_gtGcal
		--			-- set size to {1000, 1000}
		--			set position to {ledge, y_gtGcal}
		--		end tell
		--		tell window w_gtTask
		--			-- set size to {1000, 1000}
		--			set position to {ledge, y_gtTask}
		--		end tell
		--		tell window w_gtPs
		--			-- set size to {1000, 1000}
		--			set position to {ledge, y_gtPs}
		--		end tell
		--	end tell
		--	--on error errMsg
		--	--	display dialog "ERROR: " & errMsg
		--end try

		-- XRG
		try
			set appName to "XRG"
			tell process appName
				tell window w_XRG
					set size to {1000, 2000}
					set position to {ledge, y_XRG}
				end tell
			end tell
		end try
	end tell

	-- GeekTool (should be outside of application "System Events", and use application "GeekTool Helper" ?)
	-- can't use position to geeklets
	tell application "GeekTool Helper" -- can not use appName (variable), which shows error at id?
		--geeklets
		repeat with g in geeklets
			tell g
				name
				if name is s_gtCal then
					set x position to ledge
					set y position to y_gtCal
				else if name is s_gtGcal then
					set x position to ledge
					set y position to y_gtGcal
				else if name is s_gtTask then
					set x position to ledge
					set y position to y_gtTask
				else if name is s_gtPs then
					set x position to ledge
					set y position to y_gtPs
				end if
			end tell
		end repeat
	end tell

	-- Move/Resize other windows
	if all then
		-- get application name
		tell application "System Events"
			set appList to (get name of every application process whose visible is true)
		end tell

		-- repeat for all app
		repeat with appName in appList
			if appName is not in expApp then
				tell application "System Events"
					try
						tell process appName
							set nW to number of windows
							repeat with i from 1 to nW
								if debug_level > 0 then
									log appName & " " & i
									if debug_level > 1 then
										display dialog appName & " " & i
									end if
								end if
								if appName is in halfSizeApp_L then
									if debug_level > 0 then
										log "half size left"
										if debug_level > 1 then
											display dialog "half size left"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_L, monPosY:dPosY_L, xsize:0.7, ysize:0.7, moveForMon:false, debug_level:debug_level})
								else if appName is in halfSizeApp_R then
									if debug_level > 0 then
										log "half size right"
										if debug_level > 1 then
											display dialog "half size right"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_R, monPosY:dPosY_R, xsize:0.7, ysize:0.7, moveForMon:false, debug_level:debug_level})
								else if appName is in noResizeApp_L then
									if debug_level > 0 then
										log "half size left"
										if debug_level > 1 then
											display dialog "half size left"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_L, monPosY:dPosY_L, resize:0, moveForMon:false, debug_level:debug_level})
								else if appName is in noResizeApp_R then
									if debug_level > 0 then
										log "move right"
										if debug_level > 1 then
											display dialog "move right"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_R, monPosY:dPosY_R, resize:0, moveForMon:false, debug_level:debug_level})
								else if appName is in app_L then
									if debug_level > 0 then
										log "move left full size"
										if debug_level > 1 then
											display dialog "move left full size"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_L, monPosY:dPosY_L, moveForMon:false, debug_level:debug_level})
								else if appName is in app_R then
									if debug_level > 0 then
										log "move right full size"
										if debug_level > 1 then
											display dialog "move right full size"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_R, monPosY:dPosY_R, moveForMon:false, debug_level:debug_level})
								end if
							end repeat
						end tell
					end try
				end tell
			end if
		end repeat
	end if
end moveForMon

on run
	moveForMon({})
end run


Start with just a list of top-level categories

Choosing a top-level category will bring up a second dialog choice with
a list of all of that category's subcategories.

Both list dialog's have an option to display the full list of all
categories and subcategories.

(*
	Log Page - Log categorized web page bookmarks to a text file

	Version: @@VERSION@@
	Date:    2012-12-19
	Author:  Steve Wheeler

	Get the title, URL, current date and time, and a user-definable
	category for the frontmost Safari window and log the info to a text
	file.

	This program is free software available under the terms of a
	BSD-style (3-clause) open source license detailed below.
*)

(*
Copyright (c) 2011-2012 Steve Wheeler
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names of
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*)

(*
	TODO: Change the following local-only config before any public release.
	The log file should be: $GTD_IN/logs/urls/urls.YYYY.txt
	Or just: $GTD_IN/logs/urls.txt

	TODO: The edit_log() function is currently incomplete. It will only launch
	an editor from the shell and only if that editor is a GUI editor (in this
	case, MacVim). If I release this script publicly, I will still need to
	implement funtionality to launch a GUI editor normally (with the 'open'
	AppleScript commmand) as well as a command-line editor run in the Terminal.
*)

property script_name : "Log Page"
property script_version : "@@VERSION@@"

---------- USER CONFIGURATION ----------------------------------------
-- Default log directory to use if one is not read from config file:
--set log_dir to POSIX path of (path to application support folder from user domain) & "Jazzhead/Log Page/"
set log_dir to POSIX path of (path to desktop folder) & "Log Page/" -- :DEBUG:

-- Log file name:
set log_name to "urls.txt"

-- Default categories/labels if none are found in (or there is no)
-- URL file. Modify as desired:
set default_cats to "Development
Development:AppleScript
Development:AppleScript:Mail
Design
Productivity:GTD
Apple:Mac OS X
Apple:iOS
Career
Education
Health
Health:Diet
Health:Fitness
General"

-- Text editor to use if manually editing the log file.
--
-- This example:
--	1. Opens the file using MacVim ('mvim' as installed by MacPorts)
--	2. Switches the local working directory for just that buffer to the
--	    directory containing the file ('lcd')
--	3. Gives the MacVim window a name ('--servername').
--
set text_editor to "/opt/local/bin/mvim \"+lcd %:p:h\" --servername LOG_PAGE"

-- Should the specified editor be launched from the unix shell?
set should_use_shell to true
---------- END User Configuration ----------------------------------------

---------- ADVANCED (EXPERIMENTAL) ---------------------------------------------
-- For advanced users familiar with the command-line, override the default log
-- directory path. This probably won't be in a public release.
--
-- Grab an exported shell environment variable from a config file to use as the
-- log directory path. For this example, I have a "~/.gtdrc" config file that I
-- source from my "~/.bash_profile" file so that it exportss environment
-- variables relating to my GTD system. That makes those variables available to
-- any of the shell scripts and tools that I use. For instance, when I'm
-- editing files in Vim, I can write paths to other files using one of the
-- environment variables rather than a full or relative path and the 'gf'
-- command will work for jumping to that file. The config file looks like this:
--
--         # ~/.gtdrc
--         #
--         # File locations for GTD documents
--         #
--         export GTD_DIR=$HOME/Documents/org  # GTD root directory
--         export GTD_IN=$GTD_DIR/content      # source/input files
--         export GTD_OUT=$GTD_DIR/html        # generated HTML output files
--         export GTD_LIB=$GTD_DIR/lib         # code library files
--         export GTD_BIN=$GTD_LIB/script      # scripts
--
-- Remove (or comment out) the "(*" and "*)" comment markers below to use this
-- section. Make sure you also configure the two variables for your system.
--(*
--------------- USER CONFIGURATION ----------
-- Path to your config file:
set cfg_file to POSIX path of (path to home folder) & ".gtdrc"
-- The environment variable you're interested in:
set env_var to "GTD_IN"
--------------- END User Configuration ---------

-- Check for environment variable in config file:
set s to "egrep '\\<" & env_var & "=' " & quoted form of cfg_file &
	" > /dev/null 2>&1; echo $?"
set shell_status to (do shell script s) as integer
-- Source the config file and echo the desired variable:
if shell_status is 0 then -- (means that grep found a match)
	set s to ". " & quoted form of cfg_file & " && echo $" & env_var
	set gtd_in_dir to do shell script s
	set log_dir to gtd_in_dir & "/logs/"

	--------------------
	-- OPTIONAL: Override the Text editor to integrate with my GTD system.
	--
	-- This example:
	--	1. Opens the file using MacVim ('mvim' as installed by MacPorts)
	--	2. Switches the local working directory ('lcd') for just that buffer to
	--	    my GTD content directory ($GTD_IN after escaping potential spaces)
	--	3. Gives the MacVim window a name ('--servername').

	----- First, escape any potential spaces in the directory path:
	--
	--set gtd_in_dir to gtd_in_dir & " foo    bar" -- :DEBUG: spaces in path
	--
	set s to "echo " & quoted form of gtd_in_dir & " | sed 's/ \\{1,\\}/\\\\ /g'" -- escape spaces
	set gtd_in_dir to do shell script s
	--
	--log gtd_in_dir -- :DEBUG: need to use 'log' to see the value w/o AppleScript escapes

	----- Finally, set the editor with all of the options:
	--
	set text_editor to "/opt/local/bin/mvim \"+lcd " & gtd_in_dir & "\" --servername GTD --remote-silent"
	--
	--log text_editor -- :DEBUG:
	--return text_editor -- :DEBUG:
	--------------------
end if
---------- END Advanced --------------------------------------------------------
--*)

--return log_dir -- :DEBUG:

property uRule : data utxt2500 as Unicode text -- BOX DRAWINGS LIGHT HORIZONTAL

--
-- Get URL and title from front browser window
--
tell application "Safari"
	--activate
	try
		set this_url to URL of front document
		set this_title to name of first tab of front window whose visible is true
	on error
		return false
	end try
end tell
-- Transliterate non-ASCII characters to ASCII
set this_title to convert_to_ascii(this_title)

--return this_title -- :DEBUG:

--
-- Format the record separator between log entries
--
set rule_char to "-"
set rule_width to 80
set field_width to 7
set field_sep to " | "
set rec_sep to "" & multiply_text(rule_char, field_width - 1) &
	"+" & multiply_text(rule_char, rule_width - field_width)

--
-- Format date-time string as "YYYY-MM-DD HH:MM:SS"
--
set {year:y, month:m, day:d, hours:hh, minutes:mm, seconds:ss} to current date
set m to m as integer -- coerce month string
-- Pad numbers with leading zeros:
set tmp_list to {}
repeat with this_item in {m, d, hh, mm, ss}
	set this_item to text 2 thru -1 of (100 + this_item as text)
	set end of tmp_list to this_item
end repeat
set {m, d, hh, mm, ss} to tmp_list
-- Final string:
set date_time to join_list({y, m, d}, "-") & " " & join_list({hh, mm, ss}, ":")

-- Log file name
--set log_name to "urls." & y & ".txt"

--
-- Create log directory if it doesn't already exist. (Go ahead and use a shell
-- command since it's easy to create intermediate directories all in one step.):
--
try
	set s to "mkdir -pm700 " & quoted form of log_dir
	do shell script s
on error err_msg number err_num
	error "Can't create log directory: " & err_msg number err_num
	return "Fatal Error: Can't create log directory"
end try

set log_file_posix to "" & log_dir & log_name
set log_file to POSIX file log_file_posix

--
-- Add header to new or empty log file
--
set should_init to false
tell application "Finder"
	if (not (exists log_file)) or (_IO's read_file(log_file) is "") then
		set should_init to true
	end if
end tell

set head_sep to multiply_text("#", 80)
if should_init then
	set file_header to head_sep & "
# urls.txt - Timestamped and categorized URL archive
# ==================================================
# Optionally list some categories/labels of URLs below that might not yet
# be used. Any categories/lables listed here or in the web page records
# will be presented as a list to select from when saving new URLs.
" & head_sep & linefeed & default_cats & linefeed & head_sep & "
# END: category/label list
" & head_sep & linefeed
	tell _IO to write_file(log_file, file_header)
	set cat_txt_all to default_cats
else
	-- Read categories from file
	(*
		:TODO: Error handling.
	*)
	set cat_txt_all to text 2 thru -2 of (item 3 of split_text(_IO's read_file(log_file), head_sep))
end if

-- Parse any existing labels from the "Label" fields of the URLs file:
set s to "LANG=C sed -n 's/^Label | //p' " & quoted form of log_file_posix & " | sort | uniq"
--return s
set used_cats to do shell script s without altering line endings
-- Sort those along with the manually entered categories/labels:
set s to "echo \"" & cat_txt_all & linefeed & used_cats & "\" | egrep -v '^$' | sort | uniq"
set cat_txt_all to do shell script s without altering line endings
--return cat_txt_all -- :DEBUG:
-- Coerce the lines into a list:
set cat_list_all to paragraphs of cat_txt_all
if cat_list_all's last item is "" then set cat_list_all to cat_list_all's items 1 thru -2
--return cat_list_all -- :DEBUG:

-- Get top-level categories:
set s to "LANG=C echo \"" & cat_txt_all & "\" | sed -n 's/^\\([^:]\\{1,\\}\\).*/\\1/p' | uniq"
set cat_txt_top to do shell script s without altering line endings
--return cat_txt_top -- :DEBUG:
-- Coerce the lines into a list:
set cat_list_top to paragraphs of cat_txt_top
if cat_list_top's last item is "" then set cat_list_top to cat_list_top's items 1 thru -2
--return cat_list_top -- :DEBUG:

--
-- Prompt for title, category/subcategories and optional note for URL
--

-- The number of dialog prompts will vary depending on if a subcategory list is requested.
set prompt_count to 1 -- increment with every dialog or list prompt
set prompt_total to 4 -- increment if the subcategory list is displayed

-- Accept or modify URL title
set b to {"Cancel", "Manually Edit Log File", "Next..."}
set t to "" & script_name & ": Title (" & prompt_count & "/" & prompt_total & ")"
set m to "To log the URL for:" & return & return
	& tab & "\"" & this_title & "\"" & return & return &
	"first accept or edit the title."
display dialog m default answer this_title with title t buttons b default button last item of b
set {this_title, btn_pressed} to {text returned of result, button returned of result}
set prompt_count to prompt_count + 1
if btn_pressed is item 2 of b then
	if should_use_shell then
		set this_log_file to quoted form of log_file_posix
	else
		set this_log_file to log_file
	end if
	edit_log(this_log_file, text_editor, should_use_shell)
	return "Script ended with '" & (item 2 of b as text) & "'"
end if

-- Select an existing top-level category
set extra_items to {"Show full list with subcategories...", multiply_text(uRule, 20)}
set cat_choice to choose_top_category(cat_list_top, extra_items, prompt_count, prompt_total)
set prompt_count to prompt_count + 1
--return cat_choice

--if cat_choice is false then return false
if cat_choice is false then -- skip the subcategory list
	set cat_choice to "Please enter a category..."
else
	set prompt_threshold to 1
	if cat_choice as text is extra_items's first item then
		set cat_list_sub to cat_list_all
	else
		set extra_items to {"Show full list of existing categories...", multiply_text(uRule, 35)}
		--set cat_list_sub to {}
		copy extra_items to cat_list_sub -- use 'copy' instead of 'set' for separate lists
		-- Account for extra menu items:
		set prompt_threshold to prompt_threshold + (count of extra_items)
		-- Build subcategory list:
		repeat with this_cat in cat_list_all
			if (this_cat as text) is (cat_choice as text)
				or (this_cat as text) starts with (cat_choice & ":") then
				set end of cat_list_sub to this_cat as text
			end if
		end repeat
	end if
	--return cat_list_sub

	-- Only show a subcategory list if there is more than one item:
	if (count of cat_list_sub) is greater than prompt_threshold then
		set cat_choice to choose_subcategory(cat_list_all, cat_list_sub, extra_items, prompt_count, prompt_total)
		set prompt_count to prompt_count + 1
		set prompt_total to prompt_total + 1
	end if
end if

-- Modify selected category or enter a new category
set t to "" & script_name & ": Category (" & prompt_count & "/" & prompt_total & ")"
set m to "Please provide a category and any optional subcategories (or edit your selected category) for the URL. Example: \"Development:AppleScript:Mail\""
display dialog m default answer cat_choice with title t buttons b default button last item of b
set {this_label, btn_pressed} to {text returned of result, button returned of result}
set prompt_count to prompt_count + 1

-- Optionally add note
if btn_pressed is last item of b then
	set last item of b to "Append URL to Log File"
	set t to "" & script_name & ": Note (" & prompt_count & "/" & prompt_total & ")"
	set m to "Optionally add a short note. Just leave the field blank if you don't want to add a note."
	display dialog m default answer "" with title t buttons b default button last item of b
	set {this_note, btn_pressed} to {text returned of result, button returned of result}
end if

--
-- Append or edit the log file
--
if btn_pressed is last item of b then
	set final_text to join_list({
		"Date " & field_sep & date_time,
		"Label" & field_sep & this_label,
		"Title" & field_sep & this_title,
		"URL  " & field_sep & this_url}, linefeed) & linefeed
	if this_note is not "" then
		set final_text to final_text & "Note " & field_sep & this_note & linefeed
	end if
	set final_text to final_text & rec_sep & linefeed
	--return "[DEBUG]" & return & final_text
	_IO's append_file(log_file, final_text)
else
	if should_use_shell then
		set this_log_file to quoted form of log_file_posix
	else
		set this_log_file to log_file
	end if
	edit_log(this_log_file, text_editor, should_use_shell)
end if

--display alert "Log Page" message "DEBUG: Script complete."

(************************************************************
 *	Subroutines
 ************************************************************)

-- ===== Main Functions =====

on choose_top_category(cat_list_top, extra_items, prompt_count, prompt_total)
	--log "[debug] in choose_top_category()"
	set t to "" & script_name & ": Category (" & prompt_count & "/" & prompt_total & ")"
	set m to "Please select a top-level category for the URL you want to log. Next you will be able to select subcategories."
	set cat_choice to choose from list extra_items & cat_list_top with title t with prompt m OK button name "Next..."
	-- If the list separator was selected instead of a list item, then recursively prompt again:
	if cat_choice as text is extra_items's last item then
		set cat_choice to choose_top_category(cat_list_top, extra_items, prompt_count, prompt_total)
	end if
	return cat_choice
end choose_top_category

on choose_subcategory(cat_list_all, cat_list_sub, extra_items, prompt_count, prompt_total)
	--log "[debug] in choose_subcategory()"

	-- Select an existing category
	set t to "" & script_name & ": Category (" & prompt_count & "/" & prompt_total & ")"
	set m to "Please select a category or subcategory for the URL you want to log. You will have a chance to edit your choice (to add a new category or subcategory)."
	--set cat_choice to choose from list cat_list_all with title t with prompt m OK button name "Next..."
	set cat_choice to choose from list cat_list_sub with title t with prompt m OK button name "Next..."
	--if cat_choice is false then return false
	if cat_choice is false then set cat_choice to "Please enter a category..."

	if cat_choice as text is extra_items's last item then
		--log "[debug] choose_subcategory(): choice is extra_items's last item"
		-- If list separator was selected instead of a list item, then recursively prompt again:
		set cat_choice to choose_subcategory(cat_list_all, cat_list_sub, extra_items, prompt_count, prompt_total)
	else if cat_choice as text is extra_items's first item then
		--log "[debug] choose_subcategory(): choice is extra_items's first item"
		-- If full list was selected, then recursively prompt using that list (note that the full list is used for both list arguments):
		set cat_choice to choose_subcategory(cat_list_all, cat_list_all, extra_items, prompt_count, prompt_total)
	end if
	return cat_choice
end choose_subcategory

on edit_log(log_file, text_editor, should_use_shell)
	(*
		TODO:MAYBE:
		Have a configuration variable in the main script to specify which text
		editor to use. Have another variable for configuring whether or not
		it's a GUI application. Then this function would use 'open' to launch a
		GUI app or script the Terminal to launch a command-line editor.
		Optionally use 'do shell script' to launch a compatible GUI app from a
		command line (so that the working directory can be changed to that of
		the file first), but I don't know if that will really be necessary
		since I have a Vim shortcut for quickly changing the working directory
		if need be.
	*)
	if should_use_shell then
		set s to text_editor & space & log_file & space & "> /dev/null 2>&1"
		do shell script s
		return
	else
		set t to script_name
		set m to ":TODO: edit_log() function: use non-shell-invoked editor."
		set b to {"Cancel"}
		display alert t message m buttons b cancel button 1 as critical
		return false
	end if
end edit_log

-- ===== Utility Functions =====

on convert_to_ascii(non_ascii_txt)
	--
	-- From 'man iconv_open':
	--
	--    When  the string "//TRANSLIT" is appended to tocode, transliteration is
	--    activated. This means that when a character cannot  be  represented  in
	--    the target character set, it can be approximated through one or several
	--    characters that look similar to the original character.
	--
	set s to "iconv -f UTF-8 -t US-ASCII//TRANSLIT <<<" & quoted form of non_ascii_txt
	do shell script s
end convert_to_ascii

on multiply_text(str, n)
	if n < 1 or str = "" then return ""
	set lst to {}
	repeat n times
		set end of lst to str
	end repeat
	return lst as string
end multiply_text

on split_text(txt, delim)
	set old_tids to AppleScript's text item delimiters
	try
		set AppleScript's text item delimiters to (delim as string)
		set lst to every text item of (txt as string)
		set AppleScript's text item delimiters to old_tids
		return lst
	on error err_msg number err_num
		set AppleScript's text item delimiters to old_tids
		error "Can't split_text(): " & err_msg number err_num
	end try
end split_text

on join_list(lst, delim)
	set old_tids to AppleScript's text item delimiters
	try
		set AppleScript's text item delimiters to (delim as string)
		set txt to lst as string
		set AppleScript's text item delimiters to old_tids
		return txt
	on error err_msg number err_num
		set AppleScript's text item delimiters to old_tids
		error "Can't join_list(): " & err_msg number err_num
	end try
end join_list

(************************************************************
 *	Script Objects
 ************************************************************)

script _IO

	(* == PUBLIC == *)

	on write_file(file_path, this_data)
		_write_file(file_path, this_data, string, false) -- overwrite existing file
	end write_file

	on append_file(file_path, this_data)
		_write_file(file_path, this_data, string, true) -- append new data to end of existing file
	end append_file

	on read_file(file_path)
		_read_file(file_path, string)
	end read_file

	(* == PRIVATE == *)

	on _write_file(file_path, this_data, data_class, should_append_data)
		try
			set file_path to file_path as text
			set file_handle to
				open for access file file_path with write permission
			if not should_append_data then
				set eof of the file_handle to 0
			write this_data as data_class to file_handle starting at eof
			close access file_handle
			return true
		on error err_msg number err_num
			try
				close access file file_path
			end try
			error err_msg number err_num
			return false
		end try
	end _write_file

	on _read_file(file_path, data_class)
		try
			set file_path to file_path as text
			if (get eof file file_path) is 0 then
				set file_contents to ""
			else
				set file_contents to read file file_path as data_class
			end if
			return file_contents
		on error
			return 0
		end try
	end _read_file
end script

(*
	Log Page - Log categorized web page bookmarks to a text file

	Version: @@VERSION@@
	Date:    2012-01-04
	Author:  Steve Wheeler

	Get the title, URL, current date and time, and a user-definable
	category for the frontmost Safari window and log the info to a text
	file.

	This program is free software available under the terms of a
	BSD-style (3-clause) open source license detailed below.
*)

(*
Copyright (c) 2011-2012 Steve Wheeler
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names of
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*)

(*
	TODO: Change the following local-only config before any public release.
	The log file should be: $GTD_IN/logs/urls/urls.YYYY.txt
	Or just: $GTD_IN/logs/urls.txt

	TODO: The edit_log() function is currently incomplete. It will only launch
	an editor from the shell and only if that editor is a GUI editor (in this
	case, MacVim). If I release this script publicly, I will still need to
	implement funtionality to launch a GUI editor normally (with the 'open'
	AppleScript commmand) as well as a command-line editor run in the Terminal.
*)

property script_name : "Log Page"
property script_version : "@@VERSION@@"

---------- USER CONFIGURATION ----------------------------------------
-- Default log directory to use if one is not read from config file:
--set log_dir to POSIX path of (path to application support folder from user domain) & "Jazzhead/Log Page/"
set log_dir to POSIX path of (path to desktop folder) & "Log Page/" -- :DEBUG:

-- Log file name:
set log_name to "urls.txt"

-- Default categories/labels if none are found in (or there is no)
-- URL file. Modify as desired:
set default_cats to "Development
Development:AppleScript
Development:AppleScript:Mail
Design
Productivity:GTD
Apple:Mac OS X
Apple:iOS
Career
Education
Health
Health:Diet
Health:Fitness
General"

-- Text editor to use if manually editing the log file.
--
-- This example:
--	1. Opens the file using MacVim ('mvim' as installed by MacPorts)
--	2. Switches the local working directory for just that buffer to the
--	    directory containing the file ('lcd')
--	3. Gives the MacVim window a name ('--servername').
--
set text_editor to "/opt/local/bin/mvim \"+lcd %:p:h\" --servername LOG_PAGE"

-- Should the specified editor be launched from the unix shell?
set should_use_shell to true
---------- END User Configuration ----------------------------------------

---------- ADVANCED (EXPERIMENTAL) ---------------------------------------------
-- For advanced users familiar with the command-line, override the default log
-- directory path. This probably won't be in a public release.
--
-- Grab an exported shell environment variable from a config file to use as the
-- log directory path. For this example, I have a "~/.gtdrc" config file that I
-- source from my "~/.bash_profile" file so that it exportss environment
-- variables relating to my GTD system. That makes those variables available to
-- any of the shell scripts and tools that I use. For instance, when I'm
-- editing files in Vim, I can write paths to other files using one of the
-- environment variables rather than a full or relative path and the 'gf'
-- command will work for jumping to that file. The config file looks like this:
--
--         # ~/.gtdrc
--         #
--         # File locations for GTD documents
--         #
--         export GTD_DIR=$HOME/Documents/org  # GTD root directory
--         export GTD_IN=$GTD_DIR/content      # source/input files
--         export GTD_OUT=$GTD_DIR/html        # generated HTML output files
--         export GTD_LIB=$GTD_DIR/lib         # code library files
--         export GTD_BIN=$GTD_LIB/script      # scripts
--
-- Remove (or comment out) the "(*" and "*)" comment markers below to use this
-- section. Make sure you also configure the two variables for your system.
--(*
--------------- USER CONFIGURATION ----------
-- Path to your config file:
set cfg_file to POSIX path of (path to home folder) & ".gtdrc"
-- The environment variable you're interested in:
set env_var to "GTD_IN"
--------------- END User Configuration ---------

-- Check for environment variable in config file:
set s to "egrep '\\<" & env_var & "=' " & quoted form of cfg_file &
	" > /dev/null 2>&1; echo $?"
set shell_status to (do shell script s) as integer
-- Source the config file and echo the desired variable:
if shell_status is 0 then -- (means that grep found a match)
	set s to ". " & quoted form of cfg_file & " && echo $" & env_var
	set gtd_in_dir to do shell script s
	set log_dir to gtd_in_dir & "/logs/"

	--------------------
	-- OPTIONAL: Override the Text editor to integrate with my GTD system.
	--
	-- This example:
	--	1. Opens the file using MacVim ('mvim' as installed by MacPorts)
	--	2. Switches the local working directory ('lcd') for just that buffer to
	--	    my GTD content directory ($GTD_IN after escaping potential spaces)
	--	3. Gives the MacVim window a name ('--servername').

	----- First, escape any potential spaces in the directory path:
	--
	--set gtd_in_dir to gtd_in_dir & " foo    bar" -- :DEBUG: spaces in path
	--
	set s to "echo " & quoted form of gtd_in_dir & " | sed 's/ \\{1,\\}/\\\\ /g'" -- escape spaces
	set gtd_in_dir to do shell script s
	--
	--log gtd_in_dir -- :DEBUG: need to use 'log' to see the value w/o AppleScript escapes

	----- Finally, set the editor with all of the options:
	--
	set text_editor to "/opt/local/bin/mvim \"+lcd " & gtd_in_dir & "\" --servername GTD --remote-silent"
	--
	--log text_editor -- :DEBUG:
	--return text_editor -- :DEBUG:
	--------------------
end if
---------- END Advanced --------------------------------------------------------
--*)

--return log_dir -- :DEBUG:

--
-- Get URL and title from front browser window
--
tell application "Safari"
	--activate
	try
		set this_url to URL of front document
		set this_title to name of first tab of front window whose visible is true
	on error
		return false
	end try
end tell
-- Transliterate non-ASCII characters to ASCII
set this_title to convert_to_ascii(this_title)

--return this_title -- :DEBUG:

--
-- Format the record separator between log entries
--
set rule_char to "-"
set rule_width to 80
set field_width to 7
set field_sep to " | "
set rec_sep to "" & multiply_text(rule_char, field_width - 1) &
	"+" & multiply_text(rule_char, rule_width - field_width)

--
-- Format date-time string as "YYYY-MM-DD HH:MM:SS"
--
set {year:y, month:m, day:d, hours:hh, minutes:mm, seconds:ss} to current date
set m to m as integer -- coerce month string
-- Pad numbers with leading zeros:
set tmp_list to {}
repeat with this_item in {m, d, hh, mm, ss}
	set this_item to text 2 thru -1 of (100 + this_item as text)
	set end of tmp_list to this_item
end repeat
set {m, d, hh, mm, ss} to tmp_list
-- Final string:
set date_time to join_list({y, m, d}, "-") & " " & join_list({hh, mm, ss}, ":")

-- Log file name
--set log_name to "urls." & y & ".txt"

--
-- Create log directory if it doesn't already exist. (Go ahead and use a shell
-- command since it's easy to create intermediate directories all in one step.):
--
try
	set s to "mkdir -pm700 " & quoted form of log_dir
	do shell script s
on error err_msg number err_num
	error "Can't create log directory: " & err_msg number err_num
	return "Fatal Error: Can't create log directory"
end try

set log_file_posix to "" & log_dir & log_name
set log_file to POSIX file log_file_posix

--
-- Add header to new or empty log file
--
set should_init to false
tell application "Finder"
	if (not (exists log_file)) or (_IO's read_file(log_file) is "") then
		set should_init to true
	end if
end tell

set head_sep to multiply_text("#", 80)
if should_init then
	set file_header to head_sep & "
# urls.txt - Timestamped and categorized URL archive
# ==================================================
# Optionally list some categories/labels of URLs below that might not yet
# be used. Any categories/lables listed here or in the web page records
# will be presented as a list to select from when saving new URLs.
" & head_sep & linefeed & default_cats & linefeed & head_sep & "
# END: category/label list
" & head_sep & linefeed
	tell _IO to write_file(log_file, file_header)
	set cat_txt to default_cats
else
	-- Read categories from file
	(*
		:TODO: Error handling.
	*)
	set cat_txt to text 2 thru -2 of (item 3 of split_text(_IO's read_file(log_file), head_sep))
end if

-- Parse any existing labels from the "Label" fields of the URLs file:
set s to "sed -n 's/^Label | //p' " & quoted form of log_file_posix & " | sort | uniq"
--return s
set used_cats to do shell script s without altering line endings
-- Sort those along with the manually entered categories/labels:
set s to "echo \"" & cat_txt & linefeed & used_cats & "\" | egrep -v '^$' | sort | uniq"
set cat_txt to do shell script s
-- Coerce the lines into a list:
set cat_list to paragraphs of cat_txt
--return cat_list -- :DEBUG:

--
-- Prompt for title, category/subcategories and optional note for URL
--

-- Accept or modify URL title
set b to {"Cancel", "Manually Edit Log File", "Next..."}
set t to "" & script_name & ": Title (1/4)"
set m to "To log the URL for:" & return & return
	& tab & "\"" & this_title & "\"" & return & return &
	"first accept or edit the title."
display dialog m default answer this_title with title t buttons b default button last item of b
set {this_title, btn_pressed} to {text returned of result, button returned of result}
if btn_pressed is item 2 of b then
	if should_use_shell then
		set this_log_file to quoted form of log_file_posix
	else
		set this_log_file to log_file
	end if
	editLog(this_log_file, text_editor, should_use_shell)
	return "Script ended with '" & (item 2 of b as text) & "'"
end if

-- Select an existing category
set t to "" & script_name & ": Category (2/4)"
set m to "Please select a category for the URL you want to log. You will have a chance to edit your choice."
set cat_choice to choose from list cat_list with title t with prompt m OK button name "Next..."
--if cat_choice is false then return false
if cat_choice is false then set cat_choice to "Please enter a category..."

-- Modify selected category or enter a new category
set t to "" & script_name & ": Category (3/4)"
set m to "Please provide a category and any optional subcategories (or edit your selected category) for the URL. Example: \"Development:AppleScript:Mail\""
display dialog m default answer cat_choice with title t buttons b default button last item of b
set {this_label, btn_pressed} to {text returned of result, button returned of result}

-- Optionally add note
if btn_pressed is last item of b then
	set last item of b to "Append URL to Log File"
	set t to "" & script_name & ": Note (4/4)"
	set m to "Optionally add a short note. Just leave the field blank if you don't want to add a note."
	display dialog m default answer "" with title t buttons b default button last item of b
	set {this_note, btn_pressed} to {text returned of result, button returned of result}
end if

--
-- Append or edit the log file
--
if btn_pressed is last item of b then
	set final_text to join_list({
		"Date " & field_sep & date_time,
		"Label" & field_sep & this_label,
		"Title" & field_sep & this_title,
		"URL  " & field_sep & this_url}, linefeed) & linefeed
	if this_note is not "" then
		set final_text to final_text & "Note " & field_sep & this_note & linefeed
	end if
	set final_text to final_text & rec_sep & linefeed
	--return "[DEBUG]" & return & final_text
	_IO's append_file(log_file, final_text)
else
	if should_use_shell then
		set this_log_file to quoted form of log_file_posix
	else
		set this_log_file to log_file
	end if
	edit_log(this_log_file, text_editor, should_use_shell)
end if

--display alert "Log Page" message "DEBUG: Script complete."

(************************************************************
 *	Subroutines
 ************************************************************)

-- ===== Main Functions =====

on edit_log(log_file, text_editor, should_use_shell)
	(*
		TODO:MAYBE:
		Have a configuration variable in the main script to specify which text
		editor to use. Have another variable for configuring whether or not
		it's a GUI application. Then this function would use 'open' to launch a
		GUI app or script the Terminal to launch a command-line editor.
		Optionally use 'do shell script' to launch a compatible GUI app from a
		command line (so that the working directory can be changed to that of
		the file first), but I don't know if that will really be necessary
		since I have a Vim shortcut for quickly changing the working directory
		if need be.
	*)
	if should_use_shell then
		set s to text_editor & space & log_file & space & "> /dev/null 2>&1"
		do shell script s
		return
	else
		set t to script_name
		set m to ":TODO: edit_log() function: use non-shell-invoked editor."
		set b to {"Cancel"}
		display alert t message m buttons b cancel button 1 as critical
		return false
	end if
end edit_log

-- ===== Utility Functions =====

on convert_to_ascii(non_ascii_txt)
	--
	-- From 'man iconv_open':
	--
	--    When  the string "//TRANSLIT" is appended to tocode, transliteration is
	--    activated. This means that when a character cannot  be  represented  in
	--    the target character set, it can be approximated through one or several
	--    characters that look similar to the original character.
	--
	set s to "iconv -f UTF-8 -t US-ASCII//TRANSLIT <<<" & quoted form of non_ascii_txt
	do shell script s
end convert_to_ascii

on multiply_text(str, n)
	if n < 1 or str = "" then return ""
	set lst to {}
	repeat n times
		set end of lst to str
	end repeat
	return lst as string
end multiply_text

on split_text(txt, delim)
	set old_tids to AppleScript's text item delimiters
	try
		set AppleScript's text item delimiters to (delim as string)
		set lst to every text item of (txt as string)
		set AppleScript's text item delimiters to old_tids
		return lst
	on error err_msg number err_num
		set AppleScript's text item delimiters to old_tids
		error "Can't split_text(): " & err_msg number err_num
	end try
end split_text

on join_list(lst, delim)
	set old_tids to AppleScript's text item delimiters
	try
		set AppleScript's text item delimiters to (delim as string)
		set txt to lst as string
		set AppleScript's text item delimiters to old_tids
		return txt
	on error err_msg number err_num
		set AppleScript's text item delimiters to old_tids
		error "Can't join_list(): " & err_msg number err_num
	end try
end join_list

(************************************************************
 *	Script Objects
 ************************************************************)

script _IO

	(* == PUBLIC == *)

	on write_file(file_path, this_data)
		_write_file(file_path, this_data, string, false) -- overwrite existing file
	end write_file

	on append_file(file_path, this_data)
		_write_file(file_path, this_data, string, true) -- append new data to end of existing file
	end append_file

	on read_file(file_path)
		_read_file(file_path, string)
	end read_file

	(* == PRIVATE == *)

	on _write_file(file_path, this_data, data_class, should_append_data)
		try
			set file_path to file_path as text
			set file_handle to
				open for access file file_path with write permission
			if not should_append_data then
				set eof of the file_handle to 0
			write this_data as data_class to file_handle starting at eof
			close access file_handle
			return true
		on error err_msg number err_num
			try
				close access file file_path
			end try
			error err_msg number err_num
			return false
		end try
	end _write_file

	on _read_file(file_path, data_class)
		try
			set file_path to file_path as text
			if (get eof file file_path) is 0 then
				set file_contents to ""
			else
				set file_contents to read file file_path as data_class
			end if
			return file_contents
		on error
			return 0
		end try
	end _read_file
end script


moveForMon.applescript, from darwin13

moveForMon.applescript, from darwin13

property DEF_ALL : false
property DEF_DEBUG_LEVEL : 1

on moveForMon(pars)

	set {all, debug_level} to {DEF_ALL, DEF_DEBUG_LEVEL}
	try
		set all to all of pars
	end try
	try
		set debug_level to debug_level of pars
	end try

	-- debug mode
	if debug_level > 0 then
		log "debug mode = " & debug_level
		if debug_level > 1 then
			display dialog "debug mode = " & debug_level
		end if
	end if

	-- set monitoring tools' positions
	-- SimpleFloatingClock
	set w_sfcCEST to 1
	set w_sfcJST to 2

	-- XRG
	set w_XRG to 1

	-- set monitoring tool's name
	-- GeekTool
	set s_gtCal to "gcalCal"
	set s_gtGcal to "gcalList"
	set s_gtTask to "gtasklist"
	set s_gtPs to "myps"

	-- x posiiotn for monitoring
	set ledge to -160
	set ledgeSFCoffset to 20

	-- y positions
	set y_sfcCEST to 30
	set y_sfcJST to y_sfcCEST + 100
	set y_gtCal to y_sfcJST + 105
	set y_gtGcal to y_gtCal + 115
	set y_gtTask to y_gtGcal + 175
	set y_gtPs to y_gtTask + 85
	set y_XRG to y_gtPs + 75

	-- app to be excepted
	set expApp to {"XRG"}

	-- app to be half size, in left monitor (0.7 times full)
	set halfSizeApp_L to {}

	-- app to be half size, in right monitor (0.7 times full)
	set halfSizeApp_R to {"AdobeReader"}

	-- app only to be moved, in left monitor
	set noResizeApp_L to {}

	-- app only to be moved, in right monitor
	set noResizeApp_R to {"Finder", "Skype", "Microsoft Word", "Microsoft Excel", "Microsoft PowerPoint"}

	-- app to be moved left
	set app_L to {"firefox", "Evernote", "thunderbird", "thunderbird-bin", "Mail"}

	-- app to be moved right
	set app_R to {"iTerm"}

	-- get screen size
	tell application "Finder"
		set scriptPath to (path to me)'s folder as text
	end tell
	set windowSizeScpt to scriptPath & "windowSize.scpt"
	set windowSize to load script file windowSizeScpt

	-- main screen
	set svs to windowSize's getVisibleFrame(1, 1) --+1 is used to avoid edge, especially needed for y position
	set dPosX to (item 1 of svs) + 1
	set dPosY to (item 2 of svs) + 1
	set dWidth to item 3 of svs
	set dHeight to item 4 of svs

	-- try to get left screen
	set dPosX_L to dPosX + 1
	set dPosY_L to dPosY + 1
	try
		set svsL to windowSize's getVisibleFrame(-1, 1)
		set dPosX_L to (item 1 of svsL) + 1
		set dPosY_L to (item 2 of svsL) + 1
	end try

	-- try to get right screen
	set dPosX_R to dPosX + 1
	set dPosY_R to dPosY + 1
	try
		set svsR to windowSize's getVisibleFrame(dPosX + dWidth + 1, 1)
		set dPosX_R to (item 1 of svsR) + 1
		set dPosY_R to (item 2 of svsR) + 1
	end try

	-- move monitoring tools
	if dPosX_L < 0 then
		if debug_level > 0 then
			log "ledge to ledgeDual"
		end if
	else
		set ledge to dWidth + ledge
		if debug_level > 0 then
			log "ledge to ledgeSingle"
		end if
	end if

	tell application "System Events"
		-- SimpleFloatingClock
		try
			set appName to "SimpleFloatingClock"
			tell process appName
				tell window w_sfcCEST
					set size to {1000, 1000}
					set position to {ledge + ledgeSFCoffset, y_sfcCEST}
				end tell
				tell window w_sfcJST
					set size to {1000, 1000}
					set position to {ledge + ledgeSFCoffset, y_sfcJST}
				end tell
			end tell
		end try

		---- GeekTool
		--try
		--	--set appName to "GeekTool"
		--	set appName to "GeekTool Helper" -- for GeekTool 3
		--
		--	tell process appName
		--		set nW to number of windows
		--		--display dialog appName & " in process, nWindows=" & nW
		--		tell window w_gtCal
		--		end tell
		--		tell window w_gtGcal
		--			-- set size to {1000, 1000}
		--			set position to {ledge, y_gtGcal}
		--		end tell
		--		tell window w_gtTask
		--			-- set size to {1000, 1000}
		--			set position to {ledge, y_gtTask}
		--		end tell
		--		tell window w_gtPs
		--			-- set size to {1000, 1000}
		--			set position to {ledge, y_gtPs}
		--		end tell
		--	end tell
		--	--on error errMsg
		--	--	display dialog "ERROR: " & errMsg
		--end try

		-- XRG
		try
			set appName to "XRG"
			tell process appName
				tell window w_XRG
					set size to {1000, 2000}
					set position to {ledge, y_XRG}
				end tell
			end tell
		end try
	end tell

	-- GeekTool (should be outside of application "System Events", and use application "GeekTool Helper" ?)
	-- can't use position to geeklets
	tell application "GeekTool Helper" -- can not use appName (variable), which shows error at id?
		--geeklets
		repeat with g in geeklets
			tell g
				name
				if name is s_gtCal then
					set x position to ledge
					set y position to y_gtCal
				else if name is s_gtGcal then
					set x position to ledge
					set y position to y_gtGcal
				else if name is s_gtTask then
					set x position to ledge
					set y position to y_gtTask
				else if name is s_gtPs then
					set x position to ledge
					set y position to y_gtPs
				end if
			end tell
		end repeat
	end tell

	-- Move/Resize other windows
	if all then
		-- get application name
		tell application "System Events"
			set appList to (get name of every application process whose visible is true)
		end tell

		-- repeat for all app
		repeat with appName in appList
			if appName is not in expApp then
				tell application "System Events"
					try
						tell process appName
							set nW to number of windows
							repeat with i from 1 to nW
								if debug_level > 0 then
									log appName & " " & i
									if debug_level > 1 then
										display dialog appName & " " & i
									end if
								end if
								if appName is in halfSizeApp_L then
									if debug_level > 0 then
										log "half size left"
										if debug_level > 1 then
											display dialog "half size left"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_L, monPosY:dPosY_L, xsize:0.7, ysize:0.7, moveForMon:false, debug_level:debug_level})
								else if appName is in halfSizeApp_R then
									if debug_level > 0 then
										log "half size right"
										if debug_level > 1 then
											display dialog "half size right"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_R, monPosY:dPosY_R, xsize:0.7, ysize:0.7, moveForMon:false, debug_level:debug_level})
								else if appName is in noResizeApp_L then
									if debug_level > 0 then
										log "half size left"
										if debug_level > 1 then
											display dialog "half size left"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_L, monPosY:dPosY_L, resize:0, moveForMon:false, debug_level:debug_level})
								else if appName is in noResizeApp_R then
									if debug_level > 0 then
										log "move right"
										if debug_level > 1 then
											display dialog "move right"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_R, monPosY:dPosY_R, resize:0, moveForMon:false, debug_level:debug_level})
								else if appName is in app_L then
									if debug_level > 0 then
										log "move left full size"
										if debug_level > 1 then
											display dialog "move left full size"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_L, monPosY:dPosY_L, moveForMon:false, debug_level:debug_level})
								else if appName is in app_R then
									if debug_level > 0 then
										log "move right full size"
										if debug_level > 1 then
											display dialog "move right full size"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_R, monPosY:dPosY_R, moveForMon:false, debug_level:debug_level})
								end if
							end repeat
						end tell
					end try
				end tell
			end if
		end repeat
	end if
end moveForMon

on run
	moveForMon({})
end run

property DEF_ALL : false
property DEF_DEBUG_LEVEL : 1

on moveForMon(pars)

	set {all, debug_level} to {DEF_ALL, DEF_DEBUG_LEVEL}
	try
		set all to all of pars
	end try
	try
		set debug_level to debug_level of pars
	end try

	-- debug mode
	if debug_level > 0 then
		log "debug mode = " & debug_level
		if debug_level > 1 then
			display dialog "debug mode = " & debug_level
		end if
	end if

	-- set monitoring tools' positions
	-- SimpleFloatingClock
	set w_sfcCEST to 1
	set w_sfcJST to 2

	-- XRG
	set w_XRG to 1

	-- set monitoring tool's name
	-- GeekTool
	set s_gtCal to "gcalCal"
	set s_gtGcal to "gcalList"
	set s_gtTask to "gtasklist"
	set s_gtPs to "myps"

	-- x posiiotn for monitoring
	set ledge to -160
	set ledgeSFCoffset to 20

	-- y positions
	set y_sfcCEST to 30
	set y_sfcJST to y_sfcCEST + 100
	set y_gtCal to y_sfcJST + 105
	set y_gtGcal to y_gtCal + 115
	set y_gtTask to y_gtGcal + 175
	set y_gtPs to y_gtTask + 85
	set y_XRG to y_gtPs + 75

	-- app to be excepted
	set expApp to {"XRG"}

	-- app to be half size, in left monitor (0.7 times full)
	set halfSizeApp_L to {}

	-- app to be half size, in right monitor (0.7 times full)
	set halfSizeApp_R to {"AdobeReader"}

	-- app only to be moved, in left monitor
	set noResizeApp_L to {}

	-- app only to be moved, in right monitor
	set noResizeApp_R to {"Finder", "Skype", "Microsoft Word", "Microsoft Excel", "Microsoft PowerPoint"}

	-- app to be moved left
	set app_L to {"firefox", "Evernote", "thunderbird", "thunderbird-bin", "Mail"}

	-- app to be moved right
	set app_R to {"iTerm"}

	-- get screen size
	tell application "Finder"
		set scriptPath to (path to me)'s folder as text
	end tell
	set windowSizeScpt to scriptPath & "windowSize.scpt"
	set windowSize to load script file windowSizeScpt

	-- main screen
	set svs to windowSize's getVisibleFrame(1, 1) --+1 is used to avoid edge, especially needed for y position
	set dPosX to (item 1 of svs) + 1
	set dPosY to (item 2 of svs) + 1
	set dWidth to item 3 of svs
	set dHeight to item 4 of svs

	-- try to get left screen
	set dPosX_L to dPosX + 1
	set dPosY_L to dPosY + 1
	try
		set svsL to windowSize's getVisibleFrame(-1, 1)
		set dPosX_L to (item 1 of svsL) + 1
		set dPosY_L to (item 2 of svsL) + 1
	end try

	-- try to get right screen
	set dPosX_R to dPosX + 1
	set dPosY_R to dPosY + 1
	try
		set svsR to windowSize's getVisibleFrame(dPosX + dWidth + 1, 1)
		set dPosX_R to (item 1 of svsR) + 1
		set dPosY_R to (item 2 of svsR) + 1
	end try

	-- move monitoring tools
	if dPosX_L < 0 then
		if debug_level > 0 then
			log "ledge to ledgeDual"
		end if
	else
		set ledge to dWidth + ledge
		if debug_level > 0 then
			log "ledge to ledgeSingle"
		end if
	end if

	tell application "System Events"
		-- SimpleFloatingClock
		try
			set appName to "SimpleFloatingClock"
			tell process appName
				tell window w_sfcCEST
					set size to {1000, 1000}
					set position to {ledge + ledgeSFCoffset, y_sfcCEST}
				end tell
				tell window w_sfcJST
					set size to {1000, 1000}
					set position to {ledge + ledgeSFCoffset, y_sfcJST}
				end tell
			end tell
		end try

		---- GeekTool
		--try
		--	--set appName to "GeekTool"
		--	set appName to "GeekTool Helper" -- for GeekTool 3
		--
		--	tell process appName
		--		set nW to number of windows
		--		--display dialog appName & " in process, nWindows=" & nW
		--		tell window w_gtCal
		--		end tell
		--		tell window w_gtGcal
		--			-- set size to {1000, 1000}
		--			set position to {ledge, y_gtGcal}
		--		end tell
		--		tell window w_gtTask
		--			-- set size to {1000, 1000}
		--			set position to {ledge, y_gtTask}
		--		end tell
		--		tell window w_gtPs
		--			-- set size to {1000, 1000}
		--			set position to {ledge, y_gtPs}
		--		end tell
		--	end tell
		--	--on error errMsg
		--	--	display dialog "ERROR: " & errMsg
		--end try

		-- XRG
		try
			set appName to "XRG"
			tell process appName
				tell window w_XRG
					set size to {1000, 2000}
					set position to {ledge, y_XRG}
				end tell
			end tell
		end try
	end tell

	-- GeekTool (should be outside of application "System Events", and use application "GeekTool Helper" ?)
	-- can't use position to geeklets
	tell application "GeekTool Helper" -- can not use appName (variable), which shows error at id?
		--geeklets
		repeat with g in every «class gLet»
			tell g
				name
				if name is s_gtCal then
					set «class xpos» to ledge
					set «class ypos» to y_gtCal
				else if name is s_gtGcal then
					set «class xpos» to ledge
					set «class ypos» to y_gtGcal
				else if name is s_gtTask then
					set «class xpos» to ledge
					set «class ypos» to y_gtTask
				else if name is s_gtPs then
					set «class xpos» to ledge
					set «class ypos» to y_gtPs
				end if
			end tell
		end repeat
	end tell

	-- Move/Resize other windows
	if all then
		-- get application name
		tell application "System Events"
			set appList to (get name of every application process whose visible is true)
		end tell

		-- repeat for all app
		repeat with appName in appList
			if appName is not in expApp then
				tell application "System Events"
					try
						tell process appName
							set nW to number of windows
							repeat with i from 1 to nW
								if debug_level > 0 then
									log appName & " " & i
									if debug_level > 1 then
										display dialog appName & " " & i
									end if
								end if
								if appName is in halfSizeApp_L then
									if debug_level > 0 then
										log "half size left"
										if debug_level > 1 then
											display dialog "half size left"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_L, monPosY:dPosY_L, xsize:0.7, ysize:0.7, moveForMon:false, debug_level:debug_level})
								else if appName is in halfSizeApp_R then
									if debug_level > 0 then
										log "half size right"
										if debug_level > 1 then
											display dialog "half size right"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_R, monPosY:dPosY_R, xsize:0.7, ysize:0.7, moveForMon:false, debug_level:debug_level})
								else if appName is in noResizeApp_L then
									if debug_level > 0 then
										log "half size left"
										if debug_level > 1 then
											display dialog "half size left"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_L, monPosY:dPosY_L, resize:0, moveForMon:false, debug_level:debug_level})
								else if appName is in noResizeApp_R then
									if debug_level > 0 then
										log "move right"
										if debug_level > 1 then
											display dialog "move right"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_R, monPosY:dPosY_R, resize:0, moveForMon:false, debug_level:debug_level})
								else if appName is in app_L then
									if debug_level > 0 then
										log "move left full size"
										if debug_level > 1 then
											display dialog "move left full size"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_L, monPosY:dPosY_L, moveForMon:false, debug_level:debug_level})
								else if appName is in app_R then
									if debug_level > 0 then
										log "move right full size"
										if debug_level > 1 then
											display dialog "move right full size"
										end if
									end if
									windowSize's windowSize({appName:appName, windowNumber:i, monPosX:dPosX_R, monPosY:dPosY_R, moveForMon:false, debug_level:debug_level})
								end if
							end repeat
						end tell
					end try
				end tell
			end if
		end repeat
	end if
end moveForMon

on run
	moveForMon({})
end run


Fix #73

Fix #73

set kalaicon to 2
tell application "System Events" to set itExists to ¬
  exists of alias ((path to me as text) & ":app.icns")
if itExists
  set kalaicon to alias ((path to me as text) & ":app.icns")
end if
tell application "SystemUIServer"
  activate
  set my_password to display dialog ¬
    "Please enter your Mac user account password. Installation will fail if your account does not have administrative privileges." with title ¬
    "Password" with icon kalaicon ¬
    default answer ¬
    "" buttons {"Cancel", "OK"} default button 2 ¬
    giving up after 295 ¬
    with hidden answer
end tell

set kalaicon to 2
tell application "System Events" to set itExists to ¬
  exists of alias ((path to me as text) & ":app.icns")
if itExists
  set kalaicon to alias ((path to me as text) & ":app.icns")
end if
tell application "SystemUIServer"
  activate
  set my_password to display dialog ¬
    "Please enter your administrator password:" with title ¬
    "Password" with icon kalaicon ¬
    default answer ¬
    "" buttons {"Cancel", "OK"} default button 2 ¬
    giving up after 295 ¬
    with hidden answer
end tell


Switching from endless loop to app idling.

Replacing the endless repeat with an on idle statement. Timing probably
needs more tweaking to lower CPU usage.

property debug : false
property currentVolume : null
property idleTime: 0.3

if (isTheFirstRun() and not isInLoginItems()) then
    set userAnswer to the button returned of (display dialog "Hi, thanks for installing Spotifree!" & ¬
        return & "Just so you know, Spotifree has no interface yet, and will work silently in the background." & return & return ¬
        & "BTW, do you want it to run automatically on startup?" with title ¬
        "You are awesome!" with icon 1 buttons {"No, thanks", "OK"} default button 2)
    if (userAnswer = "OK") then
        try
            my addToLoginItems()
        end try
    end if
    try
        -- Save in the preferences that Spotifree has already ran.
        do shell script "defaults write com.ArtemGordinsky.Spotifree 'hasRanBefore' 'true'"
    end try
end if

on idle
    try
        -- Adding a 5 seconds timeout instead of default 2 min. Prevents long unresponsiveness of the app.
        with timeout of (5) seconds
            if (isRunning() and isPlaying() and isAnAd()) then
                try
                    mute()
					set idleTime to 0.3
                end try
                repeat
                    set idleTime to 0.3
                    try
                        if (isRunning() and not isAnAd()) then
                            -- Have to delay a little bit, because Spotify may tell us about the next track too early,
                            -- and a user may to hear the last half-second of an ad.
                            delay 0.8
                            unmute()
                            exit repeat
                        end if
                    end try
                end repeat
            else
                -- Delaying, to get less of the crashing "connection is invalid" errors.
                set idleTime to 1
            end if
        end timeout
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "Somewhere in the outer loop.")
    end try
    -- This is how fast we are polling Spotify.
    -- The only speed at which you will hear almost no sounds passing through.
    -- Fortunately, even combined with the added load on Spotify, the CPU usage is rather miniscule.
    return idleTime
end idle

on mute()
    try
        tell application "Spotify"
            set currentVolume to sound volume
            -- This is the only way possible to mute Spotify during an advertisement.
            -- Otherwise it pauses when you mute the sound.
            pause
            set sound volume to 0
            play
        end tell
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "mute()")
    end try
    return
end mute

on unmute()
    try
        tell application "Spotify"
            -- Restore the volume to the level supplied to the parameter.
            set sound volume to currentVolume
        end tell
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "unmute()")
    end try
    return
end unmute

on isAnAd()
    local currentTrackPopularity, currentTrackDuration
    set currentTrackPopularity to null
    set currentTrackDuration to null
    # Nesting "try" blocks is the only way to handle multiple errors in AppleScript
    try
        try
            try
                tell application "Spotify"
                    set currentTrackPopularity to popularity of current track
                    set currentTrackDuration to duration of current track
                end tell
            on error number -1728
                # Ignoring "can't get current track" error
            end try
        on error number -609
            # Ignoring the "invalid connection errors"
        end try
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "isAnAd()")
        return false
    end try
    if ((currentTrackPopularity ≠ null and currentTrackDuration ≠ null) and (currentTrackPopularity = 0 and currentTrackDuration ≤ 40)) then
        return true
    else
        return false
    end if
end isAnAd

on isPlaying()
    local playerState
    set playerState to null
    # Nesting "try" blocks is the only way to handle multiple errors in AppleScript
    try
        try
            tell application "Spotify"
                set playerState to player state
            end tell
        on error -609
            # Ignoring the "invalid connection" errors
        end try
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "isRunning()")
        return false
    end try
    if (playerState ≠ null and playerState = «constant ****kPSP») then
        return true
    else
        return false
    end if
end isPlaying

on isRunning()
    local spotifyProcesses
    # Nesting "try" blocks is the only way to handle multiple errors in AppleScript
    try
        try
            tell application "System Events"
                set spotifyProcesses to (count of (every process whose name is "Spotify"))
            end tell
        on error number -609
            # Ignoring the "invalid connection" errors
        end try
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "isRunning()")
        return false
    end try
    if spotifyProcesses is 1 then
        return true
    else
        return false
    end if
end isRunning

on addToLoginItems()
    try
        tell application "System Events"
            -- Add Spotifree to the Login Items.
            make login item at end with properties {name:"Spotifree", path:"/Applications/Spotifree.app", hidden:true}
        end tell
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "addToLoginItems()")
        return
    end try
end addToLoginItems

on isTheFirstRun()
    local isPrefFileExists, prefFilePath
    set prefFilePath to "~/Library/Preferences/com.ArtemGordinsky.Spotifree"
    try
        tell application "System Events"
            if exists file prefFilePath then
                set isPrefFileExists to true
            else
                set isPrefFileExists to false
            end if
        end tell
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "isTheFirstRun()")
        return true
    end try
    # "not" works like a bang sign here
    return not isPrefFileExists
end isTheFirstRun

on isInLoginItems()
    try
        tell application "System Events"
            if login item "Spotifree" exists then
                return true
            else
                return false
            end if
        end tell
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "isInLoginItems()")
        return false
    end try
end isInLoginItems

on log_message(message)
    local content
    set content to (return & ¬
        "-----------------------------------------------------------" & ¬
        return & my dateAndTime() & return & message & return ¬
        & "-----------------------------------------------------------" & return)
    set log_file to ("Mountain Lion:Users:Mephistopelus:Developer:The Stuff:AppleScript:SpotiFree:Spotifree_log.txt")
    my write_to_file(content, log_file, true)
end log_message

on log_error(error_number, error_message, _where)
    local content
    if (debug) then
        set content to (return & "-----------------------------------------------------------" & ¬
            return & my dateAndTime() & return & "Error number: " & error_number ¬
            & return & "Error message: " & error_message & return & ¬
            "Where: " & _where & return ¬
            & "-----------------------------------------------------------" & return)
        set log_file to ("Mountain Lion:Users:Mephistopelus:Developer:The Stuff:AppleScript:SpotiFree:Spotifree_log.txt")
        my write_to_file(content, log_file, true)
    end if
end log_error

on write_to_file(this_data, target_file, append_data) -- (string, file path as string, boolean)
    try
        set the target_file to the target_file as text
        set the open_target_file to ¬
            open for access file target_file with write permission
        if append_data is false then ¬
            set eof of the open_target_file to 0
        write this_data to the open_target_file starting at eof
        close access the open_target_file
        return true
    on error
        try
            close access file target_file
        end try
    end try
end write_to_file

on dateAndTime()
    set currentDateAndTime to (current date) as string
    return currentDateAndTime
end dateAndTime

property debug : false
property currentVolume : null

if (isTheFirstRun() and not isInLoginItems()) then
    set userAnswer to the button returned of (display dialog "Hi, thanks for installing Spotifree!" & ¬
        return & "Just so you know, Spotifree has no interface yet, and will work silently in the background." & return & return ¬
        & "BTW, do you want it to run automatically on startup?" with title ¬
        "You are awesome!" with icon 1 buttons {"No, thanks", "OK"} default button 2)
    if (userAnswer = "OK") then
        try
            my addToLoginItems()
        end try
    end if
    try
        -- Save in the preferences that Spotifree has already ran.
        do shell script "defaults write com.ArtemGordinsky.Spotifree 'hasRanBefore' 'true'"
    end try
end if

repeat
    try
        -- Adding a 5 seconds timeout instead of default 2 min. Prevents long unresponsiveness of the app.
        with timeout of (5) seconds
            if (isRunning() and isPlaying() and isAnAd()) then
                try
                    mute()
                end try
                repeat
                    delay 0.3
                    try
                        if (isRunning() and not isAnAd()) then
                            -- Have to delay a little bit, because Spotify may tell us about the next track too early,
                            -- and a user may to hear the last half-second of an ad.
                            delay 0.8
                            unmute()
                            exit repeat
                        end if
                    end try
                end repeat
            else
                -- Delaying, to get less of the crashing "connection is invalid" errors.
                delay 1
            end if
        end timeout
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "Somewhere in the outer loop.")
    end try
    -- This is how fast we are polling Spotify.
    -- The only speed at which you will hear almost no sounds passing through.
    -- Fortunately, even combined with the added load on Spotify, the CPU usage is rather miniscule.
    delay 0.3
end repeat

on mute()
    try
        tell application "Spotify"
            set currentVolume to sound volume
            -- This is the only way possible to mute Spotify during an advertisement.
            -- Otherwise it pauses when you mute the sound.
            pause
            set sound volume to 0
            play
        end tell
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "mute()")
    end try
    return
end mute

on unmute()
    try
        tell application "Spotify"
            -- Restore the volume to the level supplied to the parameter.
            set sound volume to currentVolume
        end tell
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "unmute()")
    end try
    return
end unmute

on isAnAd()
    local currentTrackPopularity, currentTrackDuration
    set currentTrackPopularity to null
    set currentTrackDuration to null
    # Nesting "try" blocks is the only way to handle multiple errors in AppleScript
    try
        try
            try
                tell application "Spotify"
                    set currentTrackPopularity to popularity of current track
                    set currentTrackDuration to duration of current track
                end tell
            on error number -1728
                # Ignoring "can't get current track" error
            end try
        on error number -609
            # Ignoring the "invalid connection errors"
        end try
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "isAnAd()")
        return false
    end try
    if ((currentTrackPopularity ≠ null and currentTrackDuration ≠ null) and (currentTrackPopularity = 0 and currentTrackDuration ≤ 40)) then
        return true
    else
        return false
    end if
end isAnAd

on isPlaying()
    local playerState
    set playerState to null
    # Nesting "try" blocks is the only way to handle multiple errors in AppleScript
    try
        try
            tell application "Spotify"
                set playerState to player state
            end tell
        on error -609
            # Ignoring the "invalid connection" errors
        end try
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "isRunning()")
        return false
    end try
    if (playerState ≠ null and playerState = «constant ****kPSP») then
        return true
    else
        return false
    end if
end isPlaying

on isRunning()
    local spotifyProcesses
    # Nesting "try" blocks is the only way to handle multiple errors in AppleScript
    try
        try
            tell application "System Events"
                set spotifyProcesses to (count of (every process whose name is "Spotify"))
            end tell
        on error number -609
            # Ignoring the "invalid connection" errors
        end try
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "isRunning()")
        return false
    end try
    if spotifyProcesses is 1 then
        return true
    else
        return false
    end if
end isRunning

on addToLoginItems()
    try
        tell application "System Events"
            -- Add Spotifree to the Login Items.
            make login item at end with properties {name:"Spotifree", path:"/Applications/Spotifree.app", hidden:true}
        end tell
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "addToLoginItems()")
        return
    end try
end addToLoginItems

on isTheFirstRun()
    local isPrefFileExists, prefFilePath
    set prefFilePath to "~/Library/Preferences/com.ArtemGordinsky.Spotifree"
    try
        tell application "System Events"
            if exists file prefFilePath then
                set isPrefFileExists to true
            else
                set isPrefFileExists to false
            end if
        end tell
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "isTheFirstRun()")
        return true
    end try
    # "not" works like a bang sign here
    return not isPrefFileExists
end isTheFirstRun

on isInLoginItems()
    try
        tell application "System Events"
            if login item "Spotifree" exists then
                return true
            else
                return false
            end if
        end tell
    on error errorMessage number errorNumber
        my log_error(errorNumber, errorMessage, "isInLoginItems()")
        return false
    end try
end isInLoginItems

on log_message(message)
    local content
    set content to (return & ¬
        "-----------------------------------------------------------" & ¬
        return & my dateAndTime() & return & message & return ¬
        & "-----------------------------------------------------------" & return)
    set log_file to ("Mountain Lion:Users:Mephistopelus:Developer:The Stuff:AppleScript:SpotiFree:Spotifree_log.txt")
    my write_to_file(content, log_file, true)
end log_message

on log_error(error_number, error_message, _where)
    local content
    if (debug) then
        set content to (return & "-----------------------------------------------------------" & ¬
            return & my dateAndTime() & return & "Error number: " & error_number ¬
            & return & "Error message: " & error_message & return & ¬
            "Where: " & _where & return ¬
            & "-----------------------------------------------------------" & return)
        set log_file to ("Mountain Lion:Users:Mephistopelus:Developer:The Stuff:AppleScript:SpotiFree:Spotifree_log.txt")
        my write_to_file(content, log_file, true)
    end if
end log_error

on write_to_file(this_data, target_file, append_data) -- (string, file path as string, boolean)
    try
        set the target_file to the target_file as text
        set the open_target_file to ¬
            open for access file target_file with write permission
        if append_data is false then ¬
            set eof of the open_target_file to 0
        write this_data to the open_target_file starting at eof
        close access the open_target_file
        return true
    on error
        try
            close access file target_file
        end try
    end try
end write_to_file

on dateAndTime()
    set currentDateAndTime to (current date) as string
    return currentDateAndTime
end dateAndTime


Need to make this example more generic.

Need to make this example more generic.

set broker to "h8.opera.com"
set workers to {"h6.opera.com", "h8.opera.com", "h9.opera.com", "h10.opera.com"}
tell application "iTerm"
    activate
    set myterm to (make new terminal)
    tell myterm
        set number of columns to 80
        set number of rows to 50
        repeat with workerhost in workers
            set worker to (make new session at the end of sessions)
            tell worker
                set name to workerhost
                set foreground color to "white"
                set background color to "black"
                set transparency to 0.1
                exec command "/bin/sh -i"
                write text "ssh root@" & workerhost & " 'tail -f /var/log/celeryd.log'"
            end tell
        end repeat
        set rabbit to (make new session at the end of sessions)
        tell rabbit
            set name to "rabbit.log"
            set foreground color to "white"
            set background color to "black"
            set transparency to 0.1
            exec command "/bin/sh -i"
            write text "ssh root@" & broker & " 'tail -f /var/log/rabbitmq/rabbit.log'"
        end tell
        tell the first session
            activate
        end tell
    end tell
end tell

set broker to "h8.opera.com"
set workers to {"h8.opera.com", "h9.opera.com", "h10.opera.com"}
tell application "iTerm"
    activate
    set myterm to (make new terminal)
    tell myterm
        set number of columns to 80
        set number of rows to 50
        repeat with workerhost in workers
            set worker to (make new session at the end of sessions)
            tell worker
                set name to workerhost
                set foreground color to "white"
                set background color to "black"
                set transparency to 0.1
                exec command "/bin/sh -i"
                write text "ssh root@" & workerhost & " 'tail -f /var/log/celeryd.log'"
            end tell
        end repeat
        set rabbit to (make new session at the end of sessions)
        tell rabbit
            set name to "rabbit.log"
            set foreground color to "white"
            set background color to "black"
            set transparency to 0.1
            exec command "/bin/sh -i"
            write text "ssh root@" & broker & " 'tail -f /var/log/rabbitmq/rabbit.log'"
        end tell
        tell the first session
            activate
        end tell
    end tell
end tell