Last active
July 4, 2018 21:33
Demonstrates a boatload of AppleScript techniques, primarily focused on gathering data from a user's Mac account for troubleshooting.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(* Actually running this script requires that several other tools be included into | |
the script's package. The entire package, which should include the latest version of | |
this script, may be downloaded from here: | |
http://sheepsystems.com/files/SSYTroubleZipper.zip *) | |
property scriptVersion : "0255 published 2014-12-04" | |
property companyUTI : "com.sheepsystems" | |
property ourProcessNames : {"Bookdog", "Bookwatchdog", "BookMacster", "Markster", "Smarky", "Synkmark", "Sheep-Sys-Worker", "Sheep-Sys-Quatch", "Sheep-Sys-Thomas", "Sheep-Sys-UrlHandler", "FileAliasWorker"} | |
-- The above are all acted upon in 'try' blocks, so it's OK if some don't exist in certain context(s). | |
property companyName : "Sheep Systems" | |
property limitExidBytes : 1.0E+7 | |
property limitSyncLogBytes : 1.0E+7 | |
property limitEmailAttachment : 1.0E+7 | |
property fileUploaderUrl : "http://www.sheepsystems.com/support/file-uploader/" | |
property ourChromeExtensionId : "dopbhkfmgllfbimacbeopgifmflgelni" | |
global bookmarksPaths | |
global namedOfLocalClientsToGetBookmarksFrom | |
global genericTitle | |
global logText | |
global progress | |
global gotAuthorization | |
-- Do not do the following, because it will *compile* the path to *my* home folder into the script, causing it to fail when run in other Mac accounts! | |
--property posixHome : POSIX path of (path to home folder) <-- Do not do this | |
set posixHome to POSIX path of (path to home folder) | |
set shoeboxDocDirPath to posixHome & "Library/Application Support/BookMacster" | |
set bookmarkshelfDocsDirPath to shoeboxDocDirPath & "/Bookmarkshelf Documents" | |
set appInfos to {{appName:"Bookdog", hasDocuments:false, shoeboxFilename:missing value, docUTI:"com.sheepsystems.bdogmigr", docType:"bdogmigr", defaultDocDirPath:posixHome, localClientApps:{"Camino", "Firefox", "iCab", "OmniWeb", "Opera", "Safari"}, appAppSupportName:"Bookdog", canSync:true}, {appName:"BookMacster", hasDocuments:"many", shoeboxFilename:missing value, docUTI:"com.sheepsystems.bkmslf", docType:"bkmslf", defaultDocDirPath:bookmarkshelfDocsDirPath, localClientApps:{"Camino", "Firefox", "Google Chrome", "iCab", "OmniWeb", "Opera", "Safari"}, appAppSupportName:"BookMacster", canSync:true}, {appName:"Markster", shoeboxFilename:"Markster.bkmslf", hasDocuments:"one", docUTI:"com.sheepsystems.bkmslf", docType:"bkmslf", defaultDocDirPath:shoeboxDocDirPath, localClientApps:{"Camino", "Firefox", "Google Chrome", "iCab", "OmniWeb", "Opera", "Safari"}, appAppSupportName:"BookMacster", canSync:true}, {appName:"Smarky", shoeboxFilename:"Smarky.bkmslf", hasDocuments:"one", docUTI:"com.sheepsystems.bkmslf", docType:"bkmslf", defaultDocDirPath:shoeboxDocDirPath, localClientApps:{"Safari"}, appAppSupportName:"BookMacster", canSync:true}, {appName:"Synkmark", shoeboxFilename:"Synkmark.bkmslf", hasDocuments:"one", docUTI:"com.sheepsystems.bkmslf", docType:"bkmslf", defaultDocDirPath:shoeboxDocDirPath, localClientApps:{"Camino", "Firefox", "Google Chrome", "iCab", "OmniWeb", "Opera", "Safari"}, appAppSupportName:"BookMacster", canSync:true}} | |
set bookmarksPaths to {} | |
set namedOfLocalClientsToGetBookmarksFrom to {} | |
set cachesNeeded to {} | |
set genericTitle to "Trouble Zipper" & " ver " & scriptVersion | |
set logText to "" | |
set gotAuthorization to false | |
set quitButton to "Quit" | |
set aLooseFile to "A \"loose\" file (Advanced)" | |
set posixLibrary to (POSIX path of (path to home folder)) & "/Library" | |
set appNames to {} | |
repeat with appInfo in appInfos | |
set end of appNames to appName of appInfo | |
end repeat | |
set pathToMe to ((POSIX path of (path to me)) as string) | |
-- See Note 20131005 | |
-- for testing | |
--set zipName to "Bkmx_Trouble" | |
--set tempDirPath to posixHome & "Desktop/" & zipName & "/" | |
--set cmd to "cd ~/Desktop ; /bin/rm -Rf " & zipName & " ; /bin/mkdir " & zipName | |
--do shell script cmd | |
tryShellScript("This command should fail!") | |
catLog("Produced by Sheep Systems Trouble Zipper version " & scriptVersion) | |
set aPrompt to "Sheep Systems Trouble Zipper will zip up an archive of data and place it on your Desktop. Sending us this archive helps us to more quickly resolve uncommon problems, with fewer emails. | |
Which application are you having trouble with?" | |
choose from list appNames with title genericTitle with prompt aPrompt cancel button name quitButton | |
set aAnswer to result | |
if aAnswer is false then | |
return | |
end if | |
catLog("Which app? ANSWER: " & aAnswer) | |
set appName to first item of aAnswer | |
set appInfo to missing value | |
repeat with anAppInfo in appInfos | |
if ((appName of anAppInfo) as string) is (appName as string) then | |
set appInfo to anAppInfo | |
exit repeat | |
end if | |
end repeat | |
if appName is false then return | |
set zipName to appName & "_Trouble" | |
tell application "Finder" | |
set appAppSupportFolder to folder (appAppSupportName of appInfo) of folder "Application Support" of folder "Library" of home | |
end tell | |
set aPrompt to "The information gathered by this script will be: | |
• Your answers to a few questions | |
• Versions of Mac OS, web browsers and | |
" & companyName & " apps | |
• Computer's Processor and Model | |
• User LaunchAgent and LaunchDaemon plist files | |
• Messages which " & companyName & " apps wrote to | |
your System Log during the last week | |
• Data created by " & companyName & " apps: | |
• Crash Reports | |
• Preferences | |
• Local Settings | |
• Command Logs | |
• Info about your Firefox and Chrome extensions and preferences | |
• Bookmarks (optional, later) | |
We'll shall destroy this data as soon as you direct, or indicate that the issue is resolved, and treat it securely as stated in sections 2.3 and 3.0 of our Privacy Policy." | |
repeat | |
display dialog aPrompt with title genericTitle buttons {quitButton, "View Policy", "Just Continue"} default button "Just Continue" | |
set buttonReturned to button returned of result | |
if buttonReturned is quitButton then | |
return | |
else if buttonReturned is "View Policy" then | |
open location "http://sheepsystems.com/privacy/" | |
delay 5 | |
else | |
exit repeat | |
end if | |
end repeat | |
tell me to activate | |
set AppleScript's text item delimiters to {} | |
-- Clean out anything from a previous run of this tool, and make a new working directory | |
set cmd to "cd ~/Desktop ; /bin/rm -f " & zipName & ".zip" | |
set aResult to tryShellScriptAndVerifySilentResult(cmd) | |
if aResult is not missing value then | |
set aMsg to "Could not remove an older zip file because " & toString(aResult) & "Please remove the old " & zipName & ".zip from your Desktop yourself, then retry this script." | |
display dialog aMsg | |
catLog(aMsg) | |
end if | |
set cmd to "cd ~/Desktop ; /bin/rm -Rf " & zipName | |
set aResult to tryShellScriptAndVerifySilentResult(cmd) | |
if aResult is not missing value then | |
set aMsg to "Could not remove an older output directory because " & toString(aResult) & "Please remove the old " & zipName & " directory from your Desktop yourself, then retry this script." | |
display dialog aMsg | |
catLog(aMsg) | |
end if | |
set cmd to "cd ~/Desktop ; /bin/mkdir " & (quoted form of zipName) | |
set aResult to tryShellScriptAndVerifySilentResult(cmd) | |
if aResult is not missing value then | |
set aMsg to "Could not create new output directory because " & toString(aResult) | |
display dialog aMsg | |
catLog(aMsg) | |
end if | |
-- See what Local Caches and Sync Snapshots are available | |
set filesFound to {} | |
try | |
tell application "Finder" | |
log ("aasfolder = " & appAppSupportFolder) | |
set filesFound to files of appAppSupportFolder | |
end tell | |
end try | |
-- Search for local extore caches… | |
set sqlFilenames to {} | |
tell application "Finder" | |
repeat with aFile in filesFound | |
set filename to name of aFile | |
log ("aas item : " & filename) | |
tell me to set doesContain to verifyTextContains(filename, "|") | |
if doesContain then | |
tell me to set doesContain to verifyTextContains(filename, ".sql") | |
if doesContain then | |
set sqlFilenames to sqlFilenames & filename | |
end if | |
end if | |
end repeat | |
end tell | |
-- Search for the raw downloaded data from web apps (webdata files)… | |
try | |
tell application "Finder" | |
set foldersFound to folders of folder "SyncSnapshots" of appAppSupportFolder | |
end tell | |
end try | |
set webdataFilenames to {} | |
try | |
tell application "Finder" | |
repeat with aFolder in foldersFound | |
set folderName to name of aFolder | |
tell me to set doesContain to verifyTextContains(filename, ".webdata") | |
if doesContain then | |
set webdataFilenames to webdataFilenames & filename | |
end if | |
end repeat | |
end tell | |
end try | |
-- Summarize | |
set candidateClients to displayizeFilenames(sqlFilenames, ".sql") & displayizeFilenames(webdataFilenames, ".webdata") | |
set candidateClients to candidateClients & (localClientApps of appInfo) | |
set candidateClients to candidateClients & aLooseFile | |
set candidateClients to deleteDuplicates(candidateClients) | |
set aPrompt to "If the problem appears when working with bookmarks from a particular web browser or Client, it is often very helpful to include the current bookmarks data, recent snapshots of it, and preferences from the Client web browser. | |
The bookmarks data contains bookmark names, URLs, tags or labels, possibly times visited, and also any comments or descriptions you have entered. It does not include any site passwords, unless you have entered these into bookmarks names, comments or descriptions (which is not a good idea in any case!)." | |
display dialog aPrompt with title (genericTitle & " : Question B") buttons {quitButton, "Don't Include Bookmarks", "Include Bookmarks"} default button "Include Bookmarks" | |
set aAnswer to button returned of result | |
catLog("Include Bookmarks? ANSWER: " & aAnswer) | |
if aAnswer is "Include Bookmarks" then | |
set aPrompt to "Please select the browsers and other Clients whose bookmarks may be involved. Note that if you imported from Browser A to Browser B, and then had trouble when exporting to Browser B, the trigger may actually be in Browser A. | |
Hold down the ⌘ key to select more than one." | |
choose from list candidateClients with title genericTitle with prompt aPrompt OK button name "Done" cancel button name "Quit" with multiple selections allowed and empty selection allowed | |
set namedOfLocalClientsToGetBookmarksFrom to result | |
if namedOfLocalClientsToGetBookmarksFrom is false then return | |
catLog("Choose Clients? ANSWER: " & namedOfLocalClientsToGetBookmarksFrom) | |
else if aAnswer is quitButton then | |
return | |
end if | |
set getFirefox to false | |
repeat with clientName in namedOfLocalClientsToGetBookmarksFrom | |
set clientName to clientName as string -- I don't know why this is needed, but it is! | |
if clientName is aLooseFile then | |
set aPrompt to "Select " & aLooseFile & " you wish to include." | |
try -- So that "cancel" will not abort script or result in nonsense POSIX path | |
choose file with prompt aPrompt | |
set end of bookmarksPaths to POSIX path of result | |
end try | |
else | |
addInfoForBrowser(clientName, pathToMe) | |
if clientName is "Firefox" then | |
set getFirefox to true | |
end if | |
end if | |
end repeat | |
set getDiaries to false | |
if canSync of appInfo is true then | |
set aPrompt to appName & " writes Sync Log entries when it imports or exports any bookmarks. | |
Sync Logs include bookmark and folder names, and any changed attributes, possibly including URLs (i.e., http://… addresses)." | |
display dialog aPrompt with title (genericTitle & " : Question L") buttons {quitButton, "Don't Include Sync Logs", "Include Sync Logs"} default button "Include Sync Logs" | |
set aAnswer to button returned of result | |
catLog("Include Sync Logs? ANSWER: " & aAnswer) | |
if aAnswer is "Include Sync Logs" then | |
set getDiaries to true | |
else if aAnswer is quitButton then | |
return | |
end if | |
end if | |
set getBkmslfs to false | |
set bkmslfPaths to {} | |
if hasDocuments of appInfo is "many" then | |
set aPrompt to "Question F. (Last Question) | |
May we include your Bookmarkshelf Document(s)? (.bkmslf files) | |
Answer \"Manually\" to pick troublesome .bkmslf file(s) yourself. You should do this if you know the name of your document, or are a new user who has only created one. Trouble Zipper will start you out in the default location where BookMacster creates .bkmslf files. | |
Answer \"Search\" to search your hard drive for .bkmslf files. This may take a few minutes but is recommended if you think you have created multiple .bkmslf files and are not sure which one you're having trouble with. We'll list the paths of the documents we find." | |
display dialog aPrompt with title (genericTitle & " : Question F") buttons {"Don't Include", "Manually", "Search"} default button "Search" | |
set aAnswer to button returned of result | |
catLog("Include bkmslf files? ANSWER: " & aAnswer) | |
set getBkmslfs to aAnswer | |
set anyMore to true | |
if getBkmslfs is "Manually" then | |
set docType to docType of appInfo | |
set defaultDocDirPath to defaultDocDirPath of appInfo | |
set didDoDefaultDocDirPath to false | |
repeat while anyMore is true | |
set aPrompt to "Please select the troublesome ." & docType & " file(s) to include." | |
-- We 'try' in case "Bookmarkshelf Documents" subfolder does not exist | |
try | |
if didDoDefaultDocDirPath is false then | |
try | |
set docDirPath to (POSIX file defaultDocDirPath) as alias | |
on error | |
-- This will happen if defaultDocDirPath does not exist. | |
set docDirPath to (POSIX file posixLibrary) as alias | |
end try | |
set didDoDefaultDocDirPath to true | |
else | |
set docDirPath to (POSIX file posixLibrary) as alias | |
end if | |
set docAliases to {} | |
try | |
-- We 'try' so that script does not exit if user chooses no files and clicks 'cancel'. Unfortunately, 'choose file' does not allow me to change the name of the 'Cancel" button to something more appropriate like 'None'. | |
set docAliases to choose file with prompt aPrompt default location docDirPath with multiple selections allowed | |
end try | |
repeat with docAlias in docAliases | |
set docPath to POSIX path of docAlias | |
set end of bkmslfPaths to docPath | |
end repeat | |
end try | |
set aPrompt to "Any more, maybe in other folders?" | |
display dialog aPrompt with title genericTitle buttons {"More", "Done"} default button "Done" | |
set aAnswer to button returned of result | |
if aAnswer is "Done" then | |
set anyMore to false | |
end if | |
end repeat | |
end if | |
else if hasDocuments of appInfo is "one" then | |
set aPrompt to "Question F. (Last Question) | |
May we include your document (.bkmslf) file, which contains your bookmarks in " & appName & "?" | |
display dialog aPrompt with title (genericTitle & " : Question F") buttons {"Don't Include", "Include Document"} default button "Include Document" | |
set aAnswer to button returned of result | |
catLog(aPrompt & " ANSWER: " & aAnswer) | |
set getBkmslfs to aAnswer | |
if getBkmslfs is "Include Document" then | |
set docPath to (defaultDocDirPath of appInfo) & "/" & (shoeboxFilename of appInfo) | |
set end of bkmslfPaths to docPath | |
end if | |
end if | |
set tempDirPath to posixHome & "Desktop/" & zipName & "/" | |
set browserBookmarksOutDir to tempDirPath & "ExtoreBookmarks/" | |
set cmd to "/bin/mkdir " & browserBookmarksOutDir | |
do shell script cmd | |
startScriptView(pathToMe) | |
set logText to logText & "User's answer's are complete. | |
Beginning to gather data at " & (current date) & " | |
Time Zone is " & timeZone() & " hours from GMT | |
" as string | |
set bookmarksPathLog to (count of bookmarksPaths) & " bookmarksPaths…" & return | |
if (count of bookmarksPaths) is greater than 0 then | |
appendScriptView("Copying Clients' Bookmarks Files and Snapshots") | |
set textFilePath to browserBookmarksOutDir & "_OriginalPaths.txt" | |
set i to 1 | |
set pathsString to "" | |
repeat with aPath in bookmarksPaths | |
set bookmarksPathLog to bookmarksPathLog & i & ": " & aPath & return | |
set filename to getLastPathComponent(aPath) | |
set cmd to "/bin/cp -Rp " & (quoted form of aPath) & " " & quoted form of (browserBookmarksOutDir & "[" & i & "]" & filename) | |
set cmdResult to 9876 -- was 9999 | |
try | |
set cmdResult to do shell script cmd | |
end try | |
-- If successful, cmdResult will be an empty string | |
if cmdResult is greater than 0 then | |
set bookmarksPathLog to bookmarksPathLog & " Failed with exit code " & cmdResult & " for command: " & cmd & return | |
else | |
set pathsString to pathsString & " " & aPath & return | |
set aLine to "[" & i & "] " & aPath as string | |
writeTextToPosixPath(textFilePath, aLine & return, true) | |
set bookmarksPathLog to bookmarksPathLog & " Succeeded." & return | |
end if | |
set i to i + 1 | |
end repeat | |
appendScriptView(pathsString) | |
end if | |
catLog(return & bookmarksPathLog) | |
if getBkmslfs is "Search" then | |
appendScriptView("Searching for BookMacster Bookmarkshelf Documents. This may take a few minutes. You can leave this running and go do something else.") | |
set toolPath to quoted form of (pathToMe & "Contents/MacOS/CarbonSearch") | |
set cmd to toolPath & " -f .bkmslf" | |
set bkmslfPathsString to do shell script cmd | |
set bkmslfPathCandidates to split(bkmslfPathsString, return) | |
set bkmslfPaths to {} | |
-- SSYCarbonSearch found files with ".bkmslf" anywhere in the name. We now filter in only those which have it as a filename extension. | |
repeat with candidatePath in bkmslfPathCandidates | |
set aLength to length of candidatePath | |
set docType to docType of appInfo | |
set extension to "." & docType | |
if (hasSuffix(candidatePath, extension)) then | |
if (verifyTextContains(candidatePath, "/.Trash/") is false) then | |
set end of bkmslfPaths to candidatePath | |
end if | |
end if | |
end repeat | |
end if | |
set bkmslfsOut to tempDirPath & "Bkmslfs/" | |
set cmd to "/bin/mkdir " & bkmslfsOut | |
do shell script cmd | |
if (count of bkmslfPaths) is greater than 0 then | |
appendScriptView("Copying " & (count of bkmslfPaths) & " Document Files") | |
set textFilePath to bkmslfsOut & "_OriginalPaths.txt" | |
set i to 1 | |
set pathsString to "" | |
repeat with bkmslfPath in bkmslfPaths | |
set pathsString to pathsString & " " & bkmslfPath & return | |
set filename to getLastPathComponent(bkmslfPath) | |
set cmd to "/bin/cp -pX " & (quoted form of bkmslfPath) & " " & quoted form of (bkmslfsOut & "[" & i & "]" & filename) | |
set errNumber to 0 | |
try | |
do shell script cmd | |
on error errStr number errNum | |
catLog("/bin/cp failed and returned error " & errNum & " : " & errStr & " when attempting to copy " & (quoted form of bkmslfPath)) | |
if (errNum is 0) or (errNum is missing value) then | |
set errNumber to 1 | |
end if | |
end try | |
if errNumber is 0 then | |
set aLine to "[" & i & "] " & bkmslfPath as string | |
writeTextToPosixPath(textFilePath, aLine & return, true) | |
end if | |
set i to i + 1 | |
end repeat | |
appendScriptView(pathsString) | |
end if | |
appendScriptView("Getting preferences") | |
-- Add prefs to zip directory | |
repeat with aProcessName in ourProcessNames | |
set aBundleID to bundleIdentifier(companyUTI, aProcessName) | |
set aPath to posixHome & "Library/Preferences/" & aBundleID & ".plist" | |
set cmd to "/bin/cp -pX " & quoted form of aPath & " " & tempDirPath | |
tryShellScript(cmd) | |
end repeat | |
set unescapedAppSupportDir to posixHome & "Library/Application Support/" & (appAppSupportName of appInfo) & "/" | |
-- We're going to escape the spaces so that we do not need to quote the path. We'll need an unquoted path in order to use file globbing to get Setting*.sql, Exids*.sql, and Diaries*.sql. | |
set appSupportDir to searchAndReplace(unescapedAppSupportDir, " ", "\\ ") | |
set appSupportOut to tempDirPath & "AppSupport-not-incl-SyncSnapshots/" | |
set cmd to "/bin/mkdir " & appSupportOut | |
tryShellScript(cmd) | |
if (appAppSupportName of appInfo) is "BookMacster" then | |
appendScriptView("Listing " & appSupportDir) | |
set cmd to "/bin/ls -alww " & appSupportDir | |
appendScriptView(" cmd: " & cmd) | |
set appSupportCatalog to "Sorry, couldn't even try" | |
try | |
set appSupportCatalog to tryShellScript(cmd) | |
-- tryShellScript() may return 'missing value' | |
on error errString number errNumber | |
catLog("AppleScript error: " & errNumber & " : " & errString) | |
set appSupportCatalog to "Sorry, ls failed" | |
end try | |
if appSupportCatalog is missing value then set appSupportCatalog to "Sorry, ls command failed" | |
catLog("Contents of " & appSupportDir & return & appSupportCatalog) | |
end if | |
if (appAppSupportName of appInfo) is "BookMacster" then | |
appendScriptView("Getting Logs from " & appSupportDir) | |
-- Add Logs database to zip directory | |
try | |
set aPath to appSupportDir & "Logs.sql" | |
set cmd to "/bin/cp -pX " & aPath & " " & appSupportOut | |
tryShellScript(cmd) | |
on error errorString number aCode | |
catLog("Getting Logs.sql, error occurred: " & aCode & " occurred. Details: " & errorString) | |
end try | |
end if | |
if (appAppSupportName of appInfo) is "BookMacster" then | |
appendScriptView("Getting Local Settings from " & appSupportDir) | |
-- Add Settings databases to zip directory | |
set aPath to appSupportDir & "Settings*.sql" | |
set cmd to "/bin/cp -pX " & aPath & " " & appSupportOut | |
tryShellScript(cmd) | |
end if | |
if (appAppSupportName of appInfo) is "BookMacster" then | |
appendScriptView("Getting Exids from " & appSupportDir) | |
-- Add Exids databases to zip directory | |
try | |
set aPath to appSupportDir & "Exids*.sql" | |
set latestFilenamez to latestFilenames(aPath, limitExidBytes) | |
repeat with filename in latestFilenamez | |
set aPath to appSupportDir & filename | |
set cmd to "/bin/cp -pX " & aPath & " " & appSupportOut | |
tryShellScript(cmd) | |
end repeat | |
on error errorString number aCode | |
catLog("Getting Exids, error occurred: " & aCode & " occurred. Details: " & errorString) | |
end try | |
end if | |
if getDiaries is true then | |
appendScriptView("Getting Sync Logs from " & appSupportDir) | |
-- Add Diaries database to zip directory | |
try | |
set aPath to appSupportDir & "Diaries*.sql" | |
set latestFilenamez to latestFilenames(aPath, limitSyncLogBytes) | |
repeat with filename in latestFilenamez | |
set aPath to appSupportDir & filename | |
set cmd to "/bin/cp -pX " & aPath & " " & appSupportOut | |
tryShellScript(cmd) | |
end repeat | |
on error errorString number aCode | |
catLog("Getting Sync Logs, error occurred: " & aCode & " occurred. Details: " & errorString) | |
end try | |
end if | |
if getFirefox is true then | |
appendScriptView("Getting Firefox Backups from " & appSupportDir) | |
try | |
-- Add Firefox backups folder to zip directory | |
set aPath to appSupportDir & "Firefox\\ Backups" | |
set cmd to "/bin/cp -pXR " & aPath & " " & appSupportOut | |
tryShellScript(cmd) | |
on error errorString number aCode | |
catLog("Getting Firefox, error occurred: " & aCode & " occurred. Details: " & errorString) | |
end try | |
end if | |
if getBkmslfs is not false then | |
appendScriptView("Getting document files") | |
try | |
repeat with bkmslfPath in bkmslfPaths | |
set cmd to "/bin/" & quoted form of bkmslfPath & " " & bkmslfsOut | |
tryShellScript(cmd) | |
end repeat | |
on error errorString number aCode | |
catLog("Getting Bookmarkshelf document files, error occurred: " & aCode & " occurred. Details: " & errorString) | |
end try | |
end if | |
appendScriptView("Getting Permissions on LaunchAgents") | |
set userShortName to short user name of (system info) | |
set testResult to "The following LaunchAgent permissions probes will print Access Control Lists (ACLs), and also flags, if any. Beware of any 'uchg' in flags. This user is " & userShortName & "." | |
catLog(testResult) | |
set aPath to "~" | |
set cmd to "ls -lde@O " & aPath | |
set aResult to tryShellScript(cmd) | |
catLog("Result of " & cmd & ":" & return & aResult) | |
set aPath to "~/Library" | |
set cmd to "ls -lde@O " & aPath | |
set aResult to tryShellScript(cmd) | |
catLog("Result of " & cmd & ":" & return & aResult) | |
set aPath to "~/Library/LaunchAgents" | |
set cmd to "ls -lde@O " & aPath | |
set aResult to tryShellScript(cmd) | |
catLog("Result of " & cmd & ":" & return & aResult) | |
set aPath to "~/Library/Safari" | |
set cmd to "ls -le@O " & aPath | |
set aResult to tryShellScript(cmd) | |
catLog("Result of " & cmd & ":" & return & aResult) | |
set aPath to "~/Library/Application\\ Support/Firefox/Profiles" | |
set cmd to "ls -le@O " & aPath | |
set aResult to tryShellScript(cmd) | |
catLog("Result of " & cmd & ":" & return & aResult) | |
set aPath to "~/Library/Application\\ Support/Firefox/Profiles/*.default" | |
set cmd to "ls -le@O " & aPath | |
set aResult to tryShellScript(cmd) | |
catLog("Result of " & cmd & ":" & return & aResult) | |
set aPath to "~/Library/Application\\ Support/Google/Chrome/Default" | |
set cmd to "ls -le@O " & aPath | |
set aResult to tryShellScript(cmd) | |
catLog("Result of " & cmd & ":" & return & aResult) | |
set aPath to "~/Library/Application\\ Support/No/Such/Directory" | |
set cmd to "ls -le@O " & aPath | |
set aResult to tryShellScript(cmd) | |
catLog("Result of " & cmd & ":" & return & aResult) | |
catLog("End of LaunchAgent permissions probes") | |
appendScriptView("Getting LaunchAgents and LaunchDaemons") | |
try | |
set destinPath to tempDirPath & "LaunchStuff-AllUsers" | |
set cmd to "/bin/rm -Rf " & destinPath | |
tryShellScript(cmd) | |
set cmd to "/bin/mkdir " & destinPath | |
tryShellScript(cmd) | |
set sourcePath to "/Library/Launch*" | |
set cmd to "/bin/cp -Rp " & sourcePath & " " & destinPath | |
tryShellScript(cmd) | |
end try | |
try | |
set destinPath to tempDirPath & "LaunchStuff-ThisUser" | |
set cmd to "/bin/rm -Rf " & destinPath | |
tryShellScript(cmd) | |
set cmd to "/bin/mkdir " & destinPath | |
tryShellScript(cmd) | |
set sourcePath to posixHome & "Library/Launch*" | |
set cmd to "/bin/cp -Rp " & sourcePath & " " & destinPath | |
tryShellScript(cmd) | |
end try | |
appendScriptView("Testing access to launchd agents in ~/Library/LaunchAgents") | |
set testResult to testAccessToDirectory("~") | |
catLog(testResult) | |
set testResult to testAccessToDirectory("~/Library") | |
catLog(testResult) | |
set testResult to testAccessToDirectory("~/Library/LaunchAgents") | |
catLog(testResult) | |
set crashReportsOut to tempDirPath & "CrashReports/" | |
set cmd to "/bin/mkdir " & crashReportsOut | |
tryShellScript(cmd) | |
appendScriptView("Getting info on Chrome Extensions and Preferences") | |
-- TODO: Get all Chrome profiles instead of this… | |
set chromeProfiles to {"Default"} | |
try -- Because we'll take whatever we can get | |
set destinDir to tempDirPath & "Chrome-App-Support/" | |
set cmd to "/bin/rm -Rf " & destinDir | |
tryShellScript(cmd) | |
set cmd to "/bin/mkdir " & destinDir | |
tryShellScript(cmd) | |
set chromeSource to posixHome & "Library/Application Support/Google/Chrome/" | |
set wantedFilename to "Local State" | |
set sourcePath to chromeSource & wantedFilename | |
set destinPath to destinDir & wantedFilename | |
set ok to copyPath(sourcePath, destinPath) | |
set wantedFilename to "External Extensions.json" | |
set sourcePath to chromeSource & wantedFilename | |
set destinPath to destinDir & wantedFilename | |
copyPath(sourcePath, destinPath) | |
repeat with profile in chromeProfiles | |
set destinDir to tempDirPath & "Chrome-App-Support/" & profile & "/" | |
set cmd to "/bin/rm -Rf " & destinDir | |
tryShellScript(cmd) | |
set cmd to "/bin/mkdir " & destinDir | |
tryShellScript(cmd) | |
set chromeSource to posixHome & "Library/Application Support/Google/Chrome/" & profile & "/" | |
-- Starting with verison 244, we only copy *our* Chrome extension, because of one user who had about 100 Chrome extensions totalling 70 MB. | |
set wantedFilename to "Extensions/" & ourChromeExtensionId | |
set sourcePath to chromeSource & wantedFilename | |
set destinPath to destinDir & wantedFilename | |
copyPath(sourcePath, destinPath) | |
set wantedFilename to "Preferences" | |
set sourcePath to chromeSource & wantedFilename | |
set destinPath to destinDir & wantedFilename | |
copyPath(sourcePath, destinPath) | |
set cmd to "/bin/ls -alww " & quoted form of chromeSource & " | grep \"Local Storage\"" | |
set someInfo to tryShellScript(cmd) | |
set someInfo to "ls (list) of Local Storage itself for Chrome profile " & profile & ":" & return & someInfo | |
catLog(someInfo) | |
set localStoragePath to posixHome & "Library/Application Support/Google/Chrome/" & profile & "/Local Storage/" | |
set cmd to "/bin/ls -alww " & quoted form of localStoragePath | |
set someInfo to tryShellScript(cmd) | |
set someInfo to "ls (list) of Local Storage contents for Chrome profile " & profile & ":" & return & someInfo | |
catLog(someInfo) | |
set ourLocalStorageFilename to "chrome-extension_" & ourChromeExtensionId & "_0.localstorage" | |
set ourLocalStoragePath to localStoragePath & ourLocalStorageFilename | |
set cmd to "/bin/ls -alww " & quoted form of (ourLocalStoragePath) | |
set someInfo to tryShellScript(cmd) | |
catLog("ls (list) of *our* Local Storage file for Chrome profile " & profile & ":" & return & someInfo) | |
set destinDir to tempDirPath & "Chrome-App-Support/" & profile & "/LocalStorage/" | |
set cmd to "/bin/rm -Rf " & destinDir | |
tryShellScript(cmd) | |
set cmd to "/bin/mkdir " & destinDir | |
tryShellScript(cmd) | |
set destinPath to destinDir & ourLocalStorageFilename | |
copyPath(ourLocalStoragePath, destinPath) | |
end repeat | |
end try | |
appendScriptView("Getting any crash reports") | |
try -- Because we'll take whatever we can get | |
-- Add user and system crash reports to zip directory | |
repeat with aProcessName in ourProcessNames | |
addCrashReports(crashReportsOut, aProcessName, "~", aProcessName & "_User_Crashes") | |
addCrashReports(crashReportsOut, aProcessName, "", aProcessName & "_System_Crashes") | |
end repeat | |
end try | |
appendScriptView("Getting " & companyName & " app versions") | |
set daVersions to "" | |
repeat with aAppName in appNames | |
set daVersions to daVersions & (reportVersionOfApplicationId(companyUTI & "." & aAppName)) & return | |
end repeat | |
set daVersions to trim(daVersions, " | |
", 1) | |
catLog(daVersions) | |
appendScriptView("Getting web browser versions") | |
set daVersions to "" | |
set daVersions to daVersions & (reportVersionOfApplicationId("org.mozilla.Camino")) & return | |
set daVersions to daVersions & (reportVersionOfApplicationId("com.google.Chrome")) & return | |
set daVersions to daVersions & (reportVersionOfApplicationId("org.chromium.Chromium")) & return | |
set daVersions to daVersions & (reportVersionOfApplicationId("org.mozilla.Firefox")) & return | |
set daVersions to daVersions & (reportVersionOfApplicationId("de.icab.iCab")) & return | |
set daVersions to daVersions & (reportVersionOfApplicationId("com.omnigroup.OmniWeb5")) & return | |
set daVersions to daVersions & (reportVersionOfApplicationId("com.operasoftware.Opera")) & return | |
set daVersions to daVersions & (reportVersionOfApplicationId("com.apple.Safari")) & return | |
catLog(daVersions) | |
appendScriptView("Getting info on Firefox extensions installed (carefully)") | |
-- Added this 'try' block for G. Zwaenepoel error -1728 | |
try | |
set toolPath to quoted form of (pathToMe & "Contents/MacOS/FirefoxProfileFinder") | |
set profileRecordsString to tryShellScript(toolPath) | |
set profileRecords to split(profileRecordsString, return) | |
set foxtensionInfo to "" | |
set foxWorkingName to "foxworks" | |
set foxWorkingDir to tempDirPath & foxWorkingName & "/" | |
set tempZipPath to foxWorkingDir & "zippedExtension.zip" | |
set tempUnzipPath to foxWorkingDir & "unzippedExtension" | |
set cmd to "cd " & tempDirPath & " ; /bin/mkdir " & foxWorkingName | |
tryShellScript(cmd) | |
repeat with profileRecord in profileRecords | |
set profileRecordColumns to split(profileRecord, " ") | |
set profileName to item 1 of profileRecordColumns | |
set profilePath to item 2 of profileRecordColumns | |
set extensionPath to profilePath & "/extensions/" | |
set cmd to "/bin/ls -1wwwww " & quoted form of extensionPath | |
-- try | |
set extensionDirsString to tryShellScript(cmd) | |
set extensionDirs to split(extensionDirsString, return) | |
set nExts to count of extensionDirs | |
set foxtensionInfo to foxtensionInfo & return & return & "***** PROFILE NAME: " & profileName & return & "***** PATH: " & profilePath & return & "***** Found " & nExts & " extensions:" & return | |
repeat with extensionDir in extensionDirs | |
set fileExtension to getFilenameExtension(extensionDir) | |
if fileExtension is "xpi" then | |
-- This extension is zipped. Copy to temporary directory and unzip. | |
-- try | |
set cmd to "/bin/cp -f " & quoted form of (extensionPath & extensionDir) & " " & tempZipPath | |
tryShellScript(cmd) | |
set cmd to "/usr/bin/unzip -o " & tempZipPath & " -d " & tempUnzipPath | |
tryShellScript(cmd) | |
set installrdfPath to tempUnzipPath & "/install.rdf" | |
-- end try | |
else | |
-- This extension is already unzipped | |
set installrdfPath to extensionPath & extensionDir & "/install.rdf" | |
end if | |
set rdfText to readTextFile(installrdfPath) | |
if rdfText is missing value then | |
set rdfText to "File not found!" | |
else | |
-- Change "\n" from the rdf file text to "\r" | |
set rdfText to searchAndReplace(rdfText, " | |
", return) | |
end if | |
set foxtensionInfo to foxtensionInfo & return & "*** install.rdf for " & extensionDir & "***" & return & rdfText & return | |
end repeat | |
end repeat | |
-- Remove the temporary directory into which we unzipped zipped extensions | |
set cmd to "/bin/rm -Rf " & foxWorkingDir | |
tryShellScript(cmd) | |
set fxInfoFilename to "Firefox-Extension-Info.txt" | |
writeTextToPosixPath(tempDirPath & fxInfoFilename, foxtensionInfo, false) | |
catLog("Succeeded getting " & fxInfoFilename) | |
on error errorString number aCode | |
catLog("Getting Firefox extension info, error occurred: " & aCode & " occurred. Details: " & errorString) | |
end try | |
appendScriptView("Getting filtered list of loaded agents") | |
repeat with anAppName in appNames | |
try | |
set filteredList to {} | |
set cmd to "launchctl list" | |
set launchctlResult to do shell script cmd | |
set agentLines to split(launchctlResult, ASCII character 13) | |
if (count of agentLines) is less than 2 then | |
set agentLines to split(launchctlResult, ASCII character 10) | |
end if | |
repeat with agentLine in agentLines | |
repeat with aAppName in appNames | |
if (verifyTextContains(agentLine, aAppName)) then | |
set end of filteredList to agentLine | |
end if | |
end repeat | |
end repeat | |
set filteredResult to join(ASCII character 10, filteredList) | |
set launchctlLoadsFilename to "launchctlLoads.txt" | |
writeTextToPosixPath(tempDirPath & launchctlLoadsFilename, filteredResult, false) | |
on error errString number errNum | |
log "errNum=" & errNum & " errString: " & errString | |
end try | |
log filteredList | |
end repeat | |
appendScriptView("Getting system info.") | |
try | |
-- Add system hardware and software to answers Log | |
set sysProfile to nanoSystemProfile() | |
catLog(sysProfile) | |
end try | |
try | |
-- Add answers Log to zip directory | |
set aPath to tempDirPath & "/answers.txt" | |
writeTextToPosixPath(aPath, logText, false) | |
end try | |
delay 2 | |
-- Add filtered console log to zip directory | |
-- We use separate 'try' blocks because, if an error occurs, it will jump to the end of the block, and we don't want to jump too far. | |
appendScriptView("Unarchiving system logs") | |
set logsOutDir to tempDirPath & "ConsoleLogs/" | |
set cmd to "/bin/mkdir " & logsOutDir | |
tryShellScript(cmd) | |
-- We unarchive the previous week's logs first, then filter them for each application in turn | |
tryUnarchiveWeeksLogsToDirPath(logsOutDir) | |
set msg to "Filtering system logs to get only entries logged by " | |
repeat with aProcessName in ourProcessNames | |
set msg to msg & " " & aProcessName | |
end repeat | |
appendScriptView(msg) | |
-- See Note 20131005 | |
filterMessagesToNewFile(ourProcessNames, logsOutDir, pathToMe) | |
-- Done with the unarchived previous week's logs. | |
tryRemoveWeeksConsoleLogsInDirPath(logsOutDir) | |
-- Zip the zip directory | |
appendScriptView("Zipping everything up.") | |
set cmd to "cd ~/Desktop ; /usr/bin/zip -r " & zipName & " " & zipName & " ; /bin/rm -Rf " & zipName | |
tryShellScript(cmd) | |
appendScriptView("All done.") | |
endScriptViewAsking(true) | |
set zipFilename to zipName & ".zip" | |
set cmd to "/usr/bin/stat -f %z ~/Desktop/" & zipFilename | |
set fileSize to tryShellScript(cmd) as integer | |
set doneButton to "Done" | |
if fileSize is greater than limitEmailAttachment then | |
set sendingAdvice to "When you click '" & doneButton & "', two windows will open: An email message to us in your email app, and a window to our File Uploader in your web browser. Please fill in and send the email to us, and use the File Uploader to upload " & zipFilename & " to us." | |
set needsFileUploader to true | |
else | |
set sendingAdvice to "When you click '" & doneButton & "', an email message to us will open in your email app. Please fill in the information, attach the file " & zipFilename & " and send the email to us" | |
set needsFileUploader to false | |
end if | |
set aPrompt to "All done. | |
The information archive is in a file on your Desktop named:" & return & return & " " & zipName & ".zip" & return & return & sendingAdvice & return & return & "Thank you for helping us to support " & appName & ". | |
Jerry Krinock" | |
display dialog aPrompt with title genericTitle buttons {"Done"} default button "Done" | |
-- Actually, we should percent-escape encode appName and zipName. But they're all ASCII at this time, so it's todo. Maybe could call out to Perl or something. Here's a crude start: | |
set encodedAppName to searchAndReplace(appName, " ", "%20") | |
set encodedZipName to searchAndReplace(zipFilename, " ", "%20") | |
set mailtoThing to "mailto:support@sheepsystems.com?subject=%20" & encodedAppName & "%20Trouble%20Zip&body=%0A%0ASuggested%20Format%3A%0A%0A*%20Here%20is%20what%20I%20did%3A%0A%0A%0A%0A*%20Here%20is%20what%20I%20expected%20to%20happen%3A%0A%0A%0A%0A*%20Here%20is%20what%20happened%20instead%3A" | |
if needsFileUploader is true then | |
set mailtoThing to mailtoThing & "%0A%0A%0A%0A**%20Please%20upload%20" & encodedZipName & "%20from%20your%20Desktop%20to%20our%20File%20Uploader." | |
else | |
set mailtoThing to mailtoThing & "%0A%0A%0A%0A**%20Please%20attach%20" & encodedZipName & "%20from%20your%20Desktop." | |
end if | |
open location mailtoThing | |
if needsFileUploader is true then | |
open location fileUploaderUrl | |
end if | |
-------------- END OF MAIN PROGRAM | |
-------------- BEGINNING OF HANDLERS | |
on displayizeFilenames(filenames, extension) | |
set displayNames to {} | |
repeat with filename in filenames | |
set displayName to searchAndReplace(filename, "|", " ") | |
set displayName to searchAndReplace(displayName, "thisUser", " ") | |
set displayName to searchAndReplace(displayName, extension, " ") | |
-- Do this several times to get rid of multiple consecutive spaces | |
set displayName to searchAndReplace(displayName, " ", " ") | |
set displayName to searchAndReplace(displayName, " ", " ") | |
set displayName to searchAndReplace(displayName, " ", " ") | |
set displayName to trim(displayName, " ", 2) | |
set displayNames to displayNames & displayName | |
end repeat | |
return displayNames | |
end displayizeFilenames | |
-- When done with the files produced by this handler, call tryRemoveWeeksConsoleLogsFromDesktop() to delete them | |
on tryUnarchiveWeeksLogsToDirPath(dirPath) | |
repeat with logFile from 8 to 0 by -1 -- in AppleScript, "7 to 0" means "7 thru 0" | |
appendScriptView("Unarchiving Log " & (logFile as Unicode text)) | |
try | |
-- If the computer was running each morning at 12:30, log files are "rotated" by the newsyslog program such that | |
-- logFile =8 means 8 days ago, which is in the file named system.log.7.bz2 | |
-- logFile =7 means 7 days ago, which is in the file named system.log.6.bz2 | |
-- logFile =6 means 6 days ago, which is in the file named system.log.5.bz2 | |
-- … | |
-- logFile =2 means 2 days ago, which is in the file named system.log.1.bz2 | |
-- logFile =1 means yesterday, which is the file named system.log.0.bz2 | |
-- logFile =0 means today, which is in the file named system.log and has not been compressed yet. | |
-- However if the computer is not awake at 12:30, then the newsyslog program will not run, and a single log file will contain logs for multiple consecutive days. | |
if (logFile is greater than 0) then | |
-- Logs are compressed | |
-- First, we copy the file from /var/log/ to our temporary directory. One reason to do this is because, if user does not have required admin privileges, any action upon /var/log/xxx.log may fail. | |
set filename to "system.log." & (logFile - 1) | |
set sourceLogArchivePath to "/var/log/" & filename & ".bz2" | |
-- However, we need to make room for the uncompressed log for today at index 0, so we don't use the -1 offset for the destination filename | |
set filename to "system.log." & logFile | |
set destinLogArchivePath to dirPath & filename & ".bz2" | |
set cmd to "/bin/cp -pX " & quoted form of sourceLogArchivePath & " " & quoted form of destinLogArchivePath | |
try | |
do shell script cmd | |
on error errorString number aCode | |
-- Unfortunately, aCode is useless since cp returns the same exit value, 1, for both inadequate permissions and file not found. So we'll have to parse errorString for the word "Permission", as in "Permission denied". | |
set badPermissions to verifyTextContains(errorString, "Permission") | |
if badPermissions then | |
-- Retry with auth services dialog | |
if gotAuthorization is false then | |
-- Prepare the user for the Authentication dialog which is about to appear. | |
display dialog "We need administrator access in order to read your system logs and filter out relevant entries." | |
end if | |
do shell script cmd with administrator privileges | |
set gotAuthorization to true | |
-- But wait there's more! Because we used admin privileges, the copy that we just made is owned by root, so filterMessagesToNewFile() won't be able to open it unless we chown so that we own it... | |
set myShortName to short user name of (system info) | |
-- The authorization we just received should be good for 5 minutes | |
set cmd to "/usr/sbin/chown " & quoted form of myShortName & " " & quoted form of destinLogArchivePath | |
try | |
if gotAuthorization is true then | |
do shell script cmd with administrator privileges | |
else | |
do shell script cmd | |
end if | |
end try | |
end if | |
end try | |
-- Unarchive the copy of this day's .bz2 log file. | |
set cmd to "usr/bin/bzcat " & destinLogArchivePath & " > " & dirPath & filename | |
try | |
if gotAuthorization is true then | |
do shell script cmd with administrator privileges | |
else | |
do shell script cmd | |
end if | |
on error errorString number aCode | |
catLog("Executing: " & cmd & return & ". Error: " & errorString) | |
end try | |
-- Now delete the copy of the bz2 file, since we don't need it any more | |
set cmd to "/bin/rm " & destinLogArchivePath | |
try | |
if gotAuthorization is true then | |
do shell script cmd with administrator privileges | |
else | |
do shell script cmd | |
end if | |
on error errorString number aCode | |
catLog("Executing: " & cmd & return & ". Error: " & errorString) | |
end try | |
else | |
-- Get log for today. This is not compressed. | |
set filename to "system.log" | |
set sourceLogPath to "/var/log/" & filename | |
set destinLogPath to dirPath & filename & ".0" | |
set cmd to "/bin/cp -pX " & quoted form of sourceLogPath & " " & quoted form of destinLogPath | |
try | |
do shell script cmd | |
on error errorString number aCode | |
-- Unfortunately, aCode is useless since cp returns the same exit value, 1, for both inadequate permissions and file not found. So we'll have to parse errorString for the word "Permission", as in "Permission denied". | |
set badPermissions to verifyTextContains(errorString, "Permission") | |
if badPermissions then | |
-- Retry with auth services dialog | |
if gotAuthorization is false then | |
-- Prepare the user for the Authentication dialog which is about to appear. | |
display dialog "We need administrator access in order to read your system logs and filter out relevant entries." | |
end if | |
do shell script cmd with administrator privileges | |
set gotAuthorization to true | |
-- But wait there's more! Because we used admin privileges, the copy that we just made is owned by root, so filterMessagesToNewFile() won't be able to open it unless we chown so that we own it... | |
set myShortName to short user name of (system info) | |
-- The authorization we just received should be good for 5 minutes | |
set cmd to "/usr/sbin/chown " & quoted form of myShortName & " " & quoted form of destinLogPath | |
try | |
if gotAuthorization is true then | |
do shell script cmd with administrator privileges | |
else | |
do shell script cmd | |
end if | |
end try | |
end if | |
end try | |
end if | |
end try | |
end repeat | |
end tryUnarchiveWeeksLogsToDirPath | |
-- See Note 20131005 | |
on filterConsoleMessages(logPath, appNames, logFile, outDir, pathToMe) | |
set logFilePartOfName to "tiger" | |
if logFile is not missing value then | |
set logFilePartOfName to logFile | |
end if | |
set logPath to quoted form of logPath | |
set destinPath to quoted form of (outDir & "system." & logFilePartOfName & ".log") | |
set perlFilterToolPath to quoted form of (pathToMe & "Contents/Resources/Scripts/filterConsoleLog.pl") | |
set cmd to perlFilterToolPath & " " & logPath & " " & destinPath | |
repeat with aAppName in appNames | |
set cmd to cmd & " " & quoted form of aAppName | |
end repeat | |
appendScriptView(" From " & logFilePartOfName & " logs ago") | |
tryShellScript(cmd) | |
end filterConsoleMessages | |
-- See Note 20131005 | |
on filterMessagesToNewFile(appNames, dirPath, pathToMe) | |
-- The log file numbered 8 is the oldest. We want oldest first. | |
repeat with logFile from 8 to 0 by -1 | |
-- Assumes that we have previously run tryUnarchiveWeeksLogsToDirPath() | |
set unarchivedLogPath to dirPath & "system.log." & logFile | |
-- Filter and add this day's log to leopardMessages | |
set dayMessages to missing value | |
filterConsoleMessages(unarchivedLogPath, appNames, logFile, dirPath, pathToMe) | |
end repeat | |
-- Note: Tiger logs are in one big file instead of separate daily files. | |
-- Todo: Make this work for non-admin users. It probably won't. | |
set tigerConsoleLogDirs to {} | |
tell application "Finder" | |
try | |
set tigerConsoleLogDir to folder "Console" of folder "Logs" of folder "Library" of startup disk | |
set tigerConsoleLogDirs to folders of tigerConsoleLogDir | |
end try | |
end tell | |
set tigerMessages to {} | |
repeat with aFolder in tigerConsoleLogDirs | |
set user50x to (name of aFolder as string) | |
set aLogPath to (POSIX path of (tigerConsoleLogDir as string)) & user50x & "/console.log" | |
filterConsoleMessages(aLogPath, appNames, missing value, dirPath, pathToMe) | |
end repeat | |
end filterMessagesToNewFile | |
on tryRemoveWeeksConsoleLogsInDirPath(dirPath) | |
try | |
set leopardMessages to "" | |
-- The log file numbered 8 is from 8 days ago. We want oldest first. | |
repeat with day from 8 to 0 by -1 | |
try | |
set filename to "system.log." & day | |
set unarchivedLogPath to dirPath & filename | |
-- Delete the file | |
set cmd to "/bin/rm " & unarchivedLogPath | |
appendScriptView("Removing unfiltered log:" & return & " " & unarchivedLogPath) | |
do shell script cmd | |
end try | |
end repeat | |
end try | |
end tryRemoveWeeksConsoleLogsInDirPath | |
on bundleIdentifier(uti, name) | |
return uti & "." & name | |
end bundleIdentifier | |
on searchAndReplace(this_text, search_string, replacement_string) | |
set oldastid to AppleScript's text item delimiters | |
set AppleScript's text item delimiters to search_string | |
-- We're going to split this_text at each searchString. However, this will miss search_string if it is at the beginning or ending of this_text, since no split will be made there. We fix that by temporarily appending a prefix and suffix "X". | |
set this_text to "X" & this_text & "X" | |
set the item_list to every text item of this_text | |
set AppleScript's text item delimiters to replacement_string | |
set this_text to item_list as string | |
set AppleScript's text item delimiters to oldastid | |
-- Remove the temporary prefix and suffix | |
set aLength to ((count of characters of this_text) - 1) | |
set this_text to characters 2 thru aLength of this_text as string | |
return this_text | |
end searchAndReplace | |
on verifyTextContains(aText, aTarget) | |
set oldastid to AppleScript's text item delimiters | |
set AppleScript's text item delimiters to aTarget | |
set answer to false | |
try | |
set textItems to text items of aText | |
set nParts to count of textItems | |
if nParts is greater than 1 then | |
set answer to true | |
end if | |
end try | |
set AppleScript's text item delimiters to oldastid | |
return answer | |
end verifyTextContains | |
on catLog(addition) | |
set newLogText to (logText & addition & return & return) | |
try | |
set logText to newLogText | |
end try | |
end catLog | |
on addInfoForBrowser(browserName, pathToMe) | |
local bookmarksPath | |
set posixHome to POSIX path of (path to home folder) | |
set SnapshotsDir to posixHome & "Library/Application Support/BookMacster/SyncSnapshots/" | |
if browserName is "Safari" then | |
set end of bookmarksPaths to posixHome & "Library/Safari/Bookmarks.plist" | |
set end of bookmarksPaths to SnapshotsDir & "Safari||thisUser|||" | |
else if browserName is "Camino" then | |
set end of bookmarksPaths to posixHome & "Library/Application Support/Camino/bookmarks.plist" | |
set end of bookmarksPaths to SnapshotsDir & "Camino||thisUser|||" | |
else if browserName is "Google Chrome" then | |
set end of bookmarksPaths to posixHome & "Library/Application Support/Google/Chrome/Default/Bookmarks" | |
set end of bookmarksPaths to posixHome & "Library/Application Support/Camino/bookmarks.plist" | |
set end of bookmarksPaths to SnapshotsDir & "Chrome||thisUser|||" | |
else if browserName is "iCab" then | |
set end of bookmarksPaths to posixHome & "Library/Preferences/iCab/iCab 4 Bookmarks" | |
set end of bookmarksPaths to SnapshotsDir & "ICab||thisUser|||" | |
else if browserName is "OmniWeb" then | |
set end of bookmarksPaths to posixHome & "Library/Application Support/OmniWeb 5/Bookmarks.html" | |
set end of bookmarksPaths to posixHome & "Library/Application Support/OmniWeb 5/Published.html" | |
set end of bookmarksPaths to posixHome & "Library/Application Support/OmniWeb 5/Favorites.html" | |
set end of bookmarksPaths to posixHome & "Library/Application Support/OmniWeb 5/ServerBookmarks" | |
set end of bookmarksPaths to SnapshotsDir & "OmniWeb||thisUser|||" | |
else if browserName is "Opera" then | |
set end of bookmarksPaths to posixHome & "Library/Preferences/Opera Preferences/bookmarks.adr" | |
set end of bookmarksPaths to posixHome & "Library/Preferences/Opera Preferences/operaprefs.ini" | |
set end of bookmarksPaths to posixHome & "Library/Preferences/Opera Preferences 1050/bookmarks.adr" | |
set end of bookmarksPaths to posixHome & "Library/Preferences/Opera Preferences 1050/operaprefs.ini" | |
set end of bookmarksPaths to posixHome & "Library/Preferences/Opera Preferences 1150/bookmarks.adr" | |
set end of bookmarksPaths to posixHome & "Library/Preferences/Opera Preferences 1150/operaprefs.ini" | |
set end of bookmarksPaths to posixHome & "Library/Preferences/Opera Preferences 1200/bookmarks.adr" | |
set end of bookmarksPaths to posixHome & "Library/Preferences/Opera Preferences 1200/operaprefs.ini" | |
set end of bookmarksPaths to SnapshotsDir & "Opera||thisUser|||" | |
else if browserName is "Firefox" then | |
set toolPath to quoted form of (pathToMe & "Contents/MacOS/FirefoxProfileFinder") | |
set profileRecordsString to do shell script toolPath | |
set profileRecords to split(profileRecordsString, return) | |
set profileNames to {} | |
set profilePaths to {} | |
set profileNeeded to missing value | |
repeat with profileRecord in profileRecords | |
set profileRecordColumns to split(profileRecord, " ") | |
set end of profileNames to item 1 of profileRecordColumns | |
set end of profilePaths to item 2 of profileRecordColumns | |
end repeat | |
if ((count of profileRecords) is greater than 1) then | |
set aPrompt to "You have more than one Firefox profile on your Mac account. Please select the Firefox profile you are having (the most) trouble with." | |
set profileNeeded to choose from list profileNames with title genericTitle with prompt aPrompt OK button name "Done" | |
catLog(aPrompt & " ANSWER: " & profileNeeded) | |
if profileNeeded is false then | |
return | |
end if | |
else | |
set profileNeeded to item 1 of profileNames | |
end if | |
set i to 1 | |
repeat with profileName in profileNames | |
if profileName as string is profileNeeded as string then | |
set profilePath to item i of profilePaths | |
set end of bookmarksPaths to profilePath & "/bookmarks.html" | |
set end of bookmarksPaths to profilePath & "/places.sqlite" | |
set end of bookmarksPaths to profilePath & "/places.sqlite-shm" | |
set end of bookmarksPaths to profilePath & "/places.sqlite-wal" | |
set end of bookmarksPaths to profilePath & "/places.sqlite.corrupt" | |
set end of bookmarksPaths to profilePath & "/prefs.js" | |
set end of bookmarksPaths to profilePath & "/bookmarkbackups" | |
set end of bookmarksPaths to SnapshotsDir & "Firefox|" & profileName & "|thisUser|||" | |
exit repeat | |
end if | |
set i to i + 1 | |
end repeat | |
else | |
-- browserName is of the form "Diggo jerrykrinock". Replace the " " with a "|" | |
set theSplit to split(browserName, " ") | |
set extoreName to item 1 of theSplit | |
set profileName to item 2 of theSplit | |
catLog("Split \"" & browserName & "\" into extoreName=" & extoreName & " and profileName=" & profileName) | |
set end of bookmarksPaths to SnapshotsDir & extoreName & "|" & profileName & "|thisUser|||" | |
end if | |
end addInfoForBrowser | |
-- If file is not found, returns 'missing value' | |
-- If file exists but is empty, returns an empty string | |
on readTextFile(path) | |
try | |
-- 'info for' could return a value, but we ignore it | |
info for path | |
on error | |
return missing value | |
end try | |
set theFile to open for access (path as POSIX file) | |
try | |
set theData to read theFile as text | |
on error | |
return "" | |
end try | |
close access theFile | |
return theData | |
end readTextFile | |
-- NEWER AND BETTER. Handles UTF8-encoded files, with or without BOM (byte order mark) | |
--open for access theFile | |
--set fileContents to (read theFile for (get eof theFile) as «class utf8») | |
--close access theFile | |
on testAccessToDirectory(testDir) | |
set testText to "This is a test!" | |
set failedStep to missing value | |
set aResult to testWriteTextToDir(testText, testDir) | |
set testFilePath to aPath of result | |
set success to succeeded of aResult | |
if success is true then | |
set aResult to testReadTextFile(testText, testFilePath) | |
set success to succeeded of aResult | |
if success is true then | |
set aResult to testDeleteFilePath(testFilePath) | |
set success to succeeded of aResult | |
if success is false then | |
set failedStep to "deleting" | |
end if | |
else | |
set failedStep to "reading" | |
end if | |
else | |
set failedStep to "writing" | |
end if | |
if success is true then | |
set narrative to "Write, Read and Delete: All succeeded for " & testFilePath | |
else | |
set narrative to "Failed " & failedStep & " of " & testFilePath & " with errNum:" & (errNum of aResult) & ": " & (errMsg of aResult) | |
end if | |
return narrative | |
end testAccessToDirectory | |
on testWriteTextToDir(testText, aDir) | |
set aDir to addTrailingSlashIfMissing(aDir) | |
set aPath to aDir & "Test.txt" | |
set succeeded to true | |
set errMsg to missing value | |
set errNum to missing value | |
try | |
writeTextToPosixPath(aPath, testText, false) | |
on error errMsg number errNum | |
set succeeded to false | |
end try | |
set aResult to {aPath:aPath, succeeded:succeeded, errNum:errNum, errMsg:errMsg} | |
return aResult | |
end testWriteTextToDir | |
on testReadTextFile(expectedText, posixPath) | |
set posixPath to expandHomeTildePathPrefix(posixPath) | |
set succeeded to true | |
set errMsg to missing value | |
set errNum to missing value | |
try | |
-- 'info for' could return a value, but we ignore it | |
info for posixPath | |
on error errMsg number errNum | |
set succeeded to false | |
end try | |
if succeeded is true then | |
try | |
open for access (posixPath as POSIX file) | |
on error errMsg number errNum | |
set succeeded to false | |
end try | |
end if | |
if succeeded is true then | |
set foundText to "" | |
try | |
set foundText to (read posixPath for (get eof posixPath) as «class utf8») | |
on error errMsg number errNum | |
set succeeded to false | |
end try | |
end if | |
set compareResult to false | |
if succeeded is true then | |
try | |
if (characters of (foundText)) is equal to (characters of expectedText) then | |
set compareResult to true | |
else | |
set errNum to 444444 | |
set errMsg to "Found: " & foundText & " Expected: " & expectedText | |
end if | |
on error errMsg number errNum | |
set succeeded to false | |
end try | |
end if | |
close access posixPath | |
set aResult to {aPath:posixPath, succeeded:succeeded, errNum:errNum, errMsg:errMsg} | |
end testReadTextFile | |
on testDeleteFilePath(posixPath) | |
set posixPath to expandHomeTildePathPrefix(posixPath) | |
set succeeded to true | |
set errNum to missing value | |
set errMsg to missing value | |
set cmd to "rm " & quoted form of posixPath | |
try | |
do shell script cmd | |
on error errMsg number errNum | |
set succeeded to false | |
end try | |
set aResult to {aPath:posixPath, succeeded:succeeded, errNum:errNum, errMsg:errMsg} | |
end testDeleteFilePath | |
on writeTextToPosixPath(aPath, newText, doAppend) | |
-- filename must not end in ".log" or else a "file already open" error will result. | |
-- Maybe some system logging daemon takes over all .log files?? | |
-- Uses File Read/Write suite of the Standard Additions scripting addition | |
set oldastid to AppleScript's text item delimiters | |
set AppleScript's text item delimiters to {""} | |
set aPath to expandHomeTildePathPrefix(aPath) | |
set filePath to (POSIX file aPath as text) | |
set fileRef to open for access filePath with write permission | |
if doAppend is true then | |
write newText to fileRef starting at eof | |
else | |
-- Wipe out all existing text first | |
set eof of fileRef to 0 | |
write newText to fileRef | |
end if | |
close access fileRef | |
set AppleScript's text item delimiters to oldastid | |
end writeTextToPosixPath | |
on addTrailingSlashIfMissing(aString) | |
set chars to characters of aString | |
if ((count of chars) is greater than 1) then | |
if (last item of chars) is not "/" then | |
set aString to aString & "/" | |
end if | |
else if chars is {"~"} then | |
set aString to "~/" | |
else | |
set aString to "/" | |
end if | |
return aString | |
end addTrailingSlashIfMissing | |
(* Replaces the "~" POSIX shorthand for home folder with its actual path and returns the resulting path. *) | |
on expandHomeTildePathPrefix(aPath) | |
if ((characters of aPath) is equal to (characters of "~")) then | |
set aPath to (POSIX path of (path to home folder)) | |
-- Remove the trailing / | |
set aPath to characters 1 thru ((count of characters of aPath) - 1) of aPath as string | |
else if ((characters of aPath) is equal to (characters of "~/")) then | |
set aPath to (POSIX path of (path to home folder)) | |
else if aPath begins with "~" then | |
set home_posix_path to POSIX path of (path to home folder) | |
set aPath to home_posix_path & characters 3 thru end of aPath | |
end if | |
return aPath | |
end expandHomeTildePathPrefix | |
on nanoSystemProfile() | |
return do shell script "/usr/sbin/system_profiler SPHardwareDataType SPSoftwareDataType SPUniversalAccessDataType" | |
end nanoSystemProfile | |
on microSystemProfile() | |
return do shell script "/usr/sbin/system_profiler SPHardwareDataType SPMemoryDataType SPSoftwareDataType SPUniversalAccessDataType SPFrameworksDataType SPApplicationsDataType" | |
end microSystemProfile | |
-- trim_chars: A string of chars to trim. | |
-- Use " \t\n" to trim spaces, tabs and newlines | |
-- trim_indicator: trim from 0 = beginning, 1 = end, 2 = both | |
-- returns the trimmed string | |
on trim(this_text, trim_chars, trim_indicator) | |
set x to the length of the trim_chars | |
-- TRIM BEGINNING | |
if the trim_indicator is in {0, 2} then | |
repeat while this_text begins with the trim_chars | |
try | |
set this_text to characters (x + 1) thru -1 of this_text as string | |
on error | |
-- the text contains nothing but the trim characters | |
return "" | |
end try | |
end repeat | |
end if | |
-- TRIM ENDING | |
if the trim_indicator is in {1, 2} then | |
repeat while this_text ends with the trim_chars | |
try | |
set this_text to characters 1 thru -(x + 1) of this_text as string | |
on error | |
-- the text contains nothing but the trim characters | |
return "" | |
end try | |
end repeat | |
end if | |
return this_text | |
end trim | |
on split(someText, delimiter) | |
if someText is missing value then | |
return {} | |
else | |
set oldastid to AppleScript's text item delimiters | |
set AppleScript's text item delimiters to delimiter | |
set someText to someText's text items | |
set AppleScript's text item delimiters to oldastid | |
if someText is missing value then | |
set someText to {} | |
end if | |
return someText | |
end if | |
end split | |
on join(joiner, aList) | |
set nJoiners to ((count of aList) - 1) | |
set answer to "" | |
set i to 0 | |
repeat with aItem in aList | |
set answer to answer & aItem | |
if i is less than nJoiners then | |
set answer to answer & joiner | |
end if | |
set i to i + 1 | |
end repeat | |
return answer | |
end join | |
on reportVersionOfApplicationId(appId) | |
set v to "not found" | |
try | |
set v to get version of application id appId | |
end try | |
set comps to split(appId, ".") | |
try | |
set aName to item 3 of comps | |
on error | |
set aName to appId | |
end try | |
return aName & " version is " & v | |
end reportVersionOfApplicationId | |
on addCrashReports(tempDirPath, appName, sourcePrefix, destinName) | |
-- Make directory for user crash reports in zip directory | |
set userCrashesZipDir to tempDirPath & destinName | |
set userCrashesZipDir to searchAndReplace(userCrashesZipDir, " ", "\\ ") | |
set cmd to "/bin/rm -Rf " & userCrashesZipDir & " ; /bin/mkdir " & userCrashesZipDir | |
tryShellScript(cmd) | |
-- Add any user crash reports to zip directory | |
-- from earlier versions | |
set aPath to sourcePrefix & "/Library/Logs/CrashReporter/" & appName & "_*" | |
set bashSafePath to searchAndReplace(aPath, " ", "\\ ") | |
set cmd to "cp -pX " & bashSafePath & " " & userCrashesZipDir | |
tryShellScript(cmd) | |
-- from 10.8, maybe 10.7? | |
set aPath to sourcePrefix & "/Library/Logs/DiagnosticReports/." & appName & "*" | |
set bashSafePath to searchAndReplace(aPath, " ", "\\ ") | |
set cmd to "cp -pX " & bashSafePath & " " & userCrashesZipDir | |
tryShellScript(cmd) | |
-- Add user crash history to zip directory | |
-- (Probably not too interesting, just seems to show path to latest crash report, but what the hell. Leopard only.) | |
set aPath to sourcePrefix & "/Library/Logs/CrashReporter/." & appName & "*.plist" | |
set bashSafePath to searchAndReplace(aPath, " ", "\\ ") | |
set cmd to "cp -pX " & bashSafePath & " " & userCrashesZipDir | |
tryShellScript(cmd) | |
end addCrashReports | |
to geekDateDaysAgo(daysAgo) -- Old_date is text, not a date. | |
set old_date to ((current date) - 24 * 3600 * daysAgo) as string | |
set {year:y, month:m, day:d} to date old_date | |
tell (y * 10000 + m * 100 + d) as string to text 1 thru 4 & text 5 thru 6 & text 7 thru 8 | |
end geekDateDaysAgo | |
(* removeTrailingSlash indicates whether or not trailing slash should be removed too *) | |
on removeLastPathComponent(aPath, removeTrailingSlash) | |
if aPath is missing value then | |
return missing value | |
end if | |
set oldastid to AppleScript's text item delimiters | |
set AppleScript's text item delimiters to the "/" | |
set the item_list to every text item of aPath | |
set lastItem to last item of item_list | |
if lastItem is not missing value then | |
set cutLength to (count of lastItem) as string | |
set wholeLength to length of aPath | |
set newLength to wholeLength - cutLength | |
if removeTrailingSlash is true then | |
set newLength to newLength - 1 | |
end if | |
set AppleScript's text item delimiters to oldastid | |
set answer to characters 1 thru newLength of aPath as string | |
else | |
set answer to aPath | |
end if | |
return answer | |
end removeLastPathComponent | |
on deleteDuplicates(aList) | |
if (aList is missing value) then | |
return {} | |
end if | |
set list2 to {} | |
repeat with x from 1 to count of items of aList | |
set n to item x of aList | |
if n is not in list2 then set end of list2 to n | |
end repeat | |
return list2 | |
end deleteDuplicates | |
-- Returns 'true' or 'false'. Case-insensitive. If aSuffix is an empty string, returns 'true'. | |
on hasSuffix(aString, aSuffix) | |
set stringLength to length of aString | |
set suffixLength to length of aSuffix | |
--log (stringLength & " -- " & suffixLength) | |
if stringLength is less than suffixLength then | |
return false | |
end if | |
if stringLength is 0 then | |
if suffixLength is 0 then | |
return true | |
else | |
return false | |
end if | |
end if | |
if suffixLength is 0 then | |
return true | |
end if | |
set theEnd to characters (stringLength - suffixLength + 1) thru stringLength of aString as string | |
-- It appears that AppleScript does a case-insensitive comparison in the following: | |
if theEnd is aSuffix then | |
return true | |
end if | |
return false | |
end hasSuffix | |
(* Gets the part of a filename after the last slash *) | |
on getLastPathComponent(aPath) | |
set oldastid to AppleScript's text item delimiters | |
set AppleScript's text item delimiters to the "/" | |
set the item_list to every text item of aPath | |
set answer to last item of item_list as string | |
set AppleScript's text item delimiters to oldastid | |
return answer | |
end getLastPathComponent | |
on removeFilenameExtension(this_name) | |
if this_name contains "." then | |
set this_name to ¬ | |
(the reverse of every character of this_name) as string | |
set x to the offset of "." in this_name | |
set this_name to (text (x + 1) thru -1 of this_name) | |
set this_name to (the reverse of every character of this_name) as string | |
end if | |
return this_name | |
end removeFilenameExtension | |
-- Returns the number of hours relative to GMT. California is -7 or -8. | |
on timeZone() | |
return (time to GMT) / hours -- hours is 3600, a built-in constant | |
end timeZone | |
-- Returns 'missing value' if no string is equal to targetString | |
-- Case-sensitive | |
on indexOfStringInList(targetString, aList) | |
set i to 1 | |
repeat with aItem in aList | |
considering case | |
set compareResult to (aItem = targetString) | |
end considering | |
if compareResult is true then | |
return i | |
end if | |
set i to i + 1 | |
end repeat | |
return missing value | |
end indexOfStringInList | |
(* | |
# Converts the specified object - which may be of any type - into a string representation for logging/debugging. | |
# Tries hard to find a readable representation - sadly, simple conversion with `as text` mostly doesn't work with non-primitive types. | |
# An attempt is made to list the properties of non-primitive types (does not always work), and the result is prefixed with the type (class) name | |
# and, if present, the object's name and ID. | |
# EXAMPLE | |
# toString(path to desktop) # -> "[alias] Macintosh HD:Users:mklement:Desktop:" | |
# To test this subroutine and see the various representations, use the following: | |
# repeat with elem in {42, 3.14, "two", true, (current date), {"one", "two", "three"}, {one:1, two:"deux", three:false}, missing value, me, path to desktop, front window of application (path to frontmost application as text)} | |
# log my toString(contents of elem) | |
# end repeat | |
Source: mklement0, in http://stackoverflow.com/questions/13653358/how-to-log-objects-to-a-console-with-applescript | |
*) | |
on toString(anyObj) | |
local i, txt, errMsg, orgTids, oName, oId, prefix | |
set txt to "" | |
repeat with i from 1 to 2 | |
try | |
if i is 1 then | |
if class of anyObj is list then | |
set {orgTids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {", "}} | |
set txt to ("{" & anyObj as string) & "}" | |
set AppleScript's text item delimiters to orgTids # ' | |
else | |
set txt to anyObj as string | |
end if | |
else | |
set txt to properties of anyObj as string | |
end if | |
on error errMsg | |
# Trick for records and record-*like* objects: | |
# We exploit the fact that the error message contains the desired string representation of the record, so we extract it from there. This (still) works as of AS 2.3 (OS X 10.9). | |
try | |
set txt to do shell script "egrep -o '\\{.*\\}' <<< " & quoted form of errMsg | |
end try | |
end try | |
if txt is not "" then exit repeat | |
end repeat | |
set prefix to "" | |
if class of anyObj is not in {text, integer, real, boolean, date, list, record} and anyObj is not missing value then | |
set prefix to "[" & class of anyObj | |
set oName to "" | |
set oId to "" | |
try | |
set oName to name of anyObj | |
if oName is not missing value then set prefix to prefix & " name=\"" & oName & "\"" | |
end try | |
try | |
set oId to id of anyObj | |
if oId is not missing value then set prefix to prefix & " id=" & oId | |
end try | |
set prefix to prefix & "] " | |
end if | |
return prefix & txt | |
end toString | |
on getFilenameExtension(aPath) | |
if aPath contains "." then | |
set aPath to ¬ | |
(the reverse of every character of aPath) as string | |
set x to the offset of "." in aPath | |
set aPath to (text 1 thru (x - 1) of aPath) | |
set aPath to (the reverse of every character of aPath) as string | |
end if | |
return aPath | |
end getFilenameExtension | |
on tryShellScript(cmd) | |
set answer to missing value | |
try | |
set answer to do shell script cmd | |
end try | |
return answer | |
end tryShellScript | |
on tryShellScriptAndVerifySilentResult(cmd) | |
set errNum to missing value | |
set errMsg to missing value | |
set cmdResult to missing value | |
try | |
set cmdResult to do shell script cmd | |
on error errMsg number errNum | |
end try | |
set didFail to false | |
if (cmdResult is not missing value) then | |
if (length of cmdResult is greater than 0) then | |
set didFail to true | |
end if | |
end if | |
if errMsg is not missing value then | |
set didFail to true | |
end if | |
if errNum is not missing value then | |
set didFail to true | |
end if | |
if didFail is true then | |
set aResult to {cmdResult:cmdResult, errMsg:errMsg, errNum:errNum} | |
else | |
set aResult to missing value | |
end if | |
return aResult | |
end tryShellScriptAndVerifySilentResult | |
(* Given a bash filesystem glob, for example "/Users/jk/Desktop/*.txt", searches the directory and returns a list of filenames which match the glob, sorted from last modified to first modified, and truncated so that the total bytes of the files in the list does not exceed a given limit. This is useful if you want to gather files from a user, but limit the size of the result. Limitation: Consecutive spaces in filenames will be coalesced into a single space in the returned result. (Friends don't give friends filenames with consecutive spaces in them!) *) | |
on latestFilenames(glob, limit) | |
set byteCount to 0 | |
set cmd to "/bin/ls -ltww " & glob | |
set lsResult to missing value | |
try | |
set lsResults to do shell script cmd | |
set fileStrings to split(lsResults, return) | |
set results to {} | |
repeat with fileString in fileStrings | |
set fileString to coalesceTabsAndSpaces(fileString) | |
set fields to split(fileString, " ") | |
set fileSize to item 5 of fields | |
set byteCount to (byteCount + fileSize) | |
if byteCount is less than limit then | |
set filePath to last item of fields | |
set pathComps to split(filePath, "/") | |
set filename to last item of pathComps | |
set end of results to filename | |
end if | |
end repeat | |
on error | |
set results to missing value | |
end try | |
return results | |
end latestFilenames | |
(* Collapses all runs of consecutive tab characters, consecutive space characters, or consecutive combinations of tab characters and space characters each into a single space character, and returns the result *) | |
on coalesceTabsAndSpaces(aString) | |
if aString is missing value then | |
return aString | |
end if | |
set oldLength to count of aString | |
set newLength to 0 | |
repeat while oldLength is not equal to newLength | |
set oldLength to count of aString | |
set aString to searchAndReplace(aString, " ", " ") | |
set aString to searchAndReplace(aString, " ", " ") | |
set newLength to count of aString | |
end repeat | |
return aString | |
end coalesceTabsAndSpaces | |
(* Copies a file using /bin/cp, returns true if successful, otherwise false *) | |
on copyPath(src, dst) | |
set ok to true | |
try | |
set cmd to "/bin/cp -Rp \"" & src & "\" \"" & dst & "\"" | |
do shell script cmd | |
on error | |
set ok to false | |
end try | |
return ok | |
end copyPath | |
-------- Reuseable Handlers for ScriptView -------- | |
on startScriptView(pathToMe) | |
set scriptViewPath to quoted form of (pathToMe & "Contents/Resources/ScriptView.app") | |
set cmd to "open " & scriptViewPath | |
set ls to do shell script cmd | |
-- You probably want your script to be the active application, and ScriptView to be directly under it. The following two lines do that | |
tell application id "com.sheepsystems.ScriptView" to activate | |
tell me to activate | |
end startScriptView | |
on appendScriptView(aLine) | |
tell application id "com.sheepsystems.ScriptView" to add line aLine | |
end appendScriptView | |
on endScriptViewAsking(doAsk) | |
if (isAppNameRunning("ScriptView")) then | |
if (doAsk) then | |
tell me to activate | |
display dialog "Click 'OK' to close the Script Log window." buttons {"OK"} default button "OK" | |
end if | |
-- In case user has already closed the ScriptView window, which will cause it to quit, we 'try'… | |
try | |
tell application id "com.sheepsystems.ScriptView" to quit | |
end try | |
end if | |
end endScriptViewAsking | |
on isAppNameRunning(targetAppName) | |
set targetAppAlias to missing value | |
tell application "System Events" | |
set runningAppAliases to application file of every application process | |
repeat with appAlias in runningAppAliases | |
try | |
if appAlias is not missing value then | |
if name of appAlias is (targetAppName & ".app") then | |
return true | |
end if | |
end if | |
end try | |
end repeat | |
end tell | |
return false | |
end isAppNameRunning | |
-------- end Reuseable Handlers for ScriptView ---- | |
(* Note 20131005. On 20131005, I found that, all of a sudden, the statement: | |
set pathToMe to ((POSIX path of (path to me)) as string) | |
would raise an error when it was inside of a function (handler). So now, I pass pathToMe to functions (handlers) that need it as a parameter. | |
*) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment