update

update

-- 2 machines per experiment
global num_experiments
set num_experiments to 15

on open_window_tabs()
	tell application "Terminal"
		activate
		-- Open a new window
		--   http://superuser.com/questions/195633/applescript-to-open-a-new-terminal-window-in-current-space
		do script "clear"
		-- set currentWindow to front window

		-- Open new tabs
		repeat (num_experiments * 2 - 1) times
			tell application "System Events" to tell process "Terminal" to keystroke "t" using command down
		end repeat
	end tell
end open_window_tabs

on ssh()
	tell application "Terminal"
		set currentWindow to front window

		-- Without the delay, some connections are not made.
		set delay_after_ssh to 0.5

		set tab_id to 0
		set s_id to 0
		repeat until tab_id is (num_experiments * 2)
			set tab_id to tab_id + 1
			set cmd to "sshs" & s_id
			do script (cmd) in tab tab_id of currentWindow
			delay delay_after_ssh

			set tab_id to tab_id + 1
			set cmd to "sshc" & s_id
			do script (cmd) in tab tab_id of currentWindow
			delay delay_after_ssh

			set s_id to s_id + 1
		end repeat
	end tell
end ssh

on screen()
	tell application "Terminal"
		set currentWindow to front window

		set tab_id to 0
		repeat until tab_id is (num_experiments * 2)
			set tab_id to tab_id + 1
			set cmd to "screen"
			do script (cmd) in tab tab_id of currentWindow
			set cmd to " "
			do script (cmd) in tab tab_id of currentWindow
		end repeat
	end tell
end screen

on install_pkgs_0()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 0
		repeat until tab_id is (num_experiments * 2)
			set tab_id to tab_id + 1
			set cmd to "sudo add-apt-repository -y ppa:webupd8team/java"
			set cmd to cmd & " && sudo apt-get update"
			set cmd to cmd & " && sudo apt-get install oracle-java8-installer git ctags ant htop tree maven gnuplot-nox ntp ioping realpath make gcc cmake g++ libboost-dev libboost-system-dev libboost-timer-dev collectl -y"
			set cmd to cmd & " && sudo apt-get autoremove -y vim-tiny"
			set cmd to cmd & " && mkdir -p ~/work"
			set cmd to cmd & " && cd ~/work"
			set cmd to cmd & " && git clone https://github.com/hoytech/vmtouch.git"
			set cmd to cmd & " && cd vmtouch"
			set cmd to cmd & " && make -j"
			set cmd to cmd & " && sudo make install"
			set cmd to cmd & " && sudo service ntp stop"
			set cmd to cmd & " && sudo ntpdate -bv 0.ubuntu.pool.ntp.org"
			set cmd to cmd & " && sudo service ntp start"
			set cmd to cmd & " && cd ~/work"
			set cmd to cmd & " && git clone git@github.com:hobinyoon/linux-home.git"
			set cmd to cmd & " && cd linux-home"
			set cmd to cmd & " && ./setup-linux.sh"
			do script (cmd) in tab tab_id of currentWindow
		end repeat
	end tell
end install_pkgs_0

on install_pkgs_1()
	tell application "Terminal" to activate
	set tab_id to 0
	repeat until tab_id is (num_experiments * 2)
		set tab_id to tab_id + 1
		tell application "System Events" to key code 36 -- enter
		delay 0.2
		tell application "System Events" to key code 123 -- left
		tell application "System Events" to key code 36 -- enter
		tell application "System Events" to key code 124 using {shift down, command down} -- move to the next tab
		delay 0.1
	end repeat
end install_pkgs_1

-- Sometimes the previous step takes forever. Need to make sure.
on install_pkgs_2()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 0
		repeat until tab_id is (num_experiments * 2)
			set tab_id to tab_id + 1
			set cmd to "yes"
			do script (cmd) in tab tab_id of currentWindow
			delay 0.1
		end repeat
	end tell
end install_pkgs_2

on killall_screen()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 0
		repeat until tab_id is (num_experiments * 2)
			set tab_id to tab_id + 1
			set cmd to "killall screen"
			do script (cmd) in tab tab_id of currentWindow
		end repeat
	end tell
end killall_screen

on exit_screen()
	tell application "Terminal" to activate
	set tab_id to 0
	repeat until tab_id is num_experiments
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "d" using {control down}
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "X"

		tell application "System Events" to keystroke "d" using {control down}
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "X"

		tell application "System Events" to keystroke "d" using {control down}
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "X"

		tell application "System Events" to keystroke "d" using {control down}
		tell application "System Events" to keystroke "d" using {control down}
		delay 0.1

		-- Move to the right
		tell application "System Events" to key code 124 using {shift down, command down}

		tell application "System Events" to keystroke "d" using {control down}
		tell application "System Events" to keystroke "d" using {control down}
		delay 0.1

		-- Move to the right
		tell application "System Events" to key code 124 using {shift down, command down}
	end repeat
end exit_screen

on server_screen_split_htop()
	tell application "Terminal" to activate
	set tab_id to 0
	repeat until tab_id is num_experiments
		set tab_id to tab_id + 1

		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "S"
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "c"
		tell application "System Events" to keystroke "htop"
		tell application "System Events" to key code 36

		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "S"
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "c"
		tell application "System Events" to keystroke "watch -n 0.5 \"free -mt\""
		tell application "System Events" to key code 36

		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "S"
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "c"

		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab

		-- Move 2 to the right, since S and C take turns.
		--   124: right arrow
		tell application "System Events" to key code 124 using {shift down, command down}
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat
end server_screen_split_htop

on client_screen_split_htop()
	tell application "Terminal" to activate
	set tab_id to 0
	-- Start from the 2nd tab, which is the first client tab
	tell application "System Events" to key code 124 using {shift down, command down}
	repeat until tab_id is num_experiments
		set tab_id to tab_id + 1

		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "S"
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "c"
		tell application "System Events" to keystroke "htop"
		tell application "System Events" to key code 36

		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab

		-- Move 2 to the right, since S and C take turns.
		--   124: right arrow
		tell application "System Events" to key code 124 using {shift down, command down}
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat

	-- Set the current tab to the 1st one
	tell application "System Events" to key code 123 using {shift down, command down}

end client_screen_split_htop

on build_cass_pressuremem_loadgen()
	tell application "Terminal"
		set currentWindow to front window

		-- Without the delay, some connections are not made.
		set delay_after_ssh to 0.2

		set tab_id to 0
		set s_id to 0
		repeat until tab_id is (num_experiments * 2)
			set tab_id to tab_id + 1
			set cmd to "cd ~/work && git clone git@github.com:hobinyoon/apache-cassandra-2.2.3-src.git && ln -s ~/work/apache-cassandra-2.2.3-src ~/work/cassandra && cd ~/work/cassandra/mtdb/tools/pressure-memory && mkdir -p .build && cd .build && cmake .. && make -j && cdcass && time ant"
			do script (cmd) in tab tab_id of currentWindow
			delay delay_after_ssh

			set tab_id to tab_id + 1
			set cmd to "cd ~/work && git clone git@github.com:hobinyoon/apache-cassandra-2.2.3-src.git && ln -s ~/work/apache-cassandra-2.2.3-src ~/work/cassandra && cd ~/work/cassandra/mtdb/loadgen && ./loadgen"
			do script (cmd) in tab tab_id of currentWindow
			delay delay_after_ssh

			set s_id to s_id + 1
		end repeat
	end tell
end build_cass_pressuremem_loadgen

on git_pull()
	tell application "Terminal" to activate
	set tab_id to 0
	repeat until tab_id is (num_experiments * 2)
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "c" using {control down}

		-- Move to the next tab
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat

	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 0
		repeat until tab_id is (num_experiments * 2)
			set tab_id to tab_id + 1
			set cmd to "cdcass && git pull"
			do script (cmd) in tab tab_id of currentWindow
		end repeat
	end tell
end git_pull

on server_format_ebs_mag_mount_dev_prepare_dirs_cass_data_to_local_ssd_cold_to_ebs_mag()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_experiments * 2)
			set cmd to "sudo mkfs.ext4 -m 0 /dev/xvdd"
			set cmd to cmd & " && sudo cp ~/work/cassandra/mtdb/ec2-tools/etc-fstab /etc/fstab"
			set cmd to cmd & " && sudo umount /mnt"
			set cmd to cmd & " && sudo mkdir -p /mnt/local-ssd"
			set cmd to cmd & " && sudo mount /mnt/local-ssd"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/local-ssd"
			set cmd to cmd & " && mkdir /mnt/local-ssd/cass-data"
			set cmd to cmd & " && mkdir ~/cass-data-vol"
			set cmd to cmd & " && sudo ln -s ~/cass-data-vol /mnt/ebs-ssd-gp2"
			set cmd to cmd & " && mkdir /mnt/ebs-ssd-gp2/cass-data"
			set cmd to cmd & " && sudo mkdir -p /mnt/ebs-mag"
			set cmd to cmd & " && sudo mount /mnt/ebs-mag"
			set cmd to cmd & " && sudo mkdir /mnt/ebs-mag/cass-data"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/ebs-mag"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/ebs-mag/cass-data"
			set cmd to cmd & " && sudo mkdir -p /mnt/ebs-ssd-gp2/mtdb-cold"
			set cmd to cmd & " && (sudo mkdir -p /mnt/ebs-mag/mtdb-cold || true)"
			set cmd to cmd & " && sudo ln -s /mnt/ebs-mag /mnt/cold-storage"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/ebs-ssd-gp2"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/cold-storage"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/cold-storage/mtdb-cold"
			set cmd to cmd & " && mkdir -p ~/work/cassandra/mtdb/logs/collectl"
			set cmd to cmd & " && ln -s /mnt/local-ssd/cass-data ~/work/cassandra/data"
			do script (cmd) in tab tab_id of currentWindow

			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end server_format_ebs_mag_mount_dev_prepare_dirs_cass_data_to_local_ssd_cold_to_ebs_mag

on server_mount_dev_prepare_dirs_cass_data_to_ebs_ssd()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_experiments * 2)
			set cmd to "sudo cp ~/work/cassandra/mtdb/ec2-tools/etc-fstab /etc/fstab"
			set cmd to cmd & " && sudo umount /mnt"
			set cmd to cmd & " && sudo mkdir -p /mnt/local-ssd"
			set cmd to cmd & " && sudo mount /mnt/local-ssd"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/local-ssd"
			set cmd to cmd & " && mkdir /mnt/local-ssd/cass-data"
			set cmd to cmd & " && mkdir ~/cass-data-vol"
			set cmd to cmd & " && sudo ln -s ~/cass-data-vol /mnt/ebs-ssd-gp2"
			set cmd to cmd & " && mkdir /mnt/ebs-ssd-gp2/cass-data"
			set cmd to cmd & " && sudo mkdir -p /mnt/ebs-ssd-gp2/mtdb-cold"
			set cmd to cmd & " && sudo ln -s /mnt/ebs-ssd-gp2 /mnt/cold-storage"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/ebs-ssd-gp2"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/cold-storage"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/cold-storage/mtdb-cold"
			set cmd to cmd & " && mkdir -p ~/work/cassandra/mtdb/logs/collectl"
			set cmd to cmd & " && ln -s /mnt/ebs-ssd-gp2/cass-data ~/work/cassandra/data"
			do script (cmd) in tab tab_id of currentWindow

			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end server_mount_dev_prepare_dirs_cass_data_to_ebs_ssd

on server_mount_dev_prepare_dirs_cass_data_to_local_ssd_cold_data_to_ebs_ssd()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_experiments * 2)
			set cmd to "sudo cp ~/work/cassandra/mtdb/ec2-tools/etc-fstab /etc/fstab"
			set cmd to cmd & " && sudo umount /mnt"
			set cmd to cmd & " && sudo mkdir -p /mnt/local-ssd"
			set cmd to cmd & " && sudo mount /mnt/local-ssd"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/local-ssd"
			set cmd to cmd & " && mkdir /mnt/local-ssd/cass-data"
			set cmd to cmd & " && mkdir ~/cass-data-vol"
			set cmd to cmd & " && sudo ln -s ~/cass-data-vol /mnt/ebs-ssd-gp2"
			set cmd to cmd & " && mkdir /mnt/ebs-ssd-gp2/cass-data"
			set cmd to cmd & " && sudo mkdir -p /mnt/ebs-ssd-gp2/mtdb-cold"
			set cmd to cmd & " && sudo ln -s /mnt/ebs-ssd-gp2 /mnt/cold-storage"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/ebs-ssd-gp2"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/cold-storage"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/cold-storage/mtdb-cold"
			set cmd to cmd & " && mkdir -p ~/work/cassandra/mtdb/logs/collectl"
			set cmd to cmd & " && ln -s /mnt/local-ssd/cass-data ~/work/cassandra/data"
			do script (cmd) in tab tab_id of currentWindow

			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end server_mount_dev_prepare_dirs_cass_data_to_local_ssd_cold_data_to_ebs_ssd

on server_edit_cassandra_yaml()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_experiments * 2)
			set cmd to "sed -i -r 's/^    migrate_to_cold_storage:.+/    migrate_to_cold_storage: true/' ~/work/cassandra/conf/cassandra.yaml"
			do script (cmd) in tab tab_id of currentWindow
			set cmd to "sed -i -r 's/^    tablet_coldness_threshold:.+/    tablet_coldness_threshold: 75/' ~/work/cassandra/conf/cassandra.yaml"
			do script (cmd) in tab tab_id of currentWindow
			set cmd to "grep migrate_to_cold_storage: ~/work/cassandra/conf/cassandra.yaml"
			do script (cmd) in tab tab_id of currentWindow
			set cmd to "grep tablet_coldness_threshold: ~/work/cassandra/conf/cassandra.yaml"
			do script (cmd) in tab tab_id of currentWindow

			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end server_edit_cassandra_yaml

on client_edit_cassandra_yaml()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 2
		repeat until tab_id > (num_experiments * 2)
			set cmd to "sed -i -r 's/^        num_writes_per_simulation_time_mins:.+/        num_writes_per_simulation_time_mins: " & (tab_id * 5000) & "/' ~/work/cassandra/conf/cassandra.yaml"
			do script (cmd) in tab tab_id of currentWindow
			set cmd to "grep num_writes_per_simulation_time_mins: ~/work/cassandra/conf/cassandra.yaml"
			do script (cmd) in tab tab_id of currentWindow

			-- Go to the next client tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end client_edit_cassandra_yaml

on save_screen_layout()
	tell application "Terminal" to activate
	set tab_id to 0
	repeat until tab_id is (num_experiments * 2)
		set tab_id to tab_id + 1

		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke ":layout save default"
		tell application "System Events" to key code 36

		-- Move to the next tab
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat
end save_screen_layout

-- One-time fix. Won't need again
on mkdir_collectl()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_experiments * 2)
			set cmd to "mkdir ~/work/cassandra/mtdb/logs/collectl"
			do script (cmd) in tab tab_id of currentWindow
			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end mkdir_collectl

on run_server()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_experiments * 2)
			set cmd to "sudo -- sh -c 'echo 1 > /proc/sys/vm/drop_caches' && cdcass && time ant && rm -rf ~/work/cassandra/data/* && rm -rf /mnt/cold-storage/mtdb-cold/* && (killall mon-num-cass-threads.sh >/dev/null 2>&1 || true) && (~/work/cassandra/mtdb/tools/mon-num-cass-threads.sh &) && (killall collectl >/dev/null 2>&1 || true) && ((collectl -i 1 -sCDN -oTm > ~/work/cassandra/mtdb/logs/collectl/collectl-`date +'%y%m%d-%H%M%S'` 2>/dev/null) &) && bin/cassandra -f | grep --color -E '^|MTDB:'"
			do script (cmd) in tab tab_id of currentWindow
			-- Give enough delay to make sure the logs have different datetimes.
			delay 3

			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end run_server

on server_pressure_memory()
	tell application "Terminal" to activate

	set tab_id to 0
	repeat until tab_id is num_experiments
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		-- Move to the next server tab
		tell application "System Events" to key code 124 using {shift down, command down}
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat

	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_experiments * 2)
			set cmd to "cd ~/work/cassandra/mtdb/tools/pressure-memory/.build"
			set cmd to cmd & " && (./pressure-memory &)"
			set cmd to cmd & " && watchsstables"
			do script (cmd) in tab tab_id of currentWindow

			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell

	set tab_id to 0
	repeat until tab_id is num_experiments
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		-- Move to the next server tab
		tell application "System Events" to key code 124 using {shift down, command down}
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat
end server_pressure_memory

on client_run_loadgen()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 2
		repeat until tab_id > (num_experiments * 2)
			set cmd to "cd ~/work/cassandra/mtdb/loadgen && ./create-db.sh && ./loadgen"
			do script (cmd) in tab tab_id of currentWindow
			-- Make sure we get different exp datetime
			delay 2
			-- Go to the next client tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end client_run_loadgen

on screen_detach()
	tell application "Terminal" to activate
	set tab_id to 0
	repeat until tab_id is (num_experiments * 2)
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "d"
		delay 0.1
		-- Move to the next tab
		tell application "System Events" to key code 124 using {shift down, command down}
	end repeat
end screen_detach

on screen_reattach()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_experiments * 2)
			set cmd to "screen -r"
			do script (cmd) in tab tab_id of currentWindow
			-- Go to the next tab
			set tab_id to tab_id + 1
		end repeat
	end tell
end screen_reattach

on server_get_client_logs_and_process()
	tell application "Terminal" to activate

	set tab_id to 0
	repeat until tab_id is num_experiments
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "c" using {control down}

		-- Move to the next server tab
		tell application "System Events" to key code 124 using {shift down, command down}
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat

	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_experiments * 2)
			set cmd to "(killall pressure-memory > /dev/null 2>&1 || true) && (sudo killall collectl > /dev/null 2>&1 || true) && (sudo killall mon-num-cass-threads.sh > /dev/null 2>&1 || true) && rsync -ave \"ssh -o StrictHostKeyChecking=no\" $CASSANDRA_CLIENT_ADDR:work/cassandra/mtdb/logs/loadgen ~/work/cassandra/mtdb/logs && cd ~/work/cassandra/mtdb/process-log/calc-cost-latency-plot-tablet-timeline && (\\rm *.pdf >/dev/null 2>&1 || true) && ./plot-cost-latency-tablet-timelines.py && du -hs ~/work/cassandra/data/"
			do script (cmd) in tab tab_id of currentWindow
			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell

	set tab_id to 0
	repeat until tab_id is num_experiments
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		-- Move to the next server tab
		tell application "System Events" to key code 124 using {shift down, command down}
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat
end server_get_client_logs_and_process

-- One-time fix. Won't need again
on server_rename_client_log()
	tell application "Terminal" to activate

	set tab_id to 0
	repeat until tab_id is num_experiments
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		-- Move to the next server tab
		tell application "System Events" to key code 124 using {shift down, command down}
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat

	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_experiments * 2)
			-- I don't like the float division. oh well.
			set cmd to "mv ~/work/cassandra/mtdb/logs/loadgen/160302-023811 ~/work/cassandra/mtdb/logs/loadgen/160302-023811-" & ((tab_id - 1) / 2) & " && mv ~/work/cassandra/mtdb/logs/cassandra/160302-023811 ~/work/cassandra/mtdb/logs/cassandra/160302-023811-" & ((tab_id - 1) / 2)
			do script (cmd) in tab tab_id of currentWindow
			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell

	set tab_id to 0
	repeat until tab_id is num_experiments
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		-- Move to the next server tab
		tell application "System Events" to key code 124 using {shift down, command down}
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat
end server_rename_client_log

-- One-time use.
on server_kill_monitor_processes()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_experiments * 2)
			set cmd to "(killall pressure-memory > /dev/null 2>&1 || true) && (sudo killall collectl > /dev/null 2>&1 || true) && (sudo killall mon-num-cass-threads.sh > /dev/null 2>&1 || true)"
			do script (cmd) in tab tab_id of currentWindow
			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end server_kill_monitor_processes

on server_switch_data_dir_to_ebs_mag()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_experiments * 2)
			set cmd to "cdcass"
			set cmd to cmd & " && \\rm data"
			set cmd to cmd & " && ln -s /mnt/ebs-mag/cass-data data"
			do script (cmd) in tab tab_id of currentWindow
			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end server_switch_data_dir_to_ebs_mag

on server_switch_data_dir_to_local_ssd()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_experiments * 2)
			set cmd to "cdcass"
			set cmd to cmd & " && \\rm data"
			set cmd to cmd & " && ln -s /mnt/local-ssd/cass-data data"
			do script (cmd) in tab tab_id of currentWindow
			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end server_switch_data_dir_to_local_ssd

on open_window_tabs_ssh_screen()
	my open_window_tabs()
	my ssh()
	my screen()
end open_window_tabs_ssh_screen

on run_all()
	my open_window_tabs_ssh_screen()

	my install_pkgs_0()
	my install_pkgs_1()
	my install_pkgs_2()

	-- Not a normal process. In case something goes wrong.
	-- my exit_screen()

	-- Need to logout and login again to make the new .bashrc in effect
	-- Hit ^D until all tabs are closed
	my open_window_tabs_ssh_screen()

	-- These can be grouped
	my server_screen_split_htop()
	my client_screen_split_htop()
	my build_cass_pressuremem_loadgen()

	my save_screen_layout()
	my screen_detach()
	my screen_reattach()

	-- Run either of these, depending on what dev you have
	my server_format_ebs_mag_mount_dev_prepare_dirs_cass_data_to_local_ssd_cold_to_ebs_mag()
	my server_mount_dev_prepare_dirs_cass_data_to_ebs_ssd()
	my server_mount_dev_prepare_dirs_cass_data_to_local_ssd_cold_data_to_ebs_ssd()

	-- Switch data directory to ebs mag
	-- my server_switch_data_dir_to_ebs_mag()

	-- Make sure which experiment you want, by editing migrate_to_cold_storage
	my server_edit_cassandra_yaml()
	my client_edit_cassandra_yaml()

	-- This takes some time to make sure each server has different exp datetime
	my run_server()

	-- Wait till all servers are ready before pressuring memory
	my server_pressure_memory()

	-- This takes some time too.
	my client_run_loadgen()

	-- Detach screen to save network traffic while running experiments
	my save_screen_layout()
	my screen_detach()

	-- May want to check the last tabs to see if the system saturates or cold sstables are created

	-- When done, reattach the screen and process the logs
	my screen_reattach()
	my server_get_client_logs_and_process()

	-- Switch data directory to ebs mag
	-- my server_switch_data_dir_to_ebs_mag()
end run_all

global num_servers
set num_servers to 10

on open_window_tabs()
	tell application "Terminal"
		activate
		-- Open a new window
		--   http://superuser.com/questions/195633/applescript-to-open-a-new-terminal-window-in-current-space
		do script "clear"
		-- set currentWindow to front window

		-- Open new tabs
		repeat (num_servers * 2 - 1) times
			tell application "System Events" to tell process "Terminal" to keystroke "t" using command down
		end repeat
	end tell
end open_window_tabs

on ssh()
	tell application "Terminal"
		set currentWindow to front window

		-- Without the delay, some connections are not made.
		set delay_after_ssh to 0.5

		set tab_id to 0
		set s_id to 0
		repeat until tab_id is (num_servers * 2)
			set tab_id to tab_id + 1
			set cmd to "sshs" & s_id
			do script (cmd) in tab tab_id of currentWindow
			delay delay_after_ssh

			set tab_id to tab_id + 1
			set cmd to "sshc" & s_id
			do script (cmd) in tab tab_id of currentWindow
			delay delay_after_ssh

			set s_id to s_id + 1
		end repeat
	end tell
end ssh

on screen()
	tell application "Terminal"
		set currentWindow to front window

		set tab_id to 0
		repeat until tab_id is (num_servers * 2)
			set tab_id to tab_id + 1
			set cmd to "screen"
			do script (cmd) in tab tab_id of currentWindow
			set cmd to " "
			do script (cmd) in tab tab_id of currentWindow
		end repeat
	end tell
end screen

on install_pkgs_0()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 0
		repeat until tab_id is (num_servers * 2)
			set tab_id to tab_id + 1
			set cmd to "sudo add-apt-repository -y ppa:webupd8team/java"
			set cmd to cmd & " && sudo apt-get update"
			set cmd to cmd & " && sudo apt-get install oracle-java8-installer git ctags ant htop tree maven gnuplot-nox ntp ioping realpath make gcc cmake g++ libboost-dev libboost-system-dev libboost-timer-dev collectl -y"
			set cmd to cmd & " && sudo apt-get autoremove -y vim-tiny"
			set cmd to cmd & " && mkdir -p ~/work"
			set cmd to cmd & " && cd ~/work"
			set cmd to cmd & " && git clone https://github.com/hoytech/vmtouch.git"
			set cmd to cmd & " && cd vmtouch"
			set cmd to cmd & " && make -j"
			set cmd to cmd & " && sudo make install"
			set cmd to cmd & " && sudo service ntp stop"
			set cmd to cmd & " && sudo ntpdate -bv 0.ubuntu.pool.ntp.org"
			set cmd to cmd & " && sudo service ntp start"
			set cmd to cmd & " && cd ~/work"
			set cmd to cmd & " && git clone git@github.com:hobinyoon/linux-home.git"
			set cmd to cmd & " && cd linux-home"
			set cmd to cmd & " && ./setup-linux.sh"
			do script (cmd) in tab tab_id of currentWindow
		end repeat
	end tell
end install_pkgs_0

on install_pkgs_1()
	tell application "Terminal" to activate
	set tab_id to 0
	repeat until tab_id is (num_servers * 2)
		set tab_id to tab_id + 1
		tell application "System Events" to key code 36 -- enter
		delay 0.2
		tell application "System Events" to key code 123 -- left
		tell application "System Events" to key code 36 -- enter
		tell application "System Events" to key code 124 using {shift down, command down} -- move to the next tab
		delay 0.1
	end repeat
end install_pkgs_1

-- Sometimes the previous step takes forever. Need to make sure.
on install_pkgs_2()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 0
		repeat until tab_id is (num_servers * 2)
			set tab_id to tab_id + 1
			set cmd to "yes"
			do script (cmd) in tab tab_id of currentWindow
			delay 0.1
		end repeat
	end tell
end install_pkgs_2

on killall_screen()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 0
		repeat until tab_id is (num_servers * 2)
			set tab_id to tab_id + 1
			set cmd to "killall screen"
			do script (cmd) in tab tab_id of currentWindow
		end repeat
	end tell
end killall_screen

on exit_screen()
	tell application "Terminal" to activate
	set tab_id to 0
	repeat until tab_id is num_servers
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "d" using {control down}
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "X"

		tell application "System Events" to keystroke "d" using {control down}
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "X"

		tell application "System Events" to keystroke "d" using {control down}
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "X"

		tell application "System Events" to keystroke "d" using {control down}
		tell application "System Events" to keystroke "d" using {control down}
		delay 0.1

		-- Move to the right
		tell application "System Events" to key code 124 using {shift down, command down}

		tell application "System Events" to keystroke "d" using {control down}
		tell application "System Events" to keystroke "d" using {control down}
		delay 0.1

		-- Move to the right
		tell application "System Events" to key code 124 using {shift down, command down}
	end repeat
end exit_screen

on server_screen_split_htop()
	tell application "Terminal" to activate
	set tab_id to 0
	repeat until tab_id is num_servers
		set tab_id to tab_id + 1

		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "S"
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "c"
		tell application "System Events" to keystroke "htop"
		tell application "System Events" to key code 36

		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "S"
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "c"
		tell application "System Events" to keystroke "watch -n 0.5 \"free -mt\""
		tell application "System Events" to key code 36

		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "S"
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "c"

		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab

		-- Move 2 to the right, since S and C take turns.
		--   124: right arrow
		tell application "System Events" to key code 124 using {shift down, command down}
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat
end server_screen_split_htop

on client_screen_split_htop()
	tell application "Terminal" to activate
	set tab_id to 0
	-- Start from the 2nd tab, which is the first client tab
	tell application "System Events" to key code 124 using {shift down, command down}
	repeat until tab_id is num_servers
		set tab_id to tab_id + 1

		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "S"
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "c"
		tell application "System Events" to keystroke "htop"
		tell application "System Events" to key code 36

		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab

		-- Move 2 to the right, since S and C take turns.
		--   124: right arrow
		tell application "System Events" to key code 124 using {shift down, command down}
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat

	-- Set the current tab to the 1st one
	tell application "System Events" to key code 123 using {shift down, command down}

end client_screen_split_htop

on build_cass_pressuremem_loadgen()
	tell application "Terminal"
		set currentWindow to front window

		-- Without the delay, some connections are not made.
		set delay_after_ssh to 0.2

		set tab_id to 0
		set s_id to 0
		repeat until tab_id is (num_servers * 2)
			set tab_id to tab_id + 1
			set cmd to "cd ~/work && git clone git@github.com:hobinyoon/apache-cassandra-2.2.3-src.git && ln -s ~/work/apache-cassandra-2.2.3-src ~/work/cassandra && cd ~/work/cassandra/mtdb/tools/pressure-memory && mkdir -p .build && cd .build && cmake .. && make -j && cdcass && time ant"
			do script (cmd) in tab tab_id of currentWindow
			delay delay_after_ssh

			set tab_id to tab_id + 1
			set cmd to "cd ~/work && git clone git@github.com:hobinyoon/apache-cassandra-2.2.3-src.git && ln -s ~/work/apache-cassandra-2.2.3-src ~/work/cassandra && cd ~/work/cassandra/mtdb/loadgen && ./loadgen"
			do script (cmd) in tab tab_id of currentWindow
			delay delay_after_ssh

			set s_id to s_id + 1
		end repeat
	end tell
end build_cass_pressuremem_loadgen

on git_pull()
	tell application "Terminal" to activate
	set tab_id to 0
	repeat until tab_id is (num_servers * 2)
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "c" using {control down}

		-- Move to the next tab
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat

	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 0
		repeat until tab_id is (num_servers * 2)
			set tab_id to tab_id + 1
			set cmd to "cdcass && git pull"
			do script (cmd) in tab tab_id of currentWindow
		end repeat
	end tell
end git_pull

on server_format_ebs_mag_mount_dev_prepare_dirs_cass_data_to_local_ssd_cold_to_ebs_mag()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_servers * 2)
			set cmd to "sudo mkfs.ext4 -m 0 /dev/xvdd"
			set cmd to cmd & " && sudo cp ~/work/cassandra/mtdb/ec2-tools/etc-fstab /etc/fstab"
			set cmd to cmd & " && sudo umount /mnt"
			set cmd to cmd & " && sudo mkdir -p /mnt/local-ssd"
			set cmd to cmd & " && sudo mount /mnt/local-ssd"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/local-ssd"
			set cmd to cmd & " && mkdir /mnt/local-ssd/cass-data"
			set cmd to cmd & " && mkdir ~/cass-data-vol"
			set cmd to cmd & " && sudo ln -s ~/cass-data-vol /mnt/ebs-ssd-gp2"
			set cmd to cmd & " && mkdir /mnt/ebs-ssd-gp2/cass-data"
			set cmd to cmd & " && sudo mkdir -p /mnt/ebs-mag"
			set cmd to cmd & " && sudo mount /mnt/ebs-mag"
			set cmd to cmd & " && sudo mkdir /mnt/ebs-mag/cass-data"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/ebs-mag"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/ebs-mag/cass-data"
			set cmd to cmd & " && sudo mkdir -p /mnt/ebs-ssd-gp2/mtdb-cold"
			set cmd to cmd & " && (sudo mkdir -p /mnt/ebs-mag/mtdb-cold || true)"
			set cmd to cmd & " && sudo ln -s /mnt/ebs-mag /mnt/cold-storage"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/ebs-ssd-gp2"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/cold-storage"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/cold-storage/mtdb-cold"
			set cmd to cmd & " && mkdir -p ~/work/cassandra/mtdb/logs/collectl"
			set cmd to cmd & " && ln -s /mnt/local-ssd/cass-data ~/work/cassandra/data"
			do script (cmd) in tab tab_id of currentWindow

			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end server_format_ebs_mag_mount_dev_prepare_dirs_cass_data_to_local_ssd_cold_to_ebs_mag

on server_mount_dev_prepare_dirs_cass_data_to_ebs_ssd()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_servers * 2)
			set cmd to "sudo cp ~/work/cassandra/mtdb/ec2-tools/etc-fstab /etc/fstab"
			set cmd to cmd & " && sudo umount /mnt"
			set cmd to cmd & " && sudo mkdir -p /mnt/local-ssd"
			set cmd to cmd & " && sudo mount /mnt/local-ssd"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/local-ssd"
			set cmd to cmd & " && mkdir /mnt/local-ssd/cass-data"
			set cmd to cmd & " && mkdir ~/cass-data-vol"
			set cmd to cmd & " && sudo ln -s ~/cass-data-vol /mnt/ebs-ssd-gp2"
			set cmd to cmd & " && mkdir /mnt/ebs-ssd-gp2/cass-data"
			set cmd to cmd & " && sudo mkdir -p /mnt/ebs-ssd-gp2/mtdb-cold"
			set cmd to cmd & " && sudo ln -s /mnt/ebs-ssd-gp2 /mnt/cold-storage"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/ebs-ssd-gp2"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/cold-storage"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/cold-storage/mtdb-cold"
			set cmd to cmd & " && mkdir -p ~/work/cassandra/mtdb/logs/collectl"
			set cmd to cmd & " && ln -s /mnt/ebs-ssd-gp2/cass-data ~/work/cassandra/data"
			do script (cmd) in tab tab_id of currentWindow

			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end server_mount_dev_prepare_dirs_cass_data_to_ebs_ssd

on server_mount_dev_prepare_dirs_cass_data_to_local_ssd_cold_data_to_ebs_ssd()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_servers * 2)
			set cmd to "sudo cp ~/work/cassandra/mtdb/ec2-tools/etc-fstab /etc/fstab"
			set cmd to cmd & " && sudo umount /mnt"
			set cmd to cmd & " && sudo mkdir -p /mnt/local-ssd"
			set cmd to cmd & " && sudo mount /mnt/local-ssd"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/local-ssd"
			set cmd to cmd & " && mkdir /mnt/local-ssd/cass-data"
			set cmd to cmd & " && mkdir ~/cass-data-vol"
			set cmd to cmd & " && sudo ln -s ~/cass-data-vol /mnt/ebs-ssd-gp2"
			set cmd to cmd & " && mkdir /mnt/ebs-ssd-gp2/cass-data"
			set cmd to cmd & " && sudo mkdir -p /mnt/ebs-ssd-gp2/mtdb-cold"
			set cmd to cmd & " && sudo ln -s /mnt/ebs-ssd-gp2 /mnt/cold-storage"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/ebs-ssd-gp2"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/cold-storage"
			set cmd to cmd & " && sudo chown -R ubuntu /mnt/cold-storage/mtdb-cold"
			set cmd to cmd & " && mkdir -p ~/work/cassandra/mtdb/logs/collectl"
			set cmd to cmd & " && ln -s /mnt/local-ssd/cass-data ~/work/cassandra/data"
			do script (cmd) in tab tab_id of currentWindow

			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end server_mount_dev_prepare_dirs_cass_data_to_local_ssd_cold_data_to_ebs_ssd

on server_edit_cassandra_yaml()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_servers * 2)
			set cmd to "sed -i -r 's/^    migrate_to_cold_storage:.+/    migrate_to_cold_storage: true/' ~/work/cassandra/conf/cassandra.yaml"
			do script (cmd) in tab tab_id of currentWindow
			set cmd to "sed -i -r 's/^    tablet_coldness_threshold:.+/    tablet_coldness_threshold: 75/' ~/work/cassandra/conf/cassandra.yaml"
			do script (cmd) in tab tab_id of currentWindow
			set cmd to "grep migrate_to_cold_storage: ~/work/cassandra/conf/cassandra.yaml"
			do script (cmd) in tab tab_id of currentWindow
			set cmd to "grep tablet_coldness_threshold: ~/work/cassandra/conf/cassandra.yaml"
			do script (cmd) in tab tab_id of currentWindow

			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end server_edit_cassandra_yaml

on client_edit_cassandra_yaml()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 2
		repeat until tab_id > (num_servers * 2)
			set cmd to "sed -i -r 's/^        num_writes_per_simulation_time_mins:.+/        num_writes_per_simulation_time_mins: " & (tab_id * 5000) & "/' ~/work/cassandra/conf/cassandra.yaml"
			do script (cmd) in tab tab_id of currentWindow
			set cmd to "grep num_writes_per_simulation_time_mins: ~/work/cassandra/conf/cassandra.yaml"
			do script (cmd) in tab tab_id of currentWindow

			-- Go to the next client tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end client_edit_cassandra_yaml

on save_screen_layout()
	tell application "Terminal" to activate
	set tab_id to 0
	repeat until tab_id is (num_servers * 2)
		set tab_id to tab_id + 1

		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke ":layout save default"
		tell application "System Events" to key code 36

		-- Move to the next tab
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat
end save_screen_layout

-- One-time fix. Won't need again
on mkdir_collectl()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_servers * 2)
			set cmd to "mkdir ~/work/cassandra/mtdb/logs/collectl"
			do script (cmd) in tab tab_id of currentWindow
			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end mkdir_collectl

on run_server()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_servers * 2)
			set cmd to "sudo -- sh -c 'echo 1 > /proc/sys/vm/drop_caches' && cdcass && time ant && rm -rf ~/work/cassandra/data/* && rm -rf /mnt/cold-storage/mtdb-cold/* && (killall mon-num-cass-threads.sh >/dev/null 2>&1 || true) && (~/work/cassandra/mtdb/tools/mon-num-cass-threads.sh &) && (killall collectl >/dev/null 2>&1 || true) && ((collectl -i 1 -sCDN -oTm > ~/work/cassandra/mtdb/logs/collectl/collectl-`date +'%y%m%d-%H%M%S'` 2>/dev/null) &) && bin/cassandra -f | grep --color -E '^|MTDB:'"
			do script (cmd) in tab tab_id of currentWindow
			-- Give enough delay to make sure the logs have different datetimes.
			delay 3

			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end run_server

on server_pressure_memory()
	tell application "Terminal" to activate

	set tab_id to 0
	repeat until tab_id is num_servers
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		-- Move to the next server tab
		tell application "System Events" to key code 124 using {shift down, command down}
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat

	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_servers * 2)
			set cmd to "cd ~/work/cassandra/mtdb/tools/pressure-memory/.build"
			set cmd to cmd & " && (./pressure-memory &)"
			set cmd to cmd & " && watchsstables"
			do script (cmd) in tab tab_id of currentWindow

			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell

	set tab_id to 0
	repeat until tab_id is num_servers
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		-- Move to the next server tab
		tell application "System Events" to key code 124 using {shift down, command down}
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat
end server_pressure_memory

on client_run_loadgen()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 2
		repeat until tab_id > (num_servers * 2)
			set cmd to "cd ~/work/cassandra/mtdb/loadgen && ./create-db.sh && ./loadgen"
			do script (cmd) in tab tab_id of currentWindow
			-- Make sure we get different exp datetime
			delay 2
			-- Go to the next client tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end client_run_loadgen

on screen_detach()
	tell application "Terminal" to activate
	set tab_id to 0
	repeat until tab_id is (num_servers * 2)
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke "d"
		delay 0.1
		-- Move to the next tab
		tell application "System Events" to key code 124 using {shift down, command down}
	end repeat
end screen_detach

on screen_reattach()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_servers * 2)
			set cmd to "screen -r"
			do script (cmd) in tab tab_id of currentWindow
			-- Go to the next tab
			set tab_id to tab_id + 1
		end repeat
	end tell
end screen_reattach

on server_get_client_logs_and_process()
	tell application "Terminal" to activate

	set tab_id to 0
	repeat until tab_id is num_servers
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "c" using {control down}

		-- Move to the next server tab
		tell application "System Events" to key code 124 using {shift down, command down}
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat

	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_servers * 2)
			set cmd to "(killall pressure-memory > /dev/null 2>&1 || true) && (sudo killall collectl > /dev/null 2>&1 || true) && (sudo killall mon-num-cass-threads.sh > /dev/null 2>&1 || true) && rsync -ave \"ssh -o StrictHostKeyChecking=no\" $CASSANDRA_CLIENT_ADDR:work/cassandra/mtdb/logs/loadgen ~/work/cassandra/mtdb/logs && cd ~/work/cassandra/mtdb/process-log/calc-cost-latency-plot-tablet-timeline && (\\rm *.pdf >/dev/null 2>&1 || true) && ./plot-cost-latency-tablet-timelines.py && du -hs ~/work/cassandra/data/"
			do script (cmd) in tab tab_id of currentWindow
			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell

	set tab_id to 0
	repeat until tab_id is num_servers
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		-- Move to the next server tab
		tell application "System Events" to key code 124 using {shift down, command down}
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat
end server_get_client_logs_and_process

-- One-time fix. Won't need again
on server_rename_client_log()
	tell application "Terminal" to activate

	set tab_id to 0
	repeat until tab_id is num_servers
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		-- Move to the next server tab
		tell application "System Events" to key code 124 using {shift down, command down}
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat

	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_servers * 2)
			-- I don't like the float division. oh well.
			set cmd to "mv ~/work/cassandra/mtdb/logs/loadgen/160302-023811 ~/work/cassandra/mtdb/logs/loadgen/160302-023811-" & ((tab_id - 1) / 2) & " && mv ~/work/cassandra/mtdb/logs/cassandra/160302-023811 ~/work/cassandra/mtdb/logs/cassandra/160302-023811-" & ((tab_id - 1) / 2)
			do script (cmd) in tab tab_id of currentWindow
			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell

	set tab_id to 0
	repeat until tab_id is num_servers
		set tab_id to tab_id + 1
		tell application "System Events" to keystroke "a" using {control down}
		tell application "System Events" to keystroke tab
		-- Move to the next server tab
		tell application "System Events" to key code 124 using {shift down, command down}
		tell application "System Events" to key code 124 using {shift down, command down}
		delay 0.1
	end repeat
end server_rename_client_log

-- One-time use.
on server_kill_monitor_processes()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_servers * 2)
			set cmd to "(killall pressure-memory > /dev/null 2>&1 || true) && (sudo killall collectl > /dev/null 2>&1 || true) && (sudo killall mon-num-cass-threads.sh > /dev/null 2>&1 || true)"
			do script (cmd) in tab tab_id of currentWindow
			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end server_kill_monitor_processes

on server_switch_data_dir_to_ebs_mag()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_servers * 2)
			set cmd to "cdcass"
			set cmd to cmd & " && \\rm data"
			set cmd to cmd & " && ln -s /mnt/ebs-mag/cass-data data"
			do script (cmd) in tab tab_id of currentWindow
			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end server_switch_data_dir_to_ebs_mag

on server_switch_data_dir_to_local_ssd()
	tell application "Terminal"
		set currentWindow to front window
		set tab_id to 1
		repeat until tab_id > (num_servers * 2)
			set cmd to "cdcass"
			set cmd to cmd & " && \\rm data"
			set cmd to cmd & " && ln -s /mnt/local-ssd/cass-data data"
			do script (cmd) in tab tab_id of currentWindow
			-- Go to the next server tab
			set tab_id to tab_id + 2
		end repeat
	end tell
end server_switch_data_dir_to_local_ssd

on open_window_tabs_ssh_screen()
	my open_window_tabs()
	my ssh()
	my screen()
end open_window_tabs_ssh_screen

on run_all()
	my open_window_tabs_ssh_screen()

	my install_pkgs_0()
	my install_pkgs_1()
	my install_pkgs_2()

	-- Not a normal process. In case something goes wrong.
	-- my exit_screen()

	-- Need to logout and login again to make the new .bashrc in effect
	-- Hit ^D until all tabs are closed
	my open_window_tabs_ssh_screen()

	-- These can be grouped
	my server_screen_split_htop()
	my client_screen_split_htop()
	my build_cass_pressuremem_loadgen()

	my save_screen_layout()
	my screen_detach()
	my screen_reattach()

	-- Run either of these, depending on what dev you have
	my server_format_ebs_mag_mount_dev_prepare_dirs_cass_data_to_local_ssd_cold_to_ebs_mag()
	my server_mount_dev_prepare_dirs_cass_data_to_ebs_ssd()
	my server_mount_dev_prepare_dirs_cass_data_to_local_ssd_cold_data_to_ebs_ssd()

	-- Switch data directory to ebs mag
	-- my server_switch_data_dir_to_ebs_mag()

	-- Make sure which experiment you want, by editing migrate_to_cold_storage
	my server_edit_cassandra_yaml()
	my client_edit_cassandra_yaml()

	-- This takes some time to make sure each server has different exp datetime
	my run_server()

	-- Wait till all servers are ready before pressuring memory
	my server_pressure_memory()

	-- This takes some time too.
	my client_run_loadgen()

	-- Detach screen to save network traffic while running experiments
	my save_screen_layout()
	my screen_detach()

	-- May want to check the last tabs to see if the system saturates or cold sstables are created

	-- When done, reattach the screen and process the logs
	my screen_reattach()
	my server_get_client_logs_and_process()

	-- Switch data directory to ebs mag
	-- my server_switch_data_dir_to_ebs_mag()
end run_all


Fix bug where some non-Apple Music songs were missing artwork

The original algorithm assumed that the file extension of every artwork
image was always jpeg. However, some songs (which I did not download
from Apple Music, coincidentally) happened to have png artwork, and so
the algorithm would fail to return the correct artwork path for those
songs.

-- core configuration --

-- configurable options --

-- limit number of songs to improve efficiency
property resultLimit : 15
-- whether or not to retrieve/display album artwork for each result
property albumArtEnabled : true

-- workflow parameters --

-- workflow folders
property libraryFolder : (path to library folder from user domain as text)
property cacheFolder : (libraryFolder & "Caches:")
property alfredWorkflowDataFolder : (cacheFolder & "com.runningwithcrayons.Alfred:Workflow Data:")
property bundleId : "com.calebevans.playsong"
property workflowCacheFolder : (alfredWorkflowDataFolder & bundleId & ":") as text

-- album artwork folders
property artworkDocsFolder : ((get path to library folder from user domain as text) & "Containers:com.apple.AMPArtworkAgent:Data:Documents")
property artworkImageFolder : (artworkDocsFolder & ":artwork:")

-- the default icon used for search results without album artwork
property defaultIconName : "resources/icon-noartwork.png"
-- the name of the playlist used by the workflow for playing songs
property workflowPlaylistName : "Alfred Play Song"
-- list of Alfred results
property resultList : {}

-- replaces substring in string with another substring
on replace(replaceThis, replaceWith, theString)

	set oldDelims to AppleScript's text item delimiters
	set AppleScript's text item delimiters to replaceThis
	set strItems to text items of theString
	set AppleScript's text item delimiters to replaceWith
	set newString to strItems as text
	set AppleScript's text item delimiters to oldDelims
	return newString

end replace

-- retrieve the plural/singular form of a quantity based on the given number
on quantifyNumber(theNumber, quantityName, pluralQuantityName)

	if theNumber is 1 then

		set theString to (theNumber as text) & space & quantityName

	else

		set theString to (theNumber as text) & space & pluralQuantityName

	end if

	return theString

end quantifyNumber

-- encodes JSON reserved characters in the given string
on encodeFeedbackChars(theString)

	set theString to replace("\\", "\\\\", theString)
	set theString to replace("\"", "\\\"", theString)
	return theString

end encodeFeedbackChars

-- decodes JSON reserved characters in the given string
on decodeFeedbackChars(theString)

	set theString to replace("\\\\", "\\", theString)
	set theString to replace("\\\"", "\"", theString)
	return theString

end decodeFeedbackChars

-- adds Alfred result to result list
on addResult(theResult)

	copy theResult to the end of resultList

end addResult

-- adds item for "No Results" message
on addNoResultsItem(query, queryType)

	addResult({uid:"no-results", valid:"no", title:"No Results Found", subtitle:("No " & queryType & "s matching '" & query & "'"), icon:defaultIconName})

end addNoResultsItem

on resultListIsFull()

	return (length of resultList is resultLimit)

end resultListIsFull

on resultListIsEmpty()

	return (length of resultList is 0)

end resultListIsFull

-- builds Alfred result item as JSON
on getResultFeedback(theResult)

	-- encode reserved JSON characters
	set resultUid to encodeFeedbackChars(uid of theResult)
	set resultValid to (valid of theResult) as text
	set resultTitle to encodeFeedbackChars(title of theResult)
	set resultSubtitle to encodeFeedbackChars(subtitle of theResult)

	if (icon of theResult) contains ":" then

		set resultIcon to encodeFeedbackChars(POSIX path of icon of theResult)

	else

		set resultIcon to icon of theResult

	end if

	set json to "{"
	set json to json & "\"uid\":\"" & resultUid & "\","
	set json to json & "\"arg\":\"" & resultUid & "\","
	set json to json & "\"valid\":\"" & resultValid & "\","
	set json to json & "\"title\":\"" & resultTitle & "\","
	set json to json & "\"subtitle\":\"" & resultSubtitle & "\","
	set json to json & "\"icon\":{\"path\":\"" & resultIcon & "\"}"
	set json to json & "}"
	return json

end getResultFeedback

-- retrieves JSON document for Alfred results
on getResultListFeedback()

	set json to "{\"items\": ["

	repeat with theResult in resultList

		set json to json & getResultFeedback(theResult)
		set json to json & ","

	end repeat

	-- remove trailing comma after last item
	set json to text 1 thru (length of json - 1) of json
	set json to json & "]}"
	return json

end getResultListFeedback

-- writes the given content to the given file
on fileWrite(theFile, theContent)

	set fileRef to open for access theFile with write permission
	try
		set eof of fileRef to 0
		write theContent to fileRef starting at eof
		close access fileRef
	on error
		close access fileRef
	end try

end fileWrite

-- query path to artwork image file cached natively by Music.app in Catalina
on getSongArtworkPath(theSong)

	try

		if albumArtEnabled is false then return defaultIconName

		tell application "Music"

			-- get persistent ID of song and convert from hexadecimal to decimal (base-10)
			set hexSongId to persistent ID of theSong
			set decSongId to (do shell script "echo $((16#" & hexSongId & "))")

			-- retrieve filename of cached artwork (without extension)
			set artworkName to (do shell script ("/usr/bin/sqlite3 " & (POSIX path of artworkDocsFolder) & "/artworkd.sqlite '" & ¬
			"select ZHASHSTRING, ZKIND from ZIMAGEINFO where Z_PK = (" & ¬
			"(select ZIMAGEINFO from ZSOURCEINFO where Z_PK = (" & ¬
			"select ZSOURCEINFO from ZDATABASEITEMINFO where ZPERSISTENTID = " & ¬
			decSongId & ")))' | " & ¬
			"awk '{split($0,a,\"|\"); print a[1] \"_sk_\" a[2] \"_cid_1\"}'"))

			set artworkPath to (artworkImageFolder & artworkName)

		end tell

		return selectFirstArtworkThatExists({ ¬
			(artworkImageFolder & artworkName & ".jpeg"), ¬
			(artworkImageFolder & artworkName & ".png")}) of me

	on error errorMessage

		log errorMessage

	end try

end getSongArtworkPath

-- select the first path (in the given set of artwork paths) that exists on the
-- user's local system
on selectFirstArtworkThatExists(artworkPaths)

	tell application "Finder"
		repeat with artworkPath in artworkPaths
			if artworkPath exists then
				return artworkPath as text
			end if
		end repeat
	end tell

	return defaultIconName

end selectFirstArtworkThatExists

-- creates album artwork cache
on createWorkflowPlaylist()

	tell application "Music"

		if not (user playlist workflowPlaylistName exists) then

			make new user playlist with properties {name:workflowPlaylistName, shuffle:false}

		end if

	end tell

end createWorkflowPlaylist

on clearQueue()

	tell application "Music"

		if user playlist workflowPlaylistName exists then

			delete tracks of user playlist workflowPlaylistName

		end if

	end tell

end clearQueue

on queueSongs(theSongs)

	tell application "Music"

		repeat with theSong in theSongs

			duplicate theSong to user playlist workflowPlaylistName

		end repeat

	end tell

end queueSongs

on playQueue()

	tell application "Music"

		if number of tracks in user playlist workflowPlaylistName is not 0 then

			play user playlist workflowPlaylistName

		end if

	end tell

end playQueue

on getPlaylist(playlistId)

	tell application "Music"

		return (first playlist whose id is playlistId)

	end tell

end getPlaylist

on getPlaylistSongs(playlistId)

	tell application "Music"

		set thePlaylist to getPlaylist(playlistId) of me
		set playlistSongs to every track of thePlaylist

	end tell

	return playlistSongs

end getPlaylistSongs

-- retrieves list of artist names for the given genre
on getGenreArtists(genreName)

	tell application "Music"

		set genreSongs to every track of playlist 2 whose genre is genreName
		set artistNames to {}

		repeat with theSong in genreSongs

			if (artist of theSong) is not in artistNames then

				set artistNames to artistNames & (artist of theSong)

			end if

		end repeat

	end tell

	return artistNames

end getGenreArtists

-- retrieves list of songs within the given genre, sorted by artist
on getGenreSongs(genreName)

	set artistNames to getGenreArtists(genreName) of me
	set genreSongs to {}

	repeat with artistName in artistNames

		set genreSongs to genreSongs & getArtistSongs(artistName) of me

	end repeat

	return genreSongs

end getGenreSongs

-- retrieves list of album names for the given artist
on getArtistAlbums(artistName)

	tell application "Music"

		set artistSongs to every track of playlist 2 whose artist is artistName
		set albumNames to {}

		repeat with theSong in artistSongs

			if (album of theSong) is not in albumNames then

				set albumNames to albumNames & (album of theSong)

			end if

		end repeat

	end tell

	return albumNames

end getArtistAlbums

-- retrieves list of songs by the given artist, sorted by album
on getArtistSongs(artistName)

	tell application "Music"

		set albumNames to getArtistAlbums(artistName) of me
		set artistSongs to {}

		repeat with albumName in albumNames

			set albumSongs to (every track of playlist 2 whose artist is artistName and album is albumName)
			set artistSongs to artistSongs & albumSongs

		end repeat

	end tell

	return artistSongs

end getArtistSongs

-- retrieves list of songs in the given album
on getAlbumSongs(albumName)

	tell application "Music"

		set albumSongs to every track of playlist 2 whose album is albumName

	end tell

	return albumSongs

end getAlbumSongs

-- retrieves the song with the given ID
on getSong(songId)

	tell application "Music"

		set theSong to first track of playlist 2 whose database ID is songId

	end tell

	return theSong

end getSong

-- retrieves a list of objects or names matching the given query and type
on getResultsFromQuery(query, queryType)

	set evalScript to run script "
	script

		on findResults(query, queryType, resultLimit)

			tell application \"Music\"

				set theSongs to (get every track in playlist 2 whose " & queryType & " starts with query)

				if length of theSongs < resultLimit then

					set theSongs to theSongs & (get every track in playlist 2 whose " & queryType & " contains (space & query) and " & queryType & " does not start with query)

				end if

				if length of theSongs < resultLimit then

					set theSongs to theSongs & (get every track in playlist 2 whose " & queryType & " contains query and " & queryType & " does not start with query and " & queryType & " does not contain (space & query))

				end if

				if length of theSongs is 0 then

					if queryType is \"name\" then

						set theSongs to theSongs & (search playlist 2 for query only songs)

					else if queryType is not \"genre\" then

						set theSongs to theSongs & (search playlist 2 for query only " & queryType & "s)

					end if

				end if

				if queryType is \"name\" then

					if length of theSongs > resultLimit then

						set theSongs to items 1 thru resultLimit of theSongs

					end if

					set theResults to theSongs

				else

					set theResults to {}

					repeat with theSong in theSongs

						if length of theResults is resultLimit then exit repeat

						set theResult to " & queryType & " of theSong

						if theResult is not in theResults then

							set theResults to theResults & theResult

						end if

					end repeat

				end if

			end tell

			return theResults

		end findResults

	end script
	"

	evalScript's findResults(query, queryType, resultLimit)

end getResultsFromQuery

-- returns the given string with leading and trailing whitespace removed
on trimWhitespace(theString)

	-- trim leading whitespace
	repeat while theString begins with space

		if length of theString is 1 then return ""
		set theString to text 2 thru end of theString

	end repeat

	-- trim trailing whitespace
	repeat while theString ends with space

		set theString to text 1 thru ((length of theString) - 1) of theString

	end repeat

	return theString

end trimWhitespace

-- queues the song with the given ID
on queueSong(songId)

	set theSong to getSong(songId)
	queueSongs({theSong})

end queueSong

-- queues all songs belonging to the given album
on queueAlbum(albumName)

	set albumName to decodeFeedbackChars(albumName)
	set albumSongs to getAlbumSongs(albumName)
	queueSongs(albumSongs)

end queueAlbum

-- queues all songs by the given artist
on queueArtist(artistName)

	set artistName to decodeFeedbackChars(artistName)
	set artistSongs to getArtistSongs(artistName)
	queueSongs(artistSongs)

end queueArtist

-- queues all songs within the given genre
on queueGenre(genreName)

	set genreName to decodeFeedbackChars(genreName)
	set genreSongs to getGenreSongs(genreName)
	queueSongs(genreSongs)

end queueGenre

-- queues all songs in the given playlist
on queuePlaylist(playlistId)

	set playlistSongs to getPlaylistSongs(playlistId)
	queueSongs(playlistSongs)

end queuePlaylist

-- parses the given result query to retrieve type and id of item to queue
on parseResultQuery(query)

	set pos to offset of "-" in query
	set theType to text 1 thru (pos - 1) of query
	set theId to text (pos + 1) thru end of query
	return {type:theType, id:theId}

end parseResultQuery

on queue(query)

	set typeAndId to parseResultQuery(query)
	set theType to type of typeAndId
	set theId to id of typeAndId

	createWorkflowPlaylist()

	if theType is "song" then
		queueSong(theId)
	else if theType is "album" then
		queueAlbum(theId)
	else if theType is "artist" then
		queueArtist(theId)
	else if theType is "genre" then
		queueGenre(theId)
	else if theType is "playlist" then
		queuePlaylist(theId)
	else
		log "Unsupported type: " & theType
	end if

end queue

on play(query)

	clearQueue()
	queue(query)
	playQueue()

end play

-- core configuration --

-- configurable options --

-- limit number of songs to improve efficiency
property resultLimit : 15
-- whether or not to retrieve/display album artwork for each result
property albumArtEnabled : true

-- workflow parameters --

-- workflow folders
property libraryFolder : (path to library folder from user domain as text)
property cacheFolder : (libraryFolder & "Caches:")
property alfredWorkflowDataFolder : (cacheFolder & "com.runningwithcrayons.Alfred:Workflow Data:")
property bundleId : "com.calebevans.playsong"
property workflowCacheFolder : (alfredWorkflowDataFolder & bundleId & ":") as text

-- album artwork folders
property artworkDocsFolder : ((get path to library folder from user domain as text) & "Containers:com.apple.AMPArtworkAgent:Data:Documents")
property artworkImageFolder : (artworkDocsFolder & ":artwork:")

-- the default icon used for search results without album artwork
property defaultIconName : "resources/icon-noartwork.png"
-- the name of the playlist used by the workflow for playing songs
property workflowPlaylistName : "Alfred Play Song"
-- list of Alfred results
property resultList : {}

-- replaces substring in string with another substring
on replace(replaceThis, replaceWith, theString)

	set oldDelims to AppleScript's text item delimiters
	set AppleScript's text item delimiters to replaceThis
	set strItems to text items of theString
	set AppleScript's text item delimiters to replaceWith
	set newString to strItems as text
	set AppleScript's text item delimiters to oldDelims
	return newString

end replace

-- retrieve the plural/singular form of a quantity based on the given number
on quantifyNumber(theNumber, quantityName, pluralQuantityName)

	if theNumber is 1 then

		set theString to (theNumber as text) & space & quantityName

	else

		set theString to (theNumber as text) & space & pluralQuantityName

	end if

	return theString

end quantifyNumber

-- encodes JSON reserved characters in the given string
on encodeFeedbackChars(theString)

	set theString to replace("\\", "\\\\", theString)
	set theString to replace("\"", "\\\"", theString)
	return theString

end encodeFeedbackChars

-- decodes JSON reserved characters in the given string
on decodeFeedbackChars(theString)

	set theString to replace("\\\\", "\\", theString)
	set theString to replace("\\\"", "\"", theString)
	return theString

end decodeFeedbackChars

-- adds Alfred result to result list
on addResult(theResult)

	copy theResult to the end of resultList

end addResult

-- adds item for "No Results" message
on addNoResultsItem(query, queryType)

	addResult({uid:"no-results", valid:"no", title:"No Results Found", subtitle:("No " & queryType & "s matching '" & query & "'"), icon:defaultIconName})

end addNoResultsItem

on resultListIsFull()

	return (length of resultList is resultLimit)

end resultListIsFull

on resultListIsEmpty()

	return (length of resultList is 0)

end resultListIsFull

-- builds Alfred result item as JSON
on getResultFeedback(theResult)

	-- encode reserved JSON characters
	set resultUid to encodeFeedbackChars(uid of theResult)
	set resultValid to (valid of theResult) as text
	set resultTitle to encodeFeedbackChars(title of theResult)
	set resultSubtitle to encodeFeedbackChars(subtitle of theResult)

	if (icon of theResult) contains ":" then

		set resultIcon to encodeFeedbackChars(POSIX path of icon of theResult)

	else

		set resultIcon to icon of theResult

	end if

	set json to "{"
	set json to json & "\"uid\":\"" & resultUid & "\","
	set json to json & "\"arg\":\"" & resultUid & "\","
	set json to json & "\"valid\":\"" & resultValid & "\","
	set json to json & "\"title\":\"" & resultTitle & "\","
	set json to json & "\"subtitle\":\"" & resultSubtitle & "\","
	set json to json & "\"icon\":{\"path\":\"" & resultIcon & "\"}"
	set json to json & "}"
	return json

end getResultFeedback

-- retrieves JSON document for Alfred results
on getResultListFeedback()

	set json to "{\"items\": ["

	repeat with theResult in resultList

		set json to json & getResultFeedback(theResult)
		set json to json & ","

	end repeat

	-- remove trailing comma after last item
	set json to text 1 thru (length of json - 1) of json
	set json to json & "]}"
	return json

end getResultListFeedback

-- writes the given content to the given file
on fileWrite(theFile, theContent)

	set fileRef to open for access theFile with write permission
	try
		set eof of fileRef to 0
		write theContent to fileRef starting at eof
		close access fileRef
	on error
		close access fileRef
	end try

end fileWrite

-- query path to artwork image file cached natively by Music.app in Catalina
on getSongArtworkPath(theSong)

	try

		if albumArtEnabled is false then return defaultIconName

		tell application "Music"

			-- get persistent ID of song and convert from hexadecimal to decimal (base-10)
			set hexSongId to persistent ID of theSong
			set decSongId to (do shell script "echo $((16#" & hexSongId & "))")

			-- retrieve filename of cached artwork
			set artworkName to (do shell script ("/usr/bin/sqlite3 " & (POSIX path of artworkDocsFolder) & "/artworkd.sqlite '" & ¬
			"select ZHASHSTRING, ZKIND from ZIMAGEINFO where Z_PK = (" & ¬
			"(select ZIMAGEINFO from ZSOURCEINFO where Z_PK = (" & ¬
			"select ZSOURCEINFO from ZDATABASEITEMINFO where ZPERSISTENTID = " & ¬
			decSongId & ")))' | " & ¬
			"awk '{split($0,a,\"|\"); print a[1] \"_sk_\" a[2] \"_cid_1.jpeg\"}'"))

			set artworkPath to (artworkImageFolder & artworkName)

		end tell

		return artworkPath

	on error errorMessage

		log errorMessage

	end try

end getSongArtworkPath

-- creates album artwork cache
on createWorkflowPlaylist()

	tell application "Music"

		if not (user playlist workflowPlaylistName exists) then

			make new user playlist with properties {name:workflowPlaylistName, shuffle:false}

		end if

	end tell

end createWorkflowPlaylist

on clearQueue()

	tell application "Music"

		if user playlist workflowPlaylistName exists then

			delete tracks of user playlist workflowPlaylistName

		end if

	end tell

end clearQueue

on queueSongs(theSongs)

	tell application "Music"

		repeat with theSong in theSongs

			duplicate theSong to user playlist workflowPlaylistName

		end repeat

	end tell

end queueSongs

on playQueue()

	tell application "Music"

		if number of tracks in user playlist workflowPlaylistName is not 0 then

			play user playlist workflowPlaylistName

		end if

	end tell

end playQueue

on getPlaylist(playlistId)

	tell application "Music"

		return (first playlist whose id is playlistId)

	end tell

end getPlaylist

on getPlaylistSongs(playlistId)

	tell application "Music"

		set thePlaylist to getPlaylist(playlistId) of me
		set playlistSongs to every track of thePlaylist

	end tell

	return playlistSongs

end getPlaylistSongs

-- retrieves list of artist names for the given genre
on getGenreArtists(genreName)

	tell application "Music"

		set genreSongs to every track of playlist 2 whose genre is genreName
		set artistNames to {}

		repeat with theSong in genreSongs

			if (artist of theSong) is not in artistNames then

				set artistNames to artistNames & (artist of theSong)

			end if

		end repeat

	end tell

	return artistNames

end getGenreArtists

-- retrieves list of songs within the given genre, sorted by artist
on getGenreSongs(genreName)

	set artistNames to getGenreArtists(genreName) of me
	set genreSongs to {}

	repeat with artistName in artistNames

		set genreSongs to genreSongs & getArtistSongs(artistName) of me

	end repeat

	return genreSongs

end getGenreSongs

-- retrieves list of album names for the given artist
on getArtistAlbums(artistName)

	tell application "Music"

		set artistSongs to every track of playlist 2 whose artist is artistName
		set albumNames to {}

		repeat with theSong in artistSongs

			if (album of theSong) is not in albumNames then

				set albumNames to albumNames & (album of theSong)

			end if

		end repeat

	end tell

	return albumNames

end getArtistAlbums

-- retrieves list of songs by the given artist, sorted by album
on getArtistSongs(artistName)

	tell application "Music"

		set albumNames to getArtistAlbums(artistName) of me
		set artistSongs to {}

		repeat with albumName in albumNames

			set albumSongs to (every track of playlist 2 whose artist is artistName and album is albumName)
			set artistSongs to artistSongs & albumSongs

		end repeat

	end tell

	return artistSongs

end getArtistSongs

-- retrieves list of songs in the given album
on getAlbumSongs(albumName)

	tell application "Music"

		set albumSongs to every track of playlist 2 whose album is albumName

	end tell

	return albumSongs

end getAlbumSongs

-- retrieves the song with the given ID
on getSong(songId)

	tell application "Music"

		set theSong to first track of playlist 2 whose database ID is songId

	end tell

	return theSong

end getSong

-- retrieves a list of objects or names matching the given query and type
on getResultsFromQuery(query, queryType)

	set evalScript to run script "
	script

		on findResults(query, queryType, resultLimit)

			tell application \"Music\"

				set theSongs to (get every track in playlist 2 whose " & queryType & " starts with query)

				if length of theSongs < resultLimit then

					set theSongs to theSongs & (get every track in playlist 2 whose " & queryType & " contains (space & query) and " & queryType & " does not start with query)

				end if

				if length of theSongs < resultLimit then

					set theSongs to theSongs & (get every track in playlist 2 whose " & queryType & " contains query and " & queryType & " does not start with query and " & queryType & " does not contain (space & query))

				end if

				if length of theSongs is 0 then

					if queryType is \"name\" then

						set theSongs to theSongs & (search playlist 2 for query only songs)

					else if queryType is not \"genre\" then

						set theSongs to theSongs & (search playlist 2 for query only " & queryType & "s)

					end if

				end if

				if queryType is \"name\" then

					if length of theSongs > resultLimit then

						set theSongs to items 1 thru resultLimit of theSongs

					end if

					set theResults to theSongs

				else

					set theResults to {}

					repeat with theSong in theSongs

						if length of theResults is resultLimit then exit repeat

						set theResult to " & queryType & " of theSong

						if theResult is not in theResults then

							set theResults to theResults & theResult

						end if

					end repeat

				end if

			end tell

			return theResults

		end findResults

	end script
	"

	evalScript's findResults(query, queryType, resultLimit)

end getResultsFromQuery

-- returns the given string with leading and trailing whitespace removed
on trimWhitespace(theString)

	-- trim leading whitespace
	repeat while theString begins with space

		if length of theString is 1 then return ""
		set theString to text 2 thru end of theString

	end repeat

	-- trim trailing whitespace
	repeat while theString ends with space

		set theString to text 1 thru ((length of theString) - 1) of theString

	end repeat

	return theString

end trimWhitespace

-- queues the song with the given ID
on queueSong(songId)

	set theSong to getSong(songId)
	queueSongs({theSong})

end queueSong

-- queues all songs belonging to the given album
on queueAlbum(albumName)

	set albumName to decodeFeedbackChars(albumName)
	set albumSongs to getAlbumSongs(albumName)
	queueSongs(albumSongs)

end queueAlbum

-- queues all songs by the given artist
on queueArtist(artistName)

	set artistName to decodeFeedbackChars(artistName)
	set artistSongs to getArtistSongs(artistName)
	queueSongs(artistSongs)

end queueArtist

-- queues all songs within the given genre
on queueGenre(genreName)

	set genreName to decodeFeedbackChars(genreName)
	set genreSongs to getGenreSongs(genreName)
	queueSongs(genreSongs)

end queueGenre

-- queues all songs in the given playlist
on queuePlaylist(playlistId)

	set playlistSongs to getPlaylistSongs(playlistId)
	queueSongs(playlistSongs)

end queuePlaylist

-- parses the given result query to retrieve type and id of item to queue
on parseResultQuery(query)

	set pos to offset of "-" in query
	set theType to text 1 thru (pos - 1) of query
	set theId to text (pos + 1) thru end of query
	return {type:theType, id:theId}

end parseResultQuery

on queue(query)

	set typeAndId to parseResultQuery(query)
	set theType to type of typeAndId
	set theId to id of typeAndId

	createWorkflowPlaylist()

	if theType is "song" then
		queueSong(theId)
	else if theType is "album" then
		queueAlbum(theId)
	else if theType is "artist" then
		queueArtist(theId)
	else if theType is "genre" then
		queueGenre(theId)
	else if theType is "playlist" then
		queuePlaylist(theId)
	else
		log "Unsupported type: " & theType
	end if

end queue

on play(query)

	clearQueue()
	queue(query)
	playQueue()

end play