Hello.
INTRODUCTION
This is a Script Library Server that unloads ScriptLibraries after a given amount of time in seconds,
and which dies within the same number of seconds after the last library was unloaded from it.
This library server (ScriptLibraryServer) is intended to work in conjunction with both the LibraryLoader and LibrarServer, and therefore all paths to libraries should be hard coded. (At least at this stage).
It does not currently support AppleMods or ScriptFactory’s LibraryLoader mechanisms, but will eventually do so soon, either directly or by providing some semi-compatible mechanisms to make the loading work.
It will be fully supported by both the LibraryLister and the LibraryLoader.
In this post you will find The ScriptLibraryServer skeleton which includes two small libraries for demonstration purposes. The LoaderMaker script to ease installment (coming soon).
About the example libraries:
A little (but very useful) simple library to play with, to see that it actually work. And a more complex library that illustrates usage of intrinsic properties, also just to play with. (It does nothing more useful than showing that get(), set() and is() works through the ScriptLibraryLoader).
It should be possible to use this ScriptLibraryServer in a network context where the Script Libraries are stored in some central locations, -if the right precautions are taken regarding Network Access and login status.
OVERVIEW
You should really know how to write a library which is intended to be used from a Script Server.
Luckily for us Bill Cheeseman has written an excellent article on the subject which can be found here.
To use a script library from a script library server brings some considerations into account, which are needed to be taken height for. I recommend that you play a little with the examples in that article, or make up some of your own while you study it, if your aren’t totally sure of how calling code through a Script Library Server differs from a loading and calling through a regular Script Library.
PRELIMINARY
The ScriptLibraryServer presented is of course only a skeleton, it should contains tons of hard coded code, if you use it properly. I have provided a script that generates the necessary hardcoded stuff “LoaderMaker”,that makes it easy for you to just paste generated template code right into the Script Library Server’s main script after having answered a few easy questions. You will full understand these questions by reading the section “About Script Libraries” if not, please refer to Bill Cheesemans article referenced above. The template code generated consists of a handler, a script object and a property. -After having pasted in the code you should be good to go.
EXAMPLE USAGE OF THE SCRIPT LIBRARY SERVER RUNTIME
General usage
Say I have entered code for having the Script Server provide a textLib for me, which resides at a given path. In order to access the functionality of that script library I would use code like this:
--> simple example:
tell application "ScriptLirarServer"
set lf to my textLib()'s LF
end tell
Observe: I access the library through a Handler in my client code.
→ more complex example:
tell application "ScriptLibraryServer"
tell its objLib()
-- Notice: we address the specific script object in the Script server by "telling it"
-- in order for the code to work
if its isFavcolor() is true then
set myColor to its getFavColor()
tell me
activate
display dialog "FavColor " & myColor
end tell
else
tell me
activate
display dialog "No color defined yet"
end tell
end if
its setFavColor("Blue")
set its txtColor to "Blue"
-- set objLib()'s txtColor to "ORANGE"
log its txtColor
if its isFavcolor() then
set myColor to its getFavColor()
display dialog "FavColor " & myColor
else
tell me
activate
display dialog "No color defined yet"
end tell
end if
log its txtColor
end tell
end tell
We have just tested for, set a property, and gotten a result when property was set.
IMPLEMENTATION DETAILS YOU REALLY NEED TO BE AWARE OF
Observe that you in the ScriptLibrarServer will use a handler to access the Script Library in speak.
After calling the handler, you should be able to access what is within that Script Library as you would with any other Script Library created to serve as a script object in a library server.
OTHER IMPLEMENATION DETAILS
You should easily be able to figure out the naming conventions by looking at the supplied Demo/template for the script library server, its internal idle handler, together with the LoaderMaker script. The code is really simple.
USAGE FOR DEVELOPMENT
The ServerSkeleton has a switch for turning a debug_load mode on. When that switch is encountered for a script object that holds the date of a moduleWe then support debugging of Script Libraries by checking the librarys timestamp on disk, and reloading it before using it. -This may lead to errant situations if the libraries are complex, and you have other applet relying on the script. I recommend that you make a second Script Library Server for development and testing of Script Libraries were you use this feature, with other Script Libraries than the production versions of your code. You turn on debug_load mode for a specific library by setting the debug_load mode property to true.
USAGE FOR DEPLOYMENT
Never use the name “ScriptLibraryServer”: that’s a no no. Use some unique name, also use a version number in its name, (This is something which you agree to in the agreements section of this code in order to use said code.)
Guidance
It is advisible to use unique filenames for the versions of the libraries you are using in order for your users/customers to easily detect what pieces are amiss, than trying to figure out why that applet/script doesn’t work as intended, which may lead to hours and hours of unecessary debugging, -that is if your usere are daring. Otherwise this may result in that your users defaults to using your software, or that you will have to spend time with your users, trying to figure out why it doesn’t work.
Your ScriptServer should also come with a version number, so that you won’t break anything that relied on an older version. You should however not “flatten” the contents of the ScriptLibraryServer but supply the necessary modules/units, and provided some kind of instructions for, or an installer to install them.
Best Practice:
Installation of the necessary modules within the Contents/Resources/Scripts folder may be seen as a best practice and referencing them from there, as then you will have the best control over your product when it is shipped to a users machine.
You can also have your Script Library Server ship the necessary osax’s to make your code work, they should be installed in the “Contents/Resources/Scripting Additions” folder. (Remark the space in Scripting Additions). Be sure to read the license agreement of the osax’s you ship with it carefully!
SOME HELPFUL TOOLS FOR DEPLOYMENT
Luther Fuller has made an excellent tool for creating application bundles which may be useful for this purpose. It can be found here.
Should you wish to make your Application faceless during a later stage, then DockDodger is a great little app that can be found here
(Maybe it it is still better to use ScriptBundler to build a final solution when your are totally sure that it works.
ABOUT SCRIPT LIBRARIES IN GENERAL
We can basically split script librarys into two groups; those that just consists of a group of independent handlers I will refer to this type as “simple libraries” and those that consists of internal script objects or handlers which are used internally within the script library these I denote as complex.
For the type two script library there is special considerations:
The scripts that are loaded in such library servers should contain a script object with all its properties within that script object and nothing at the top level. Those issues is thoroughly treated in Ben Walidies article which is referenced to above.
Aside: I have no ida as we speak wether modules loaded with either the AppleMods loader system nor Tetsuro Kurita’s module loader will work directly from within the ScriptLibraryServer due to the extra condisederations which is needed to be taken in order to make a Script Library Server work. -I haven’t had the time to test it yet.
ABOUT LoaderMaker
This script creates the template for loading your unit of code.
You will have to answer three or four different easy questions! in order for the LoaderMaker script create a template for you to paste into your Script Library Server Script without any further rearrangement!
- It will ask for the Script Library you want to make a template for. It will then use the given filename for creating a hardcoded reference to that file at this stage. If it is a complex library, then it will also read out the script objects name in question in order to create a correct template for it. -Or stall if said script object isn’t there.
¢ It will first of all ask if the library only contains handlers, or if it is more complex than so.
It isn’t regarded as a complex one if not all of its contents resides within a script object within that script, so the LoaderMaker will stall if the script library isn’t so.
¢ It will then ask you for the library file through a choose file dialog.
¢ If the library is a complex one it will ask you for the name of the script object to extract from the library file.
¢ In every case it will then ask for the object name to use in the ScriptLibraryServer
It will then ask if the loaded Script Library is in a development state, set the correct value for the load_debug property within the generated script object (a boolean ).
Enjoy!
ACCOMPANYING CODE:
USAGE EXAMPLE (snippet above)
Template/Demo of ScriptLibraryServer
LoaderMaker Script Not implemented -Yet!
Example Simple Library
Example Complex Library
-- The Idea and implementation and any faults is totally mine. © McUsr 2010 and put in the Public Domain.
-- The usually guarrantees about nothing what so ever applies, use it at your own risk.
-- Read the documentation.
-- You are not allowed to post this code elsewhere, but may of course refer to the post at macscripter.net.
” macscripter.net/viewtopic.php?pid=131684#p131684
(*
TERMS OF USE.
This applies only to posting code, as long as you don't post it, you are welcome to do
whatever you want to do with it without any further permission.
Except for the following: Selling the code as is, or removing copyright statmentents and the embedded link in the code (without the http:// part) from the code.
SPECIAL TERMS REGARDING SCRIPT LIBRARY SERVER.
Under no circumstance should you ship a Script Library Server with the name Script Library Server You must agree to finding a unique name for any
script library servers you ship with script in order to use the code. Preferably with a version number. And for the case that you hadn't thought of it:
It would be very wise to ship your libraries from the Contents/Resources/Scripts folder in the bundle of the ScriptLibraryServer of yours.
You must also state what you eventually have done with the original source. This obviously doesn't matter if you distribure AppleScript as read only. I do not require you to embed any properties helding copyright notice for the code.
Credit for having contributed to your product would in all cases be nice!
If you use this code as part of script of yours you are of course welcome to post that code with my code in it here at macscripter.net. If you then wish to post your code elsewhere after having uploaded it to MacScripter.net please email me and ask for permission.
The ideal situation is however that you then refer to your code by a link to MacScripter.net
The sole reason for this, is that it is so much better for all of us to have a centralized codebase which are updated, than having to roam the net to find any usable snippets. Which some of us probabaly originated in the first hand.
I'm picky about this. If I find you to have published parts of my code on any other site without previous permission, I'll do what I can to have the site remove the code, ban you, and sue you under the jurisdiction of AGDER LAGMANNSRETT of Norway. Those are the terms you silently agree too by using this code.
The above paragraphs are also valid if you violate any of my terms.
If you use this or have advantage of this code in a professional setting, where professional setting means that you use this code to earn money by keeping yourself more productive. Or if you as an employee share the resulting script with other coworkers, enhancing the productivity of your company, then a modest donation to MacScripter.net would be appreciated.
*)
-- © McUsr 2010 and put in Public Domain see: macscripter.net/edit.php?id=131684#p131684 for reference and terms of use.
script libInit
on init()
local fileModDate
if contents of my _scriptLIb is missing value then
-- we have taken the tour with the garbage collector and have to reinitialize
if my _libtype is 1 then
LoadType1Library(my _scriptLIb, my _hfsFileName, my _libName)
else
LoadType2Library(my _scriptLIb, my _hfsFileName, my _libName, my _libLoader)
end if
end if
if my _reload_on_update then
try
tell application "Finder" to set fileModDate to (modification date of (my _hfsFileName as alias))
on error e number n
tell me
activate
display alert "ScriptLibrary Server _init : " & my _libName & " File probably missing " & e & " : " & n
end tell
end try
set end of my unitsExpiryList to {my _thisScript, my _scriptLIb, (current date), my _hfsFileName, fileModDate, my _libName}
else
set end of my unitsExpiryList to {my _thisScript, my _scriptLIb, (current date)}
end if
set my _index to (count my unitsExpiryList)
end init
end script
property unitsExpiryList : {}
-- every item of unitsExpiryList holds the following: { ref to the units Script object, ref to the units property (script library), last access time )
(* ===================== SERVER CODE ABOVE ====================== *)
-- Example code below, which illustrates how the contents is intended to be structured.
(* SCRIPT LIBRARY EXAMPLE # 1 ======================================================================*)
property _1_textLib : (load script alias "Macintosh HD:Users:McUsr:Library:Scripts:Modules:TextLib.scpt") -- hardcode
-- EXAMPLE : SIMPLE LIBRARY WHICH ONLY CONTAIN INDIVIDUAL HANDLERS (TYPE 1)
script _textLib
-- EXAMPLE : SIMPLE LIBRARY WHICH ONLY CONTAIN INDIVIDUAL HANDLERS (TYPE 1)
property parent : libInit
property _index : 0
property _reload_on_update : true
property _hfsFileName : "Macintosh HD:Users:McUsr:Library:Scripts:Modules:TextLib.scpt"
property _libName : "text_lib"
property _scriptLIb : a reference to my _1_textLib
property _thisScript : a reference to my _textLib
property _libtype : 1
property _libLoader : ""
end script
on textLib()
if my _textLib's _index is 0 then
my _textLib's init()
else
if my _textLib's _reload_on_update then my _textLib's checkAndUpdateLibrary(a reference to my _textLib)
end if
return my _1_textLib
end textLib
(* SCRIPT LIBRARY EXAMPLE # 2 ====================================================================== *)
property _1_objLib : objLib of (load script alias "Macintosh HD:Users:McUsr:Library:Scripts:Modules:favColorLib2.scpt")
-- EXAMPLE : LIBRARY WHICH NEED TO GET CONTENTS OF A LIBRARY THROUGH AN OBJECT BECAUSE OF INTERNAL REFERENCES. (TYPE 2)
script _objLib
-- EXAMPLE : LIBRARY WHICH NEED TO GET CONTENTS OF A LIBRARY THROUGH AN OBJECT BECAUSE OF INTERNAL REFERENCES. (TYPE 2)
property parent : libInit
property _index : 0
property _reload_on_update : true
property _hfsFileName : "Macintosh HD:Users:McUsr:Library:Scripts:Modules:favColorLib2.scpt"
property _libName : "objlib"
property _scriptLIb : a reference to my _1_objLib
property _thisScript : a reference to my _objLib
property _libtype : 2 -- We must use an "object of" load script "the lib" in order to get our goodies type 2 in description
property _libLoader : "objLib of (load script alias \"Macintosh HD:Users:McUsr:Library:Scripts:Modules:favColorLib2.scpt\")"
end script
on objLib()
if my _objLib's _index is 0 then
my _objLib's init()
else
if my _objLib's _reload_on_update is true then my _objLib's checkAndUpdateLibrary(a reference to my _objLib)
end if
return my _1_objLib -- which your code will tell what to do.
end objLib
(* SCRIPT LIBRARY # and so on*)
(* ===================== SERVER CODE BELOW ====================== *)
on LoadType1Library(refObj, txtAHfsFilename, txtAScriptName)
try
txtAHfsFilename as alias
on error e number n
tell me
activate
display alert "Script " & txtAScriptName & "'s LoadType1Library() : Cant find file: " & txtAHfsFilename & " " & e & " : " & n
end tell
end try
try
set contents of refObj to (load script alias txtAHfsFilename) -- ## hardcode
on error e number n
tell me
activate
display alert "LoadType1Library " & txtAScriptName & ": Cant load Library, something is wrong: " & txtAHfsFilename & " " & e & " : " & n
end tell
end try
end LoadType1Library
on LoadType2Library(refObj, txtAHfsFilename, txtAScriptName, txtRunScriptStatement)
try
txtAHfsFilename as alias
on error e number n
tell me
activate
display alert "Script " & txtAScriptName & "'s LoadType2Library() : Cant find file: " & txtAHfsFilename & " " & e & " : " & n
end tell
end try
try
set contents of refObj to (run script txtRunScriptStatement) -- ## hardcode
on error e number n
tell me
activate
display alert "LoadType2Library " & txtAScriptName & ": Cant get Library from object via run, something is wrong: " & txtAHfsFilename & " " & e & " : " & n
end tell
end try
end LoadType2Library
on checkAndUpdateLibrary(refToLib) --Type 1 checkAnd updater, trenger 2 typer.
local libindex, libtype
set {libindex, libtype} to {_index of contents of refToLib, _libtype of contents of refToLib}
local fileModDate
try
tell application "Finder" to set fileModDate to (modification date of alias (contents of (item 4 of (item libindex of my unitsExpiryList))))
on error e number n
tell me
activate
display alert "ScriptLibrary Server checkAndUpdateType1Library : " & item 6 of (item libindex of my unitsExpiryList) & " File probably missing " & e & " : " & n
end tell
end try
if fileModDate is greater than contents of item 5 of (item libindex of my unitsExpiryList) then -- update contents.
if libtype is 1 then
my LoadType1Library(item 2 of (item libindex of my unitsExpiryList), contents of (item 4 of (item libindex of my unitsExpiryList)), ¬
item 6 of (item libindex of my unitsExpiryList))
-- item2 ref to glob prop helds script object:, item 4 -> filename., item 6 --> script library name
else -- type 2
LoadType2Library(item 2 of (item libindex of my unitsExpiryList), contents of (item 4 of (item libindex of my unitsExpiryList)), item 6 of (item libindex of my unitsExpiryList), _libLoader of contents of refToLib)
-- the new thing here contra above, is that we pass a string to be executed as a string with it.
end if
set item 5 of (item libindex of my unitsExpiryList) to fileModDate
end if
end checkAndUpdateLibrary
on idle
-- empties buffers on two hours expiry dies when nothing more to do
global started
local hadExpired
if started is true then
set started to false
return 900
end if
set hadExpired to false
set libCount to (count my unitsExpiryList)
try
repeat with i from 1 to libCount
if (current date) - (contents of item 3 of item i of my unitsExpiryList as date) is greater than 7200 then -- 2 hours
set (contents of item 1 of item i of my unitsExpiryList)'s _index to 0
-- so that the handler can see that it must reload the script object before returning result
set contents of item 2 of item i of my unitsExpiryList to missing value
-- effectively kills the script object in speak
set hadExpired to true
end if
end repeat
if hadExpired is true then set my unitsExpiryList to my unitsExpiryList's lists
on error e number n
tell me
activate
display alert "LibraryServers 's Idle handler : error " & e & " : " & n
end tell
end try
if hadExpired is false and libCount is 0 then
tell me to quit
end if
return 900 -- 15 minutes
end idle
on quit
continue quit
end quit
on run
global started
set started to true
(*
-- This is the calling convention which must be used.
set theName to textLib()'s LF
log theName
*)
end run
LoaderMaker Script
Past the code it generates directly into the server, no need for rearranging!
-- © McUsr 2010 and put in Public Domain see: macscripter.net/edit.php?id=131684#p131684 for reference and terms of use.
set choice to button returned of (display dialog "Will you make a simple or complex load statment for a ScriptLibraryServer (simple is with indepedent handlers, complex means with internal dependencies or properties)?" with title "LoaderMaker" buttons {"Cancel", "Simple", "Complex"} cancel button 1 default button 2)
set theLibaryFile to (choose file of type {"com.apple.applescript.script"} with prompt "Choose the Library you want to make a load statement for") as text
if choice is "Complex" then
repeat
set libName to text returned of (display dialog "Enter the name of the script object you will extract from the library file" default answer "" with title "LoaderMaker")
if libName is not "" then exit repeat
end repeat
end if
repeat
set ServerLibName to text returned of (display dialog "Enter the name the script object shall have in the Script Library Server " default answer "" with title "LoaderMaker")
if ServerLibName is not "" then exit repeat
end repeat
set reloadOnUpdate to button returned of (display dialog "Enter the initial status of \"_reload_on_update\"" with title "LoaderMaker" buttons {"Cancel", "true", "false"} cancel button 1 default button 2)
if choice is "Complex" then
mkType2Template(ServerLibName, libName, theLibaryFile, reloadOnUpdate)
else
mkType1Template(ServerLibName, theLibaryFile, reloadOnUpdate)
end if
on mkType1Template(T1, T2, T3)
-- T1 : scrObj of server, T2: script file T3 load_debug , T4 lib name for debug.
local theText
set theText to "(* SCRIPT LIBRARY T1 OF TYPE 1 ======================================================================*)
property _1_T1 : (load script alias \"T2\") -- hardcode
-- SIMPLE LIBRARY WHICH ONLY CONTAIN INDIVIDUAL HANDLERS (TYPE 1)
script _T1
-- SIMPLE LIBRARY WHICH ONLY CONTAIN INDIVIDUAL HANDLERS (TYPE 1)
property parent : libInit
property _index : 0
property _reload_on_update : T3
property _hfsFileName : \"T2\"
property _libName : \"T1\"
property _scriptLIb : a reference to my _1_T1
property _thisScript : a reference to my _T1
property _libtype : 1
property _libLoader : \"\"
end script
on T1()
if my _T1's _index is 0 then
my _T1's init()
else
if my _T1's _reload_on_update then my _T1's checkAndUpdateLibrary(a reference to my _T1)
end if
return my _1_T1
end textLib
"
set theText to swapText(theText, "T1", T1)
set theText to swapText(theText, "T2", T2)
set theText to swapText(theText, "T3", T3)
-- set theText to swapText(theText, "T4", t4)
set the clipboard to theText
tell me
activate
display alert "The text is ready to be pasted into the clipboard"
end tell
end mkType1Template
on mkType2Template(T1, T2, T3, t4)
-- T1 : scrObj of server, T2: scrObj of library, T3 filename, T5 reload_debug.
local theText
set theText to "(* SCRIPT LIBRARY T1 OF TYPE 2 ====================================================================== *)
property _1_T1 : T2 of (load script alias \"T3\")
-- COMPLEX LIBRARY WHICH NEED TO GET CONTENTS OF A LIBRARY THROUGH AN OBJECT BECAUSE OF INTERNAL REFERENCES. (TYPE 2)
script _T1
-- COMPLEX LIBRARY WHICH NEED TO GET CONTENTS OF A LIBRARY THROUGH AN OBJECT BECAUSE OF INTERNAL REFERENCES. (TYPE 2)
property parent : libInit
property _index : 0
property _reload_on_update : T4
property _hfsFileName : \"T3\"
property _libName : \"T1\"
property _scriptLIb : a reference to my _1_T1
property _thisScript : a reference to my _T1
property _libtype : 2 -- We must use an \"object of\" load script \"the lib\" in order to get our goodies type 2 in description
property _libLoader : \"T2 of (load script alias \\\"T3\\\")\"
end script
on T1()
if my _T1's _index is 0 then
my _T1's init()
else
if my _T1's _reload_on_update is true then my _T1's checkAndUpdateLibrary(a reference to my _T1)
end if
return my _1_T1 -- which your code will tell what to do.
end objLib"
set theText to swapText(theText, "T1", T1)
set theText to swapText(theText, "T2", T2)
set theText to swapText(theText, "T3", T3)
set theText to swapText(theText, "T4", t4)
set the clipboard to theText
tell me
activate
display alert "The text is ready to be pasted into the clipboard"
end tell
end mkType2Template
to swapText(theText, swapOut, swapIn)
(* This bit comes from Apple's own "Replace Text in Item Names" script
with some of the variables changed to make it a call-able handler *)
if the theText contains the swapOut then
-- replace target string using delimiters
set AppleScript's text item delimiters to the swapOut
set the text_item_list to every text item of theText
set AppleScript's text item delimiters to the swapIn
set the theText to the text_item_list as string
set AppleScript's text item delimiters to ""
end if
return theText
end swapText
Example Simple Library (But with great handlers!)
property LF : (run script "\"\\n\"") as Unicode text -- Nigel Garvey
to extractAllBetween(SearchText, startText, endText) ”Yvan Koenig
set tid to AppleScript's text item delimiters -- save them for later.
set AppleScript's text item delimiters to startText -- find the first one.
set liste to text items of SearchText
set AppleScript's text item delimiters to endText -- find the end one.
-- causes "copy text item 1" to only copy text before the end text.
set extracts to {}
repeat with subText in liste
if subText contains endText then
copy text item 1 of subText to end of extracts
end if
end repeat
set AppleScript's text item delimiters to tid -- back to original values.
return extracts
end extractAllBetween
to swapText(theText, swapOut, swapIn)
(* This bit comes from Apple's own "Replace Text in Item Names" script
with some of the variables changed to make it a call-able handler *)
if the theText contains the swapOut then
-- replace target string using delimiters
set AppleScript's text item delimiters to the swapOut
set the text_item_list to every text item of theText
set AppleScript's text item delimiters to the swapIn
set the theText to the text_item_list as string
set AppleScript's text item delimiters to ""
end if
return theText
end swapText
on indexOfItem(theItem, itemsList) -- credit to Emmanuel Levy but I modified it with the considering case statements
local rs
considering case
set text item delimiters to return
set itemsList to return & itemsList & return
set text item delimiters to {""}
try
set rs to -1 + (count (paragraphs of (text 1 thru (offset of (return & theItem & return) in itemsList) of itemsList)))
on error
return 0
end try
rs
end considering
end indexOfItem
Example Complex Library ( it stores and retrieves properties )
script objLib
property txtColor : missing value
on isFavcolor()
log "FAVCOLOR LIB isFavcolor : my new color is : " & (txtColor of my objLib)
if txtColor of objLib is missing value or txtColor of objLib is "" then
return false
else
return true
end if
end isFavcolor
on setFavColor(theColor)
set txtColor of objLib to theColor
log "FAVCOLOR LIB setFavColor() : my new color is : " & (txtColor of objLib)
end setFavColor
on getFavColor()
log "FAVCOLOR LIB getFavColor() : my new color is : " & (txtColor of objLib)
return (txtColor of objLib)
end getFavColor
end script