I wrote an app in AppleScript called ‘AutoCopy’ and here is the code.
AutoCopy is a simple app that saves a copy of any item in the Finder, as often as you choose.
Its purpose is to auto-save versions while you’re making changes to a file or several files inside a folder.
How to use: the fastest way is to pick the item you want to autocopy (it can be a single item or folder containing many items) and drag it onto the AutoCopy icon. You’re asked how often (in minutes) you want to save a copy.
Enter a number, and that’s it.
Open the below code in AppleScript Editor and save it as application:
(*
Script written in AppleScript Editor vers. 2.6.1, using AppleScript version 2.3.2 in OS X version 10.9.5.
Author: Steve Thompson
*)
--This routine runs when the app is launched by dragging an item onto its icon.
on open draggedItem
tell application "Finder"
activate
set chosenItem to draggedItem as alias
end tell
mainRoutine(chosenItem)
end open
-- This routine runs if you launch by double-clicking the app.
on run
tell application "Finder"
activate
set theResult to display dialog "Want to auto-copy a File or a Folder ?" buttons ¬
{"Cancel", "Folder", "File"} default button 3
end tell
set chosenItem to chooseItem(theResult)
if chosenItem is false then return --program quits.
mainRoutine(chosenItem)
end run
--Subroutines:
on mainRoutine(chosenItem)
tell application "Finder"
repeat while kind of chosenItem is "Volume"
-- will keep repeating until they choose "Cancel" or anything that's not a disk.
set theResult to display dialog ¬
"Don't choose a disk! You can choose a File or a Folder." buttons ¬
{"Cancel", "Folder", "File"} default button 3
set chosenItem to my chooseItem(theResult)
if chosenItem is false then return --program quits.
end repeat
set {chosenItemName, chosenItemLocation} to {(name of chosenItem), (container of chosenItem)}
--Ask user how often to make a copy:
set {copyFrequency, waitTime} to my requestedcopyFrequency()
end tell -- Because the next line of code must run outside of Finder.
-- Begin repeat loop of this task: Make new duplicate of chosenItem, name it after current date and time
--and put it in folder "versions".
frequencyLoop(chosenItem, chosenItemName, chosenItemLocation, copyFrequency, waitTime)
end mainRoutine
on chooseItem(theResult)
if button returned of theResult is "Cancel" then return false
tell application "Finder"
if button returned of theResult is "Folder" then
set chosenItem to choose folder with prompt ¬
"Choose Folder to AutoCopy" default location desktop as alias
else -- if button returned is "File"
set chosenItem to choose file with prompt "Choose File to AutoCopy" default location ¬
desktop as alias
end if
end tell
return chosenItem
end chooseItem
on requestedcopyFrequency()
set copyFrequency to display dialog "Enter every number of minutes you want to " & return & ¬
"save a copy:" default answer "1" with title "AutoCopy"
set waitTime to display dialog ¬
"AutoCopy will quit automatically if the item hasn't been " & return & ¬
"modified after a certain number of minutes. Enter the " & return & ¬
"number of minutes AutoCopy should wait before quitting:" default answer "15" with title "AutoCopy"
return {(text returned of copyFrequency as integer), (text returned of waitTime as integer)}
end requestedcopyFrequency
on frequencyLoop(chosenItem, chosenItemName, chosenItemLocation, copyFrequency, waitTime)
set x to 0 --x keeps track of number of minutes it's been since a copy of item was made.
repeat
tell application "Finder"
--Make sure folder "versions" exists in same folder as specified item.
if not (exists folder (chosenItemName & "__versions") of chosenItemLocation) then
set AutoCopyFolder to (make new folder at chosenItemLocation with properties ¬
{name:chosenItemName & "__versions"})
else
set AutoCopyFolder to folder (chosenItemName & "__versions") of chosenItemLocation
end if
set AutoCopyFolder to (AutoCopyFolder as alias)
my renameLoop(AutoCopyFolder as string, chosenItem)
try
set lastModified to modification date of chosenItem
set theDateTime to my makeDateTime(lastModified)
set newName to (theDateTime & "__" & chosenItemName as string)
set AutoCopyFolder to (AutoCopyFolder as string)
--Only make a new copy if there's not already one with an identical name in AutoCopyFolder.
if not (exists item (AutoCopyFolder & newName)) then
--First check if item with same name as chosenItem already exists in AutoCopyFolder:
set duplicateItem to (AutoCopyFolder & chosenItemName) as string
if (exists item duplicateItem) then
set duplicateItem to (duplicateItem as alias)
set lastModified to modification date of duplicateItem
--Prepare to change its name to its modification date:
set theDateTime to my makeDateTime(lastModified)
--But if an item with exact same modification date exists, get rid of duplicate:
if exists item (AutoCopyFolder & theDateTime & "__" & chosenItemName) then
delete item duplicateItem
else --change duplicate's name to modification date:
set name of duplicateItem to (theDateTime & "__" & chosenItemName)
end if
end if
--Make a new copy, but only if item with same modification date doesn't exist:
if not (exists item (AutoCopyFolder & newName)) then
copy item chosenItem to folder (AutoCopyFolder as string)
set duplicateItem to result as alias
set name of duplicateItem to newName
set x to 0
end if
end if
on error
my itemDoesntExistMessage()
return --program quits.
end try
end tell
-- pause script for ((number of minutes user specified) * 60) seconds:
repeat copyFrequency times
delay 60
set x to (x + 1) --x gets incremented plus-one for every minute.
if x ≥ waitTime then return --program quits.
end repeat
end repeat
end frequencyLoop
--Make function that will run at beginning of every loop iteration in frequencyLoop.
--It checks modification dates of every item in a folder, and renames each
--item after the modification date. If multiple items share a mod date (and are same filetype), only one can remain.
on renameLoop(folderStringPath, chosenItem)
tell application "System Events"
--get every visible item in the folder:
set theItems to every item of item folderStringPath whose visible is true
set modDatesAndExtensions to {}
repeat with i in theItems
set end of modDatesAndExtensions to ¬
{modified:modification date of i, name extension:¬
name extension of i}
end repeat
--Find out if any items match, both in mod date and extension, and if so, keep only one:
set removedItems to item 2 of my removeDuplicates(modDatesAndExtensions)
if (count of removedItems) > 0 then
repeat with i in removedItems
delete (item i of theItems)
end repeat
end if
set theItems to every item of item folderStringPath whose visible is true
--Make sure the names of the items all begin with mod dates:
repeat with i in theItems
set theDateTime to my makeDateTime(modification date of i)
set formattedNm to (theDateTime & "__" & chosenItem's name as string)
if ((i's name does not start with theDateTime) or ("copy" is in i's name)) and ¬
(i's name extension is chosenItem's name extension) then
set name of i to formattedNm
else if (i's name does not start with theDateTime) then
set name of i to (theDateTime & "__" & (i's name) as string)
end if
end repeat
end tell
--return modDatesAndExtensions
end renameLoop
--Returns list of two items: first is modified version of lst with duplicates removed.
--Second item is list of indexes of removed items.
on removeDuplicates(lst)
local lst, itemRef, res, itm
try
if lst's class is not list then error "not a list." number -1704
script k
property l : lst
property res : {}
property indx : {}
end script
repeat with i from 1 to count of k's l
set itm to (item i of k's l)
if k's res does not contain {itm} then
set k's res's end to itm
else
set k's indx's end to i
end if
end repeat
return {k's res, k's indx}
on error eMsg number eNum
return false
end try
end removeDuplicates
--This function returns a specially formatted date-time string, i.e, "140211-201412"
on makeDateTime(dateObject)
set shortString to short date string of dateObject
set theList to explode(shortString, "/")
if (count of (LI(1, theList))) = 1 then
setLI(1, theList, ("0" & (item 1 of theList)))
end if
if (count of (item 2 of theList)) = 1 then
setLI(2, theList, ("0" & (item 2 of theList)))
end if
set theDate to (item 3 of theList) & (item 1 of theList) & (item 2 of theList)
set AppleScript's text item delimiters to ""
set theTime to time of dateObject
set theHour to (round (theTime / 3600) rounding down) as string
set hourInt to theHour as integer
set hourSecs to (hourInt * 3600)
set minSecs to ((theTime - hourSecs) / 60)
set theMin to (round minSecs rounding down) as string
set minInt to theMin as integer
set minSecs to (minInt * 60)
set theSec to (theTime - hourSecs - minSecs) as string
if ((count of theHour) = 1) then set theHour to ("0" & theHour)
if ((count of theMin) = 1) then set theMin to ("0" & theMin)
if ((count of theSec) = 1) then set theSec to ("0" & theSec)
return (theDate & "-" & theHour & theMin & theSec) as string
end makeDateTime
on itemDoesntExistMessage()
display dialog ¬
"The item no longer exists. Either its name changed or it was deleted. I'm quitting. Bye." with title ¬
"AutoCopy" buttons {"OK"} default button 1 giving up after 10
end itemDoesntExistMessage
--This function is just for creating a short-hand way of accessing a list item.
--ItemNum can be a single integer, or a list of two integers for accessing a range of items:
on LI(itemNum, theList)
if class of itemNum is integer then
return (item itemNum of theList)
else if class of itemNum is list then
return (items (item 1 of itemNum as integer) thru ¬
(item 2 of itemNum as integer) of theList)
end if
end LI
--This function is for assigning a value to a list item:
on setLI(itemNum, theList, theValue)
set item itemNum of theList to theValue
end setLI
-- This function separates pieces of a string into list items, using theDelimit
-- as the separator:
on explode(theString, theDelimit)
set origDelimit to AppleScript's text item delimiters
set AppleScript's text item delimiters to theDelimit
set theResult to every text item of theString
set AppleScript's text item delimiters to origDelimit
return theResult
end explode
--This function re-assembles a list of strings into a single string,
--using theDelimit as glue to reconnect each string.
on implode(textList, theDelimit)
set origDelimit to AppleScript's text item delimiters
set AppleScript's text item delimiters to theDelimit
set theString to (textList as string)
set AppleScript's text item delimiters to origDelimit
return theString
end implode