-- $Date: 2001-06-18 20:15:40 -0700 (Mon, 18 Jun 2001) $ -- $Revision: 973 $ -- Copyright 2000-2001, Bill Castrogiovanni -- This object manages the downloading/import of remote media -- One shortcoming in this version of the manager is that bitmap data is automatically -- imported into the cast library, but downloaded text is stored in a variable. Specifically, -- when bitmaps are imported, their cast member numbers are stored in the pDownloadImportMap -- property list. Instead of importing downloaded text, though, it is pushed as a string into -- pDownloadImportMap. Though both values are stored in the same list, I've written 2 seperate -- accessors to avoid confusion. These accessors are called "getImportedMember()" and "getDownloadedText()". -- If I ever get around to improving this class, I may weigh the benefits of completely splitting -- text downloaded logic from binary download logic versus combining both into a single interface. -- Here, then, are this object's most useful public handlers: -- queueDownload() -- syntax: downloadManagerInstance.queueDownload(aPath, anObjectID): -- aPath is a URL string -- anObjectID is the address of the object that is requesting the URL -- queueDownload MUST BE CALLED BY AN OBJECT WHICH RESPONDS TO setLoadStatus -- The object that calls queue download will automatically be notified of changes -- in the URL's download status. (see below) -- NOTE TO SELF: I may wish to make the anObjectID argument optional in future versions -- of this manager. Using anObjectID will make download status notification automatic. -- Users that don't pass-in anObjectID will have to poll for download status manually. -- getImportedMember() -- syntax: downloadManagerInstance.getImportedMember(aPath) -- aPath is the URL string the caller used when calling queueDownload -- This handler returns the cast member number of the requested binary file -- If the file at aPath hasn't been successfully downloaded, this handler returns 0 -- getDownloadedText() -- syntax: downloadManagerInstance.getDownloadedText(aPath) -- aPath is the URL string the caller used when calling queueDownload -- This handler returns a string containing the text stored at aPath -- If the file at aPath hasn't been successfully downloaded, this handler returns "" -- Objects that register with the download manager (or their ancestors) must have a "setLoadStatus" -- handler that responds to the following symbols: -- #queued - returned to the registrant when the object is successfully added to the download queue -- #loading - returned to the registrant when this object begins downloading the requested media -- #done - returned to the registrant if this object successfully downloads the requested media -- #error - returned to the registrant if this object has encounters a download error -- THIS IS VERY IMPORTANT - these objects -- make cast managment calls through a couple -- of global objects: global gMediaManager, gErrorManager, gStatusBarManager -- These objects need to be properly initialized before -- downloading can properly occur. property pDownloadQueue, pDownloadImportMap, pNetIDList, pCurrentDownloadList -- pDownloadQueue is a property list that stores paths to download items and objects ids to notify -- when the downloads are complete -- The format is as follows: -- [PathToDownloadItem:[LinearListOfObjects]] -- pDownloadImportMap is a property list that maps import paths to cast members numbers. -- After a file is successfully downloaded from the internet, it is added to this list. -- The format is as follows: -- [PathToDownloadItem: ImportedCastMemberNumber] -- pNetIDList is a linear list of NetIDs for currently downloaded media. The index for -- each NetID matches the index for the corresponding media info contained in pCurrentDownloadList. -- The size of this list will never exceed pMaximumThreads. The format is as follows: -- [NetID(1), NetID(2), ... NetID(pMaximumThreads)] -- pCurrentDownloadList, similar to pDownloadQueue) is a property list that maps paths to download -- items to a linear list of object ids. This list correlates with pNetIDList - the first NetID in -- that list matches the first path property in this list. The size of this list will never exceed -- pMaximumThreads. The format is as follows: -- [PathToDownloadItem:[LinearListOfObjects]] property pTotalDownloadsQueued, pCurrentDownloadNumber -- These properties are only used to generate status messages for users -- pTotalDownloadsQueued stores the total number of files scheduled for download in a -- user-defined "batch" -- pCurrentDownloadNumber is the number of files already downloaded -- these properties are reset when the user calls downloadManager.resetCounter() property pManagerActiveFlag, pMaximumThreads -- pManagerActiveFlag is a boolean value that controls the starting and stopping of downloads -- pMaximumThreads is an integer that defines the maximum number of simultaneous downloads on new me me.init() return me end -- initialize this object's properties on init me pDownloadQueue = [:] pDownloadImportMap = [:] pNetIDList = [] pCurrentDownloadList = [:] pManagerActiveFlag = 1 -- the download object will automatically start processing requests pMaximumThreads = 3 -- up to 3 pieces of media can be downloaded at a time me.resetCounter() -- zeros the download counter -- TEST CODE -- This code should be moved to a permenant home, once I build-in projector (instead of shockwave) logic if (the environment.runmode = "Author") or (the environment.runmode = "Projector") then cacheDocVerify #always end if -- END TEST CODE end -- resets the counter. -- The caller is responsible for determining when the counter should be reset on resetCounter me pTotalDownloadsQueued = 0 pCurrentDownloadNumber = 0 end -- destroy this object on destroy me me.deleteAllDownloads() me = VOID return me end -- allows objects to register items for download on queueDownload me, aPath, anObjectID if (pDownloadImportMap.getAProp(aPath) <> VOID) then -- the media has already been downloaded, notify the object me.notifyRegistrants(anObjectID, #done) else if (pCurrentDownloadList.getAProp(aPath) <> VOID) then -- the media is currently downloading thisRegistrantList = pCurrentDownloadList.getAProp(aPath) -- add the object to this path's registrant list thisRegistrantList.add(anObjectID) me.notifyRegistrants(anObjectID, #loading) -- notify the object that the media is currently downloading else if (pDownloadQueue.getAProp(aPath) <> VOID) then -- another object has already requested this media thisRegistrantList = pDownloadQueue.getAProp(aPath) -- add the object to this path's registrant list thisRegistrantList.add(anObjectID) me.notifyRegistrants(anObjectID, #queued) -- notify the object that the download is queued else -- this is the first object to request this media pDownloadQueue.setAProp(aPath, [anObjectID]) -- queue the download me.notifyRegistrants(anObjectID, #queued) -- notify the object that the download is queued pTotalDownloadsQueued = pTotalDownloadsQueued + 1 -- increments the total counter (only affects the status message) end if end -- returns the cast member number associated with successfully downloaded -- and imported media. Returns 0 if the media hasn't been downloaded yet. -- If the the value associated with aPath is a string, the caller has requested -- a text document (not an imported cast memeber), so this handler returns 0 on getImportedMember me, aPath, anObject if (pDownloadImportMap.getAProp(aPath) <> VOID) then if (not stringP(pDownloadImportMap.getAProp(aPath))) then return pDownloadImportMap.getAProp(aPath) else return 0 end if else return 0 end if end -- returns the text that was located at aPath -- returns "" if the media hasn't been downloaded yet. -- if the value associated with aPath is NOT a string, the caller has requested -- a binary document (an imported cast member), so this handler returns "" on getDownloadedText me, aPath if (pDownloadImportMap.getAProp(aPath) <> VOID) then if stringP(pDownloadImportMap.getAProp(aPath)) then return pDownloadImportMap.getAProp(aPath) else return "" end if else return "" end if end -- This handler checks the progress of current downloads on processDownloads me if (pManagerActiveFlag) then -- if downloading is turned on me.importCompletedDownloads() me.downloadFromQueue() end if end -- This handler checks the progress of current downloads, and -- imports the media when each download is complete. -- THIS HANDLER REQUIRES THAT "gMediaManager" HAS BEEN INITIALIZED on importCompletedDownloads me if (pNetIDList.count) then -- if objects are currently being downloaded, check their status netIDsToRemove = [] -- cycle through the current Net IDs, check download progress, and import completed transactions repeat with a = 1 to pNetIDList.count thisNetID = pNetIDList.getAt(a) -- BEGIN HANDLING OF COMPLETED DOWNLOADS if (netDone(thisNetID)) then downloadSuccessful = me.verifyDownload(thisNetID) -- BEGIN HANDLING ERROR-FREE DOWNLOADS if (downloadSuccessful) then -- BEGIN IMPORT LOGIC thisMediaPath = pCurrentDownloadList.getPropAt(a) -- get the download path theseRegistrants = pCurrentDownloadList.getAt(a) -- get the download registrants thisMediaType = me.determineMediaType(thisNetID) -- determine the type of media -- IF THE MEDIA TYPE IS #text THEN -- add the text to the import map -- notify the callers that their media is ready -- IF THE MEDIA TYPE IS A BINARY (any symbol other than #text) -- create a blank cast member (REQUIRES THAT gMediaManager IS INITIALIZED) -- import the media -- add the media info to the import map -- notify the callers that their media is ready if symbolP(thisMediaType) then case thisMediaType of #text: -- this is a *very* bad line of code. pDownloadImportMap.setAProp(thisMediaPath, netTextResult(getNetText(thisMediaPath))) -- snag the text stored in netTextResult and push it into the download import map array me.notifyRegistrants(theseRegistrants, #done) -- notify registered objects that the download was successful otherwise: thisMemberNumber = gMediaManager.createCastMember(thisMediaType) importFileInto(thisMemberNumber, thisMediaPath) pDownloadImportMap.setAProp(thisMediaPath, thisMemberNumber) -- add (or reset) the cast member number and path info in the Download import path me.notifyRegistrants(theseRegistrants, #done) -- notify registered objects that the download was successful end case else -- incompatible mime type, notify the registrants me.notifyRegistrants(theseRegistrants, #error) -- notify registered objects of a download error end if -- END IMPORT LOGIC end if -- END HANDLING ERROR-FREE DOWNLOADS NetIDsToRemove.append(thisNetID) end if -- END HANDLING OF COMPLETED DOWNLOADS end repeat -- end cycling through Net IDs, status checking, and importing -- remove Net IDs and currentDownloadList info for completed downloads repeat with a = 1 to netIDsToRemove.count() thisNetID = NetIDsToRemove.getAt(a) thisIndex = pNetIDList.getPos(thisNetID) pNetIDList.deleteAt(thisIndex) pCurrentDownloadList.deleteAt(thisIndex) end repeat -- end Net ID removal end if end -- if the maximum number simultaneous downloads has not been exceeded, -- this handler begins downloading queued items, until the pMaximumThreads is reached on downloadFromQueue me -- only begin downloading if there are available threads and items in the download queue repeat while ((pNetIDList.count() < pMaximumThreads) and (pDownloadQueue.count())) -- get the first download path (property) and its associated list thisDownloadPath = pDownloadQueue.getPropAt(1) thisRegistrantList = pDownloadQueue.getAProp(thisDownloadPath) -- begin downloading the file located at the first path thisNetID = preloadNetThing(thisDownloadPath) -- Message Display logic pCurrentDownloadNumber = pCurrentDownloadNumber + 1 -- increment the current download number gStatusBarManager.displayMessage("Receiving File" && string(pCurrentDownloadNumber) && "of" && string(pTotalDownloadsQueued)) -- display download status message -- end message display logic -- append the net id to the net id list and the path:registrants to the current download list pNetIDList.add(thisNetID) pCurrentDownloadList.addProp(thisDownloadPath, thisRegistrantList) -- delete the remove the current download path: registrant list from the download queue pDownloadQueue.deleteProp(thisDownloadPath) me.notifyRegistrants(thisRegistrantList, #loading) -- notify the registered objects that the media is currently downloading end repeat end -- deletes all of the media that was downloaded -- THIS HANDLER REQUIRES THAT "gMediaManager" HAS BEEN INITIALIZED -- since pDownloadImportMap now stores downloaded text as well as cast member info, -- a conditional statement prevents text being sent to the media manager. on deleteAllDownloads me repeat with a = 1 to pDownloadImportMap.count() thisImportMember = pDownloadImportMap.getAt(a) if (not stringP(thisImportMember) ) then gMediaManager.deleteCastMember(thisImportMember) end if end repeat end -- accepts an integer NetID as an argument, returns a symbol of the media type of the downloaded data -- if an invalid mime type (i.e. one not described in the case statement) is found, this handler -- returns 0. on determineMediaType me, aNetID mediaType = 0 mimeType = netMIME(aNetID) -- put mimeType case mimeType of "image/gif": mediaType = #bitmap "image/jpeg": mediaType = #bitmap "text/plain": mediaType = #text end case return mediaType end -- sends aMessage to the specified object(s) -- registrants is an object id or a list of object ids -- THIS METHOD ASSUMES THAT REGISTERED OBJECTS (OR THEIR ANCESTORS) HAVE "setLoadStatus()" HANDLERS on notifyRegistrants me, registrants, aMessage if (listP(registrants)) then repeat with a = 1 to registrants.count thisRegistrant = registrants.getAt(a) thisRegistrant.setLoadStatus(aMessage) -- send the message to the registrant end repeat else if (objectP(registrants)) then registrants.setLoadStatus(aMessage) end if end -- accepts an integer NetID -- This handler checks for download errors. -- Returns 1 for no errors, 0 if errors were found -- If errors were discovered, this method notifies the appropriate registrants on verifyDownload me, aNetID error = netError(aNetID) if (error = "OK") then return 1 else -- TEST CODE put error -- END TEST CODE thisIndex = pNetIDList.getPos(aNetID) theseRegistrants = pCurrentDownloadList.getAt(thisIndex) me.notifyRegistrants(theseRegistrants, #error) -- notify registered objects of a download error case error of 4: 5: 6: 20: 4146: 4149: 4150: end case return 0 end if end