CodeMunki.com | |
Mostly AppleScript With Some Other Programming Stuff Thrown In For Good Measure |
Process Runner: A Script Scheduling and Launching System
05 Jan 2018 9:45 AM
This system will launch any number of scripted processes, each on their own unique schedule of your choosing. The code is written using only "vanilla" AS and has no dependencies other than System Events, Standard Additions, and a handful of shell commands. The system is robust and has been in continuous operation for years, running on a Mac Mini used as a "drone". We reboot the computer about once a week to maintain system stability. Here's how it works: first, a process folder is set up that contains the following files: Process_Runner.app The controlling script using an idle loop, saved as a stay-open application Sample_Process_1.scpt A process script file to execute Sample_Process_1.plist A property list for the script's "Run Parameters" and execution history Note that each process consists of a pair of files: a compiled script file and a property list file. There may be any number of processes present, or none at all. The brains of the system, the script "Process_Runner.app", will dynamically detect them and deploy any found processes according to each one's unique schedule. Processes may be added or removed any time the main script is stopped. Processes can be triggered by one or more of the following "Run Parameters". These parameters are compared against the current day and time, and the history of prior executions of the process, to determine whether or not to run the process. This is rechecked 30 seconds (or at whatever delay you desire) after any triggered processes complete their runs. Launch times are approximate, depending on the number of processes and how long they each take to complete. dailyStartTime (Earliest time of day to execute the process script) 9:00 AM = Start at 9:00 AM 12:01 AM = Start at midnight dailyEndTime (Latest time of day to run) 9:00 PM = End at 9:00 PM 11:59 PM = End at midnight minsDelayBetweenRuns (Delay between executions) 60 = Run once per hour 0 = No delay between runs; run as often as possible numRunsPerDay (Limit of daily executions) 4 = Run process 4 times per day 0 = No limit on daily runs; run as many times as possible weekdaysToRun (Days of week to execute) SuMoTuWeThFrSa = Run every day of the week Sa = Run only on Saturdays MoWeFr = Run only on Mondays, Wednesdays, and Fridays As an example, to run a script every weekday (not weekends) at noon, you'd use the following "Run Parameters": dailyStartTime* 12:00 PM Begin checking Run Parameters at this time dailyEndTime* 12:30 PM Stop checking Run Parameters at this time minsDelayBetweenRuns 0 Don't specify a delay between runs numRunsPerDay 1 Execute only once per day weekdaysToRun MoTuWeThFr Execute only on these days of the week Or another example, to run a script every 4 hours every day, but not more than 4 times, you'd use the following "Run Parameters": dailyStartTime* 6:00 AM Begin checking Run Parameters at this time dailyEndTime* 7:00 PM Stop checking Run Parameters at this time minsDelayBetweenRuns 240 Specify a 240-minute delay between runs numRunsPerDay 4 Execute four times per day weekdaysToRun SuMoTuWeThFrSa Execute every day of the week Click this link to download a zip file containing the following files: About Process_Runner.txt Process_Runner.app Sample_Process_1.plist Sample_Process_1.scpt Sample_Process_2.plist Sample_Process_2.scpt ______ *If another long-running process may encroach on these start or end times, make 'dailyEndTime' later to allow for it. Getting Dimensions of EPS and JPEG
18 Dec 2012 12:15 PM
Raster Images Saved From Photoshop CS5 The above header is rather specific, but only because I've not taken time to try other file types or applications. Feel free to try this idea in other situations. You'll need to experiment a bit and tweak things to get it to do what you need. I needed to determine X and Y pixel dimensions and the resolution (in pixels per inch) for EPS and JPEG image files. I decided to use the AppleScript read file command, so as to avoid opening the files in an application, such as Photoshop. After reading each file, its content is parsed using text manipulation to extract the desired dimensions. The logic uses two variations of the read file command—with the using delimiter option, and without. With the using delimiter option, the delimiter can only be a single character, which is not terribly useful. However, in the case of EPS files, it works well enough to use the forward slash ("/") as the delimiter. For JPEG files, I chose to read the entire file, then split it myself using multiple delimiters, each containing multiple characters. Here's the code: set {dimX, dimY, resX, resY} to {0.0, 0.0, 0.0, 0.0} -- default set f to open for access alias imagePath --HFS path to EPS or JPEG file if imagePath ends with ".eps" then set parts to read f using delimiter "/" close access f repeat with i from 1 to (length of parts) set thisText to item i of parts if thisText contains "<exif:PixelXDimension>" then set dimX to getTextItem(thisText, {"<", ">"}, 4) else if thisText contains "<exif:PixelYDimension>" then set dimY to getTextItem(thisText, {"<", ">"}, 4) else if thisText contains "<tiff:XResolution>" then set resX to getTextItem(thisText, {"<", ">"}, -1) / 10000 else if thisText contains "<tiff:YResolution>" then set resY to getTextItem(thisText, {"<", ">"}, -1) / 10000 end if if 0.0 is not in {dimX, dimY, resX, resY} then exit repeat end repeat else if (imagePath ends with ".jpg") or (imagePath ends with ".jpeg") then set txt to read f close access f set AppleScript's text item delimiters to {" exif:", " tiff:"} set parts to text items 2 thru -2 of txt set AppleScript's text item delimiters to "" repeat with i from 1 to (length of parts) set thisText to item i of parts if thisText starts with "PixelXDimension=" then set dimX to getTextItem(thisText, {"\""}, 2) else if thisText starts with "PixelYDimension=" then set dimY to getTextItem(thisText, {"\""}, 2) else if thisText starts with "XResolution=" then set resX to getTextItem(thisText, {"\"", "/"}, 2) / 10000 else if thisText starts with "YResolution=" then set resY to getTextItem(thisText, {"\"", "/"}, 2) / 10000 end if if 0.0 is not in {dimX, dimY, resX, resY} then exit repeat end repeat end if if resX is not resY then error "Asymmetrical image resolution." number 300 from {resX, resY} return {dimX, dimY, resX} on getTextItem(theText, theDelims, itemNumber) set AppleScript's text item delimiters to theDelims set textItem to text item itemNumber of theText set AppleScript's text item delimiters to "" return textItem end getTextItem Obfuscation of Hard-Coded Data in an AppleScript
16 Nov 2012 11:00 AM
If you, like me, are hesitant to put hard-coded user IDs, passwords, or whatever into your scripts, this bit of obfuscation may be of interest. The bad news is that the information is still in the script and decipherable by an astute intruder. The good news is that the typical computer user will find the ciphertext stored in your scripts to be utterly mysterious and incomprehensible. Let me state clearly that this method is one of "security by obscurity." It is NOT going to provide genuine security, so use it at your own risk. I suppose one could slightly improve the pseudosecurity of this technique by writing the ciphertext to a text file, thus separating the information from the deciphering handlers. As to the methodology employed, the code is my own spin on the ROT18 cipher with some extra twists thrown in. One twist is that spaces, periods, commas, and question marks are enciphered, along with letters and numbers (making it ROT20, I guess). Other characters, such as parentheses, will appear in the ciphertext as is. Another twist is the breaking up of the text into five chunks (or three, if the text is short) and shuffling the order of those chunks. If the text is really short, the order of the characters is simply reversed. Like ROT 18, this logic is self-inverting. That is, the same logic both enciphers plaintext and deciphers ciphertext. Maybe you won't use it for passwords. You and a friend could generate coded messages to email to back and forth. Anyway, for whatever good it is, here is the code. Enjoy! set theText to "Quick Brown Fox, 73, Jumps(?) Over Lazy Dog 129." set cipherText to applyCodec(theText) set plaintextAgain to applyCodec(cipherText) return {cipherText, plaintextAgain} on applyCodec(theText) -- mutant ROT20 retains case of plaintext set lowerCharSet to "abcdefghijklmnopqrstuvwxyz.,? 0123456789" set lowerCipherSet to "uvwxyz.,? 0123456789abcdefghijklmnopqrst" set upperCharSet to "ABCDEFGHIJKLMNOPQRSTUVWXYZ" set upperCipherSet to "NOPQRSTUVWXYZABCDEFGHIJKLM" set cipherText to {} repeat with i from 1 to (length of theText) set thisChar to character i of theText considering case if thisChar is in lowerCharSet then set x to offset of thisChar in lowerCharSet set end of cipherText to character x of lowerCipherSet else if thisChar is in upperCharSet then set x to offset of thisChar in upperCharSet set end of cipherText to character x of upperCipherSet else set end of cipherText to thisChar end if end considering end repeat return shuffle(cipherText as text) end applyCodec on shuffle(theText) set len to count of theText if len < 3 then set shuffledChars to reverse of characters of theText else set usingFifths to len > 29 if usingFifths then set x to 5 set {a, b, c, d, e} to splitIntoFifths(len) else -- using thirds set x to 3 set {a, b, c} to splitIntoThirds(len) end if set shuffledChars to {} set end of shuffledChars to reverse of characters (a + 1) thru (a + b) of theText set end of shuffledChars to reverse of characters 1 thru a of theText set end of shuffledChars to reverse of characters ¬ (a + b + 1) thru (a + b + c) of theText if usingFifths then set end of shuffledChars to reverse of characters ¬ (a + b + c + d + 1) thru (a + b + c + d + e) of theText set end of shuffledChars to reverse of characters ¬ (a + b + c + 1) thru (a + b + c + d) of theText end if end if return shuffledChars as text end shuffle on splitIntoFifths(n) set a to round (n / 5) rounding up set b to round ((n - a) / 4) set c to round (n - a - b) / 3 set d to round (n - a - b - c) / 2 set e to round (n - a - b - c - d) return {a, b, c, d, e} end splitIntoFifths on splitIntoThirds(n) set a to round (n / 3) rounding up set b to round ((n - a) / 2) set c to round (n - a - b) return {a, b, c} end splitIntoThirds Checking an AppleScript for Unused and Undefined Variables
16 Jan 2012 06:00 PM
Thanks to a recent thread on the AppleScript User's mailing list, I learned of a very useful function in Smile, the free AppleScript development software from Satimage Software, available here. Smile is not just a script editor, it's a complex development and working environment. My ignorance shows when I say that I've never quite "made sense" of the Smile environment. But then, I've never needed the advanced capabilities found in Smile—until now. The following script uses the functionality of Smile's validate command to check for both unused and undefined variables. It also uses the scriptability of Script Debugger, my editor of choice, to determine the line number of each problem's occurrence, making it easy to locate within the script window. [If you don't use Debugger, you should be able to rework the script for use with AppleScript Editor or Smile.] I've added this script to Script Debugger's script menu so, when invoked, it will check the frontmost script window. The result is returned by a dialog box and, from there, can be copied to the clipboard or written to a text file. -- get script text tell application "Script Debugger 4.5" set {scriptName} to name of windows whose index is 1 set scriptSource to script source of document of window scriptName set scriptFilePath to (file spec of document of window scriptName) as text end tell set dialogMessage to "Validate script \"" & scriptName & "\"?" displayDialog(dialogMessage, {"Cancel", "OK"}, 2, 1) tell application "System Events" set fileExt to name extension of alias scriptFilePath end tell set baseName to text 1 thru -((length of fileExt) + 2) of scriptName -- hide Smile and its windows tell application "System Events" if not (exists application process "Smile") then activate application "Smile" end if set visible of application process "Smile" to false end tell -- do validation of script tell application "Smile" set background to true set scriptWindow to make new script window set text of scriptWindow to scriptSource validate scriptWindow set textWindow to last text window whose name is "Script errors: " set validationResult to text of textWindow -- validation result close textWindow saving no close scriptWindow saving no end tell -- determine affected line numbers set affectedLineNumbers to {} tid({"show window \"\" selection "}) set blah to text items of validationResult tid("") repeat with i from 1 to (length of blah) set thisInstance to item i of blah if thisInstance starts with "{" then tid(", ") set x to text item 1 of (text 2 thru -1 of thisInstance) tid("") set scriptChunk to text 1 thru x of scriptSource set end of affectedLineNumbers to length of (paragraphs of scriptChunk) end if end repeat set defaultAnswer to convertAnythingToString({validationResult:validationResult, ¬ affectedLineNumbers:affectedLineNumbers}) set action to button returned of displayDialogWithText("Validation result:", ¬ defaultAnswer, {"Write to Text File", "OK"}, 2, 1) if action is "Write to Text File" then set filePath to ((path to desktop) as text) set fileRef to open for access ((filePath & "temp_validation.txt") ¬ as file specification) with write permission set eof of fileRef to 0 write defaultAnswer to fileRef as «class utf8» close access fileRef tell application "System Events" if exists alias (filePath & baseName & "_validation.applescript") then delete alias (filePath & baseName & "_validation.applescript") end if set name of alias (filePath & "temp_validation.txt") to ¬ (baseName & "_validation.applescript") end tell set dialogMessage to "Text file \"" & baseName & ¬ "_validation.applescript\" is on the Desktop." displayDialog(dialogMessage, "OK", 1, 1) end if -- handlers follow on convertAnythingToString(something) if class of something is in {string, text, Unicode text} then set listString to ("\"" & something & "\"") else if something is {} then set listString to "{}" else if something is in {missing value, null, true, false} then set listString to something as text else try something as integer set listString to something as text on error errText set opening to offset of "{" in errText set closing to offset of "}" in "" & (reverse of (characters of errText)) set listString to text opening thru -closing of errText end try end if return listString end convertAnythingToString on displayDialog(dialogMessage, buttonList, defButton, iconNum) tell application "Script Debugger 4.5" activate with timeout of 17700 seconds display dialog dialogMessage buttons buttonList ¬ default button defButton with icon iconNum end timeout end tell end displayDialog on displayDialogWithText(dialogMessage, defaultAnswer, buttonList, defButton, iconNum) tell application "Script Debugger 4.5" activate with timeout of 17700 seconds display dialog dialogMessage default answer defaultAnswer ¬ buttons buttonList default button defButton with icon iconNum end timeout end tell end displayDialogWithText on tid(d) set AppleScript's text item delimiters to d end tid This has found problems in my scripts, saving me some potential headaches. Enjoy! Using 'load script' With Scripts Saved In Text Format
20 Jan 2011 06:30 PM
Often I use the 'load script' command to bring in the code from another script file, so its handlers are available in the current script. It works something like this: load script alias "MacHD:Users:stanc:repository:Some Project:Some Script.app" This works great unless the script you want to load is saved in text format (with the ".applescript" extension). In that case, you get error number -1752 and the message "Script doesn’t seem to belong to AppleScript." Great! So now what? This is a problem for me, because I store all my AppleScript source code in text format, which makes things much easier for version control and dealing with absent applications. Just use the following handler, which will attempt to load the script file normally and, if that fails, will read the file as text and turn that into the desired script object. on loadScript(scriptFileToLoad) set scriptFileToLoad to scriptFileToLoad as text -- to be safe try set scriptObject to load script alias scriptFileToLoad on error number -1752 -- text format script set scriptObject to run script ("script s" & return & ¬ (read alias scriptFileToLoad as «class utf8») & ¬ return & "end script " & return & "return s") end try return scriptObject end loadScript Just pass the script's path to the handler as a string or alias. Like so: set scriptObject to loadScript("MacHD:Users:stanc:MyScript.applescript") Hope you can use this little bit of trickery. Happy New Year, 2011!
31 Dec 2010 09:30 AM
Our plan is to find more time in the coming year to devote to sharing some of our thoughts and knowledge about AppleScript and programming. Though time is never actually "found," improvements in organizing our schedule should free up some time from other relatively unimportant activities. Greetings!
30 Aug 2010 02:00 PM
This is a fine example of minimalism, no? But we're just getting started (and we're slow, not to mention lazy), so don't hold your breath or anything. Okay? |
MacScripter
Search this site:
Mac OS X Automation Script Factory Arduino Bases Hallmark Train Ornaments Heathkit HD-1416 Oscillator Kodak Library of Creative Photography Lufkin Tools Time-Life Old West Books Sunset Heating & Cooling Wells Fargo Bank Gamefield Email:
©2010-2022 Stan Cleveland
|