MEDIA STORAGE AND RETRIEVAL SYSTEM
Background of the Invention The invention relates to non-linear editing systems and the storage and retrieval of the media associated with the system, i.e., video and audio data. Non-linear editing on computer oriented systems involves digitizing media data recorded from a linear source, e.g., a video tape cassette, and storing the digitized media data on a storage device, e.g., a hard disk drive. Once digitized, the media data can be accessed quickly at any point in the linear sequence in which it was recorded so that various portions of the data can be accessed and edited in a non-linear way.
Editing in either a linear or non-linear system involves a similar principle. Source material from some source (video tape, audio recording, film etc.) is broken down into a series of separate "clips" representing the material desired for the final master, and then reassembling these "clips" into a final sequence achieving the desire of the editor and producer. "Clips" can be either video or audio material or both (synchronous audio and video.) In a non-linear system the typical approach involved alloting to each clip an associated digitized section of the original source in storage on the system in a "media file." The system would allow the user to manipulate the clips in order to produce the final sequence. The clips referred to the media files when certain specific information about the source media was needed, such as the original source name or nature of the media (video or audio), or when the need arose
to actually view or hear (i.e., play) the media associated with the clip.
For example, a user editing on a non-linear system had the ability to manipulate clips into any order, use audio clips with other video clips, and create new clips by using smaller pieces of other clips. Tools existed to allow the user to combine clips of similar material for other effects. Video clips were used in combination to create dissolve effects, and audio clips to create various audio effects.
Typically, the output of an edit, i.e., an editing procedure such as the one described above, is an "Edit Decision List" (EDL) which can be used either by a conventional on-line editing system such as the CMX300 or a non-linear system to create or assemble a new linear sequence from other existing linear source material, e.g., video tape. The EDL is used to direct the on-line system to lovate or "cue" the first frame of a desired clip which is recorded on a source video tape and loaded into a video tape recorder (VTR). The editing system then records the cued clip onto a target or destination medium, e.g., video tape, and cues the first frame of the next desired clip. (Note that the next desired clip may be recorded on the same or a different physical source medium as the first clip). Once cued, the editing system records the next desired clip onto the target medium. This process is repeated until the EDL is exhausted and the target medium represents the selected original material reorganized into the sequence described by the EDL. The standard or conventional method when establishing a system of media archival is as follows: As each clip of source material is captured for storage in the system, the information about the clip and its actual
digitized data is either coresident or linked directly at the time of the capture. Whenever the clip is referenced by the user of the system, the media associated with it is always the same particular one that was associated with it at the time of the capture (whether the media was digitized or actually was still intact on the original source). Any manipulation or editing concerning the clip or segment would directly use the media data tied to it for viewing or playback. Any information about the source that it came from or equivalent sources would need to be stored with each clip or segment. As such, the whole collection of clips or segments would be needed at any time in order to determine the breadth of any source relationships. And as new source relationships were developed it would be difficult if not impossible to inform all clips or segments of the new information. Additionally, tying the media data directly to a clip or segment would make it necessary to duplicate media data if certain clips or segments overlapped or were contained entirely within one another. The invention solves these and other difficulties and problems.
Summary of the Invention The invention involves dynamically linking or binding a digitized representation of the media with a specific reference to the media at the time the information is needed at run time and being able to change the binding as certain facets in the system change. To that end the invention is a system for determining the media needed at the time a clip is requested to be played, viewed or information retrieved concerning the media associated with the clip. Specifically, each clip is dynamically connected to the specific media at the time that it needs access to the media associated with it.
The invention also involves the separation of information concerning the specifics of a piece of digitized media, information specific about the source material the media was derived from, and information concerning the connection of media data to those requesting or needing access to it. Specifically, the three groups of information that are distinctly separate from each other are:
(1) the information concerning physical source mediums may indicate which sets (or subsets) of physical source material are equivalent, or make correlations in the labeling of certain segments of the source material (example: film edge numbers equivalenced (i.e., correlated with time code);
(2) the information about the specific digitized media as to the type of media, the length of the data, the range on the source the media represents and the locations of such media resources; and
(3) the information concerning the binding of the media data to the requesters of media. Included in the invention is the concept that the binding of media resources to those in need of the media is not made until the request for the media is made, and the fulfillment of the request may change depending on the media available at the time of the request. The invention also involves the method of storage and retrieval of the necessary source relational information from one invocation of the application to the next, such that it is coresident with the clips and/or media that it is specific for. This makes knowledge of the form of information storage inperceptable to the user of the system. Advantages of such a system are described below: Media need only be digitized once. Clips referring in part or in whole to the same media result in references
to the same physical data in the system. Duplicate copies of the media are not needed or created.
Deletion and recapturing of segments of the original source results in all clips referring to the specific new source material entered into the system.
Clips requesting media from one physical source may receive media from a distinctly different physical source if the sources have been identified as equivalent.
Actual location of the media in storage is free to move to any location on disk, without notification necessary to clips requiring reference to the media.
Description of Preferred Embodiment Fig. 1 is a block diagram illustrating the overall functional relationships of the media storage and retrieval system according to the invention, with media hardware, disk storage and the user interface.
As shown in Fig. 1, media sources such as VTRs containing linear media data are controlled via editing and capture systems under the user's control. Digitized media data from the capture system is transferred and stored on a mass storage volume such as a hard disk drive as digitized media files and selectively retrieved under the control of a media storage and retrieval system which is the subject of the present application. The media storage and retrieval system is implemented preferably as a software application having two distinct components: the media file manager (MFM) and the source manager (SM) along with their respective databases, namely, the media database and the SM database. A user accesses and operates on the digitized media files via calls placed by the editing system to the
MFM which both creates and manages the media files. The MFM also interacts with the SM which inter alia maintains a
table of relations between the linear media data, recorded, for example, on source tapes, and the digitized media files. MFM exists in modular software form and consists of a procedural interface that accepts requests for specific pieces of media from specific source material. SM exists in modular form and consists of a procedural interface that accepts requests for source material relational information, and requests to read or write source relational and specific information to an area of storage. The source code appendix provides specific code for implementing both MFM and SM. The system makes use of two other procedural subsystems: one being a linked data list manager, and the other being a sectional file writing and reading manager. These subsystems are conventional utilities sometimes offered as "toolboxes".
Linked data list management involves the functions of linking together records of information in some order. Other procedural interactions with this tool make it possible to sort the records in a variety of orders depending on certain key fields in the records. The list manager is also able to "sift" certain records from the entire pool of records based on requested values of key fields in the records.
Sectional file writing utility provides the ability for multiple clients to write data to the same file and retrieve it without knowledge of either the format of the file or the identity of other clients using the file.
Media File Manager Media File Manager (MFM) is responsible for the management of all media that is present at any time and available to the system for use. This media may be media recorded earlier and stored in some medium, or available on demand via some link to mechanical devices. The media may
be duplicately represented in a variety of resolutions or qualities. MFM's purpose is to locate media (or information pertaining thereto) specified by a user request consisting of a range of time from some specific source. The range in time may be specified by the now common SMPTE time code, film edge numbers, real time or some other standard of range identification of source material. The request does consist of a specific source from which the media is desired. This specific source request is unique and the system works on the concept that identifiers exist that separately identify any source medium.
At any time after the system is initialized the MFM has an internal abbreviation (i.e., a log or set of records) of all the media that is known to be accessible to it, and where and how that material may be retrieved. The internal abbreviation is contained in the media database built by the MFM. When a client of the MFM requests information or access to actual media, MFM uses its internal abbreviation of the media available to determine a media source that will serve as a satisfactory response to the client's request. A MFM identifier is returned to the client. In any other requests for the specific media the client may use the identifier for quick access to the material. This identifier represents the dynamic link or binding of a client's need for media and the actual source of media to be used. This identifier remains constant to the client, and any media deletions, changes or additions are changed internally by the MFM, i.e., transparently to the user, and have corresponding effects on any identifiers already released. As described in the procedural interface, two different types of identifiers can be released by the MFM. For the sake of differentiation, one type is designated a MFM_CRUX and the other a MFM_GIST, the main difference
between these being a level of privilege available to the client holding one and/or the other. The holder of a MFM_CRUX is able to make any number of procedural calls to MFM requesting information about the media, but the privilege to read or write the actual media is withheld. Using a MFM_CRUX, the client can call a routine mfm_open which will give the client a MFM_GIST identifier. This identifier is accepted by mfm_read and mfm_close to be able to read the actual media from the file. The reason for this is to give the MFM some control over which physical connections to actual media are opened or closed. On some systems this is desired as the number of channels to existing media may be limited, and MFM needs a method of managing access. Media File Procedural Interface
The media file manager (MFM) is a procedural interface between a media requester and the media files themselves. The interface consists of a set of software modules which are described below. - mfm init
Mfm_init is called at the time the application is invoked. It is a one-time operation. Its basic functionality is to initialize the linked list data structures that will be needed and determine all media available on the system at the current time. Functionally it scans all the disk drives on the system and determines if the short-hand version of the media file database which it has previously placed on the drive is valid. This is determined by comparing the time stamp on the file with the time stamp on the directory in which it was stored. If they are equal (they are made equal when the database was written) then the database is valid and MFM proceeds to read in the information pertaining to it into RAM using the
sectional file routines and then passes the file to the SM (sm_readNtable) so that it can read in the SM information stored there. Of course, the file itself is not transferred; only its address in memory. If it is invalid then the file is passed to the SM for processing the SM information contained in it (see mfm_quit), and then all media files on the volume are scanned individually. In the reading of the media databases and the scanning of the other drives the run time media database (Fig. 1) is initialized with its original media. - mfm_handle
Mfm_handle is the call a client, e.g., the user interface (Fig. 1), uses to receive an identifier (MFM_CRUX) giving inquiry rights on the media and for determining a binding between the request and an actual media source. The request is comprised of a source unique identifier or "id", a range on the source, type of media (video or audio,) and the physical channel requested if any that the source media was recorded from (for instance the type of media may be audio, and the physical channel may be two, indicating audio2 media). To handle the request MFM sifts through its existing linked list of records based on the values of the request. (This is actually done with search support procedures within the linked list utility.) If a match is found then the handle to that record is returned in the form of a MFM_CRUX. If no media is found, then MFM calls SM_relate to determine if any other source material has equivalent material contained in it, equal to that being requested by the client. If so, MFM then again sifts its database, looking for the appropriate media on these other sources. If any is found a MFM_CRUX handle is returned to the client. If no media is obtained via any of these methods, mfm_handle returns a MFM_CRUX for BLACK or SILENCE
depending on the media type originally requested, and flags an error message that no media as requested was available.
- mfm_open
Mfm_open is called by the client when it will be necessary to actually obtain media data from the media source. Mfm_open accepts a MFM_CRUX identifier, which the client/requester must have already obtained, and proceeds to establish a connection with the media source (open the file). Once the connection is established the client is given a MFM_GIST identifier. This identifier can be used with mfm_read calls to obtain the actual raw media data.
- mfm_read
Mfm_read is the procedural interface used to pass actual media data from the media source into a buffer specified by the caller. The parameters to the call are designed such that the caller asks for a frame of information using a range identifier to identify the frame offset from zero of the range scale. For example in the time code ranging method, the caller asks for the absolute time code of the frame desired. The call analyzes the type of media being requested, the size of the buffer the caller has provided and the number of frames the caller has requested. Based on this information a best fit is made for the caller's buffer and the actual number of frames passed to the buffer is returned.
- mfm_close
Mfm close is used to allow MFM to close the channel to the media source. The call accepts a MFM_GIST identifier and from this identifier MFM is able to distinguish if the media channel is open for write (a previous mfm_create call), or open for read (a previous mfm_open call).
If the media channel is open for write, the call examines a parameter which indicates caller specific
information about the nature of the write to the channel. Length, range identifier, media identifier and data rate over time are all specified. MFM includes this information in the media channel in a header and then closes the channel. This media channel (source) is now available in the MFM database as a possible candidate for fulfilling a mfm_handle request.
If on the other hand the channel was open for read (via a previous mfm_open) the channel is simply noted as being closed for that particular client, and if no other clients are using the channel then the channel is closed.
Regardless of the type of closure, the MFM_GIST identifier passed in is no longer valid, and a MFM_CRUX identifier is passed back to the caller. This identifier would be used in further calls to mfm_open if the client again desired an open channel to the media data.
The call mfm_close also makes decisions on the type of channel being created. Two types are possible, temporary and disk resident. A temporary media channel exists in memory only for the duration of the application run, disk resident files are placed on disk and will be available at the time of the next application invocation. For example, an experimental dissolve effect on two other video media channels might become a temporary file, while actual video from an external source might be captured, digitized and stored in a disk resident file. - mfm_create
Mfm_create is the procedural interface used by the client who wishes to permanently store media data on file with the MFM. The call grants the caller a MFM_GIST identifier allowing calls to mfm_write to actually write the media data to a open channel. At the time of the call MFM checks its available space for recording of such information
and sets up to receive media data into such space. Specifically mfm_create creates files on disk and preallocates their size as large as possible. The initial header on the file is created and it is cast a media channel in the stage of creation. In this way it can be identified later if the caller fails to write data to the channel, or fails to close the channel (via mfm_close.) - mfm_write
Mfm_write is the procedural interface used by the caller to actually transfer media data obtained from source into a media channel of the MFM. It is in this way that MFM is able to store media data for use later as a media source in response to mfm_handle requests.
Specifically the call takes in a pointer to a buffer containing the data and a length on this particular buffer. The data is copied from the buffer for the specified length into the media channel identified by the MFM_GIST identifier handled in via a previous call to mfm_create. The buffer information is simply copied onto the end of any data already written to the channel. The channel may be a temporary channel (main memory) or a disk resident channel (disk file). The two types of records are structured according to the following formats.
Runtime MFM record structure
One of these is present in memory at runtime for each known media file out on disk.
See C typedef for MFM_Crux_t in mfm_pvt.h
Channel identifier
This is the physical channel associated with the media type from the physical source. Ie: Two tracks of audio from the same source would be differentiated by different channel identifiers.
File_use
An internal identifier indicating whether the media file is open for access and if so the nature of the open, read or write.
Media Type
This is an internal identifier indication what type of media data is stored in the file. Ie: video or audio or some other.
File Type
This is an internal identifier as to the format of the media stored in the file.
Volume ID
DirlD
Filename
These three fields together indicate the exact position on disk such that the file can be opened, read, written or closed.
UID
This is the unique source medium identifier. This is the uid that the SM tracks and manages in its database.
Start_time
This is the range identifier for the position in the source that the beginning of the media data in this file corresponds to.
End_time
End range identifier.
Media_specific
This is a optional area for storage of specfic information for this particular typ e of media.
Diskfile MFM header
One of these is present on disk at the beginning of each media file.
See C typedef for mfm_base_t in mfm_pvt.h
Channel identifier
This is the physical channel associated with the media type from the physical source. Ie: Two tracks of audio from the same source would be differentiated by different channel identifiers.
Media Type
This is an internal identifier indication what type of media data is stored in the file. Ie: video or audio or some other.
File Type
This is an internal identifier as to the format of the media stored in the file.
Name
This is a copy of the character name the user specified as the source of the media data in the file.
DTD
This is the unique source medium identifier. This is the uid that the SM tracks and manages in its database.
Start_time
This is the range identifier for the position in the source that the beginning of the media data, in this file corresponds to.
End_time
End range identifier.
Media_specific
This is a optional area for storage of specfic information for this paricular type of media.
- mfm_guit
Mfm_quit is the procedural called used by the application when the application is quitting. It provides MFM with the opportunity to clean up and write any information necessary out to permanent storage before quitting.
Specifically, the list of current media channels known to exist is sorted and sifted based on the areas in which the channels exist. All the records in the list for those channels existing on a single hard disk are grouped together and written to disk as a single file. This write of information is done using the sectional file write utilities. Once the database is written to disk in this abbreviated form, the file handle is passed to SM (sm_closeNwrite) so that SM information pertinent to the media channels on this disk can also be recorded in the file. MFM is unaware of the format or specifics of the SM information. Once this is done for all existing disk drives, MFM releases any remaining storage used for its run time databases or lists. Control is returned to the caller (the application).
Source Manager Source Manager (SM) is responsible for the management of all information specific to the physical attributes of the source mediums. Each source medium is assigned a numerically unique identifier (UID) upon initial exposure to the system. To ensure a unique identification, the UID is a random number such as a combination of the seconds since January 1, 1904 and the number of seconds since the system was started. The user specified name of the source is coupled with this unique identifier. This name/unique id pair is the minimum information entered as a record in the Source Manager.
At its most primitive functional level the SM manages the list of source medium names being used or referred to internally in the system at any point in time. As an additional function the SM is responsible for maintaining the relational connections of source mediums. For example: if two video tapes have been identified with different names, but actually contain the same source video (albeit possibly different time code labelling), the SM is charged with cataloging this information once the user has made the information available. In a more specific and restrictive example it may be the case that only some portion of some source material is equivalent to some other portion of some other source material. This also the SM is responsible for storing and managing. These relationships may exist using different standards of labeling. For instance: SM could store the relationship that a particular piece of film starting at a specific edge number is equivalent to some video tape at a time code for some number of frames of video (or feet of film). This information is available to clients of the SM for whatever purposes are deemed necessary by the clients. One such client in the system described here is the mfm_handle procedural call in the MFM (See the description of mfm_handle).
The run-time SM database is retrieved at each invocation of the application. Specialized procedures are used for retrieval and reconstruction of the SM database. The design and selection of the content of the information stored as well as the retrieval method itself allow the SM to accommodate changes in the media sources available between application runs and mobility of files describing the edited sequences between systems independent of the media associated with them. The SM not only keeps track of the location of the media files and their sources but also
keeps track of varying equivalency relationships between the files and portions of files. Given these properties, the SM functions in such a way as to never require the user to also be knowledgeable of the location of this source specific information that the SM maintains. To avoid the need of storing this information in its own localized place, the SM stores the pertinent pieces of its database in the interested client's disk resident files. At the time of application invocation, as each of these clients accesses or opens the files specific to it, the SM is notified and also reads in the pertinent data to it, stored there at a previous time, or by the SM of another system.
Source Manager Procedural Interface
- SMInit Sm_init is the procedural interface the application uses upon invocation in order to initialize the SM data structures. It is called once at this time and not again for the duration of the running of the application.
Specifically, the call uses the linked list manager to set up data structures to accept records of source names and source identifiers and for storage of any relational information between these existing sources. The actual data for the data structures is accumulated at a later time. No file is read in from disk or elsewhere at this time for initial seeding of the SM's list. - SMReadNTable
SMReadNTable is the procedural interface used by clients of the SM, enabling SM to read in data to be added to its data structures. Clients of SM who had in a previous application run provided SM the chance to include information in a file via use of the sectional file utilities use this call to enable the SM to read in the data and place it in the SM data base.
Specifically, the SM processes the call as follows: First, the sectional file handle passed in the call is queried to determine if there is in fact any name information in the file pertinent to the SM. If there is, the length of the data is determined via a call to the sectional file manager and then the information is read into a block of main memory. Once the data is in main memory the data is traversed and processed. For each record of information it is checked to see if the information is already in the SM's database. If it is, it is skipped, if not it is added. This continues until the entire block of information is exhausted. Once this process is complete, the file is checked for the presence of any SM relational information. If any exists, a block of memory is allocated large enough to hold the information and the data read into it. Once in memory, it is traversed and processed. In much the same way as previous, any new information is added to the SM's database and redundant or repeated information is ignored. Once the process is complete, control is returned to the caller, and any memory allocated is returned. - SMOpenTable
SMOpenTable is the preliminary procedural call used by the caller to inform the SM to prepare for building a list of source identifiers. This list of source identifiers will be later used to determine the pertinent information to be written to a sectional file handle.
Specifically, SMOpenTable initializes a linked list to zero and returns an identifier to the list (i.e., the "list identifier") to the caller. This identifier is used in subsequent calls to SMBuildTable as the caller encounters source identifiers to be included in the file it is creating.
- SMBuildTable
SMBuildTable is the procedural interface used by the client to indicate the source identifiers for which the pertinent SM information is to be stored in a sectional file manager handle to be indicated later (SMCloseNWriteTable). The client making this call need not be concerned about indicating the same source identifier multiple times. SM will resolve these duplications later when actually writing the file (SMCloseNWriteTable). Specifically, the procedure uses a source identifier passed in by the client to locate the record in the SM database and make an instantiation of the record in the list being built. The list being built is indicated by an identifier passed into the call. This list identifier was derived from a call to SMOpenTable.
- SMCloseNWriteTable
SMCloseNWriteTable is the procedural interface used by the client to indicate to SM that it should use the source identifiers accumulated in all previous calls to SMBuildTable on the specific list identifier, and write the pertinent SM information for those source identifiers to the sectional file manager specified. The procedure determines the unique list of identifiers, all relational information associated with that list and then also includes any new names introduced by the processing and inclusion of any relational information. This secondary inclusion happens only once and does not cause a recursive iteration of the algorithm. The information pertinent is then written to the sectional file manager handle specified, and control returned to the caller. It is the caller's responsibility to close the sectional file manager's handle.
Specifically, the process is as follows:
First, the list of source identifiers is sorted and then traversed. As the traversal ensues, all duplications are eliminated yielding a list of unique records. Also as a product of the traversal, for each unique source identifier processed, the relational information pertinent to that specific source identifier is added to a list of relational information. Once this is completed, the list of relational information is traversed and processed.
As the list of relational information is traversed, two separate operations take place. First, the relational record is written to the sectional file manager handle specified in the call.
Second, any new source identifiers encountered are added to the unique source identifier list. After the relational list is processed, the source identifier list
(with source names) is written to the sectional file manager handle. This completes the process for SMCloseNWrite. Control is returned to the caller. - SMRelated SMRelated is the procedural interface for clients of the SM to obtain information about source relationships (relational information) that SM is managing. This is the primary function of the SM to the other systems in the application. For a given source identifier and range identification on that source, SMRelated will report any other source identifiers and ranges that have equivalent media. For example: Let us assume video tape A has a copy (or clip) of some material from tape B on it, and that copy of material occurs on tape A from time code 2 hours 7 minutes to time code 3 hours 27 minutes and on tape B from time code 4 hours 17 minutes to time code 5 hours 37 minutes. A caller to SMRelated asking for relations to tape B, time code 4 hours 40 minutes for a duration of 20
minutes, (i.e., a portion of the clip) would receive from SMRelated the information that a duplicate exists on tape A, 2 hours 30 minutes for a duration of 20 minutes.
Specifically the procedure works as follows. The record in the SM's database specified by the source identifier in the call is located. The relational information tagged on that record is sifted for the ranges specified in the caller's range specification. A list is constructed of all resultant records using the related source identifier and the related range specification. This list is a linked list manager list and the handle to it is returned. Control is returned to the caller. When the application quits via MFM_QUIT, the relations created by SMRelated are written to the MFD 14 on disk by the operation SMCloseNWriteTable which is described above. In this way, the table of relations is preserved in the media database on disk so that its information is easily transported with the media database.
The attached appendix embodies the MFM and SM modules. The programming language and compiler used are THINK C version 3.01 by Symantec Corporation, and the computer used is the Macintosh II running under Mac OS version 6.0.2.
Portions of the disclosure of this patent document and the accompanying appendix contain material which is subject to copyright protection and for which copyright protection is expressly claimed. The copyright owner has no objection to the facsimile reproduction, e.g., photocopy, by anyone of the patent document as it appears in the Patent and Trademark Office files, but otherwise reserves all copyright rights whatsoever, for example, including but not restricted to the right to load the software on a computer system.
In addition to the advantages already discussed above, the media storage and retrieval system according to the invention accommodates changes and modifications to the source of media and makes equivalent media available to the client of the method without notification to the client. When more complete media becomes available from another source, or when sections of the requested media are absent in the original media channel given, the source of the media can be changed. The system offers the capability of dynamically linking the client of the media with the media available at the run time of the application. Such links are possibly different with each run of the system, and channels of media may actually change even after the link is established. The flexibility of the media management system in effect frees the user from making the actual selection of source media as well as keeping track of source media equivalencies, without unduly burdening the process of making the basic identifying request. Other embodiments are within the following claims.
SOURCE CODE APPENDIX
Applicants: Stephen J. Reber et al. Title: MEDIA EDITING SYSTEM
Jourceman.h
Saturday, December 2, 1989 3:11 PM
\* * Module Name: SourceMan.h * * Module Description: Public protocol to Tape management module * * Revision History: * $Log: Engineering:PVCS:Sources:sourceιran.h_v $ * * Rev 1.9 02 Dec 1989 15:11:00 SJR * exit film structs and calls, fix Black and silence in source dialog * Rev 1.8 22 Nov 1989 17:10:06 SJR * film struct in sourcemanager * * Rev 1.7 15 Nov 1989 16:02:02 SJR * rewrote mfm_handle to correctly use muliple tapenames from SMRelated * * Rev 1.6 06 Nov 1989 12:23:04 SJR support uid_t * * * Rev 1.5 03 Nov 1989 13:04:30 SJR *got rid of getting uid by name in sourcemanager * * Rev 1.4 31 Oct 1989 10:37:46 SJR *rewrite SMCloseNWrite and SMReadTable * * Rev 1.3 24 Oct 1989 12:00:14 SJR * support Source Manager *
* Rev 1.2 16 Oct 1989 00:47:36 SJR * late night mfm integration * * Rev 1.1 11 Oct 1989 20:09:26 JHR * No change. * * Rev 1.0 11 Oct 1989 19:20:40 JHR * Initial revision. * * /-------------------------------------------------------------------------------------------------------------------- * | The following programs are the sole property of Avid Technology, Inc., | * | and contain its proprietary and confidential information. | * | Copyright © 1989, Avid Technology | * /--------------------------------------------------------------------------------------------------------------|
#ifndaf SRCM_H #define SRCM_H 1
#include "cluster.h" typedef long SMTableid;
typedef struct { timecode_t uid1_start; time code_t uid2_start; sourceuid_t uid1; sourceuld_t uid2; long uid1_generation; /* zero is original */ long uid2_generation; long Nframes;
} SMrelations_t,
* SMrelatlons_ptr,
**SMrelatlons_hdl; /*
* Error messages
*/ #define SRM_INVALID_NAME (0 | ISTAT_SRCM) #define SRM_DUPLICATE_SOURCE_UIDS (1 | ISTAT_SRCM) #define SRM_NO_SUCH_UID (2 | ISTAT_SRCM) #define SRM_NAFE_NOT_FOUND (3 | ISTAT_ SRCM) #define SRM_BAD_FILE_ HANDLE (4 | ISTAT_ SRCM) #define SRM_LIST_CORRUPTION (5 | ISTAT_SRCM)
/************************************************
* NAME: SMInit *
* FUNCTION: Intialize the data structures for the source manager. */
SMInit ( istat_t *istat); /**********************************************************************************************************************
* NAME: SMRelated
*
* FUNCTION: Determine the relation between tapename and uid fed in with other
* tapenames in the tape database. Return a handle to a list of
* tapes_t. Count is number of tapes_t records on the handle.
*
* NOTE:
* Caller is responsible for Disposing of the handle. */
SMrelations_hdl SMRelated( sourceuid_t sourceUID, time code_t start, timecode_ t end, long *count, istat_t *i* stat); s t a t ) ;
/************************************************
* NAME: SMSetRelate *
* FUNCTION: Add the relation information to the SMrelations list. *
*/ void SMSetRelate ( SMrelations_ptr relations, istat_t *istat);
/*************************************************
* NAME: SMAddName
*
* FUNCTION: Add the name to the source manager list.
*
* NOTE:
* If uid points to NULL a new UID will be generated. If the uid
* points to a nonzero value, the name and uid will be added as a pair. */ void SMAddName( name_t sourcename, sourceuid_t *uid, istat_t *istat);
/*************************************************
* NAME: SMGetUid
*
* FUNCTION: Given a sourcename, cough up the source uid.
* Reber says - this goes away real soon. Don't use it.
*
*/ void SMGetUld( sourceuid_t *uid, name_t name, istat_t *istat);
/*************************************************
* NAME: SMGetName
*
* FUNCTION: Given a sourceuid, cough up the sourcename. */ void SMGetName, sourceuid_t uid, name_t name, istat_t *istat);
/********************************************************
* NAME: SMNameDialog
*
* FUNCTION: Put up the Source Name Dialog and get a sourcename.
*
*/ void SMNamaDialog ( char "prompt, sourceuid_t *uid, istat_t "istat); /**********************************************************************
* NAME: SMOpenTable
* FUNCTION: Open a table to enable setting up a list of
* UIDs to be written to disk. */
SMTableid SMOpenTable ( istat_t* istat); /***********************************************************************************************************************
* NAME: SMBuildTable
* FUNCTION: Build a table of UIDs to be written to disk.
*
*/ void SMBuildTable ( SMTableid tableid, sourceuid_t UID, istat_t *istat);
/***************************************************************************
* NAME: SMCloseNWriteTable
* FUNCTION: Write Source Manager UID table out to file.
*
*/ void SMCloseNWriteTable ( SMTableid tableid, cluster_ t *cluster, istat_t *istat); /*********************************************************************************************************** * NAME: SMReadNTable
* * FUNCTION: Read Source Manager UID table from file.
* */ void SMReadNTable ( cluster_t "cluster, istat_t' istat); #endif SRCM_H
mfm .h
Tuesday, December 5, 1989 10:55 AM
/*
* mfm.h - Include file for media file manager.
*
* Copyright © 1989 Avid Technology, Inc. */
#ifndef MFM_H #define MFM_H 1
#include "avid_base.h" #include "timecode.h" #include "sourceman.h" #include "soundintf.h" #include "channel.h"
/*
* $Log: Engineering:PVCS:Sources:mfm.h_v $
*
* Rev 1.29 05 Dec 1989 09:45:28 SJR
* handle file size setting and disk full conditions
*
* Rev 1.13 12 Oct 1989 09:57:56 SJR
* rewrite mfm to handle media files dynamically by sourcename and
* timecode range rather than by direct pathnames.
*
*/
/* The available file types for media files.
* SLIDE - file with one frame of information intended to be repeated for some length.
* ABRIDGED - file with one frame of information representing a previous full file.
* TEMP - and in memory file that is never written to disk.
* FULL - full digitized material file, resident on disk.
*/ enum {
SLIDE,
ABRIDGED,
TEMP, FULL
}; typedef long Ftype_t;
/*
* Media types: */ enum
{ still_writing, /* file is open for write, Mtype unknown */ vldeo_cm8, /* 8-bit Video (std colors) */ audio /* 8-bit?? Audio */
}; typedef u_long capture_mask_t; typedef struct { capture_mask_t mask; long shift; } capture_read_t; typedef struct { capture_ mask_ t mask; /* capture mask. 0 means all frames captured. */ float rate; /* effective capture rate */ u_char start_offset; /* offset in capture mask for first frame */ u_char length; /* length of capture mask */ u_ char one_bits; /* number of 1-bits in capture mask */
} capture_data_t, * capture_data_ptr, **capture_data_hdl; def struct { short height; /* TEMPS */ short width; /* TEMPS */ capture_data_t capture;
} vldeoSpecWrlte; typedef struct {
/* a little entropy here.
* On a mfm_close the caller could be closing an actual digitized file, or a
* temporary one. In the case of an actual digitized one, the field videofile will
* be used to determine the number of video frames associated with this audio. In
* the case of a temporary the field video_frames will be expected to have the caller's
* intention of the number of frames to associate with the audio. In the case of a null
* videofile handle for a disk file, or video_frames ==0 for a temp file, a best guess
* for the number of associated video frames will be calculated, reber 9/26/89 */
MFM_CRUX videofile; /* associated video file * / long video_frames; /* TEMPS */ long bytes_per_sample; /* TEMPS */ long samples_per_second; /* TEMPS */
AudicMode audio_mode; /* TEMPS */
} audioSpecWrite; typedef union { videoSp ecWrite vldeo_cm8; /* TEMPS */ audioSpecWrite audio; /* TEMPS */
} unionSpecWrite; typedef struct { long frame_rate; /* TEMPS */
Mchannel_t channel; /* TEMPS */ timacode_t start_time; sourceuid_t sourceid;
Ftype_t Ftype; /* TEMPS */
Mtype_t Mtype; /* TEMPS */ u_long trlm_file_size; /* truncate file to this size */
/* zero indicates no truncation neeeded */ unionSpecWrite specific; /* TEMPS */
} mediaSpecWrite _t, * mediaSpecWrite_ptr; typedef struct { capture_read_t capture; /* capture mask stuff for video */ } videoSpecRead_ t; typedef struct { AudicMode audiomode; /* audio mode of the audio media */ } audioSpecRead_t; typedef union { videoSpecRead_t video_cm8; audioSpecRead_t audio; } unionSpecRead;
typedef struct { long logical_frames; /* number of logical frames obtained on the
* mfm_read call. NULL here means caller is
* not interested in the information. */ long physical_frames; /* Actual physical number of frames that were
* read into the buffer. NULL here means caller
* is not interested in the information. */ unionSpecRead specific; /* specific readbacks for audio or video. */ } mediaSpecRead_t;
/*
* Constants */ #define MFM_CURRENT_ POS -1 /* Read or write at current pos */
#define MFM_OPEN_READ 0x00000000 /* Open media file read-only */
#define MFM_OPEN_WRITE 0x00000001 /* Open media file for writes */
#define MFM_TEMP 0x00000002 /* Temporary file */
#define MFM_ FORCE_ CREATE 0x00000004 /* Force creation of file */ /*
* Error messages */ #define MFM_CANT_CREATE_DBASE_ FILE (0x0000 | ISTAT_MFM) #define MFM_CANT_OPEN_DBASE_FILE (0x0001 | ISTAT_ MFM) #define MFM_CANT_READ_DBASE_FROM_DISK (0x0002 | ISTAT_MFM) #define MFM_INTERNAL_MFM (0x0003 | ISTAT_MFM) #define MFM_NONUNIQUE_TAPENAME_TAPEUID (0X0004 | ISTAT_MFM) #define MFM_DATA_BUFFER_NULL_ON_TEMPFILE (0x0005 | ISTAT_ MFM) #define MFM_WRONG_FRAME_RATE (0x0006 | ISTAT_ MFM) #define MFM_UNKNOWN_FILE_TΥPE (0x0007 | ISTAT_ MFM) #define MFM_BAD_FILE_HANDLE (0x0008 | ISTAT_MFM) #define MFM_ UNKNOWN_MEDIA_TYPE (0x0009 | ISTAT_ MFM) #define MFM_MULTIPLE_WRITERS (0x000A | ISTAT_ MFM) #define MFM_FILE_ NOT_OPEN_WRITE (0x000B | ISTAT_ MFM) #define MFM_CANT_WRITE_ABRIDGED (0x000C | ISTAT_MFM) #define MFM_CLOSE_VIDEO_PARENT (0X000D | ISTAT_ MFM) #define MFM_DELETE_OPEN_FILE_ ATTEMPT (0x000E | ISTAT_MFM) #define MFM_ NOT_ VIDEO_MEDIA (0x000F | ISTAT_ MFM)
#define MFM_VIDEO_SLIDE_WRITE_1 (0x0010 | ISTAT_MFM)
#define MFM_NO_FILM_DATA (0x0011 | ISTAT_MFM)
#define MFM_INVALID_AUDIO_CREATE_DATA (0x0012 | ISTAT_MFM)
#define MFM_NOT_AUDIO_MEDIA (0x0013 | ISTAT_MFM)
#define MFM_FILE_OPEN_FOR_WRITE (0x0014 | ISTAT_MFM)
#define MFM_FILE_NOT_OPEN_READ (0x0015 | ISTAT_MFM)
#define MFM_DELETED_EMPTY_MFILE (0X0016 | ISTAT_MFM)
#define MFM_UNKNOWN_MEDIA_VOLUME (0X0017 | ISTAT_MFM)
#define MFM_NO_MEDIA (0x0018 | ISTAT_MFM)
#define MFM_GIST_TO_CRUX_WRITE (0x0019 | ISTAT_MFM)
#define MFM_DELETE_BLANK (0x001A | ISTAT_MFM)
#define MFM_CALL_MFM_DELETE (0x001B | ISTAT_MFM)
#define MFM_WRITESPEC_CLOSE (0x001C | ISTAT_MFM)
#define MFM_UNUSED (0x001D | ISTAT_MFM)
#define MFM_BAD_MEDIA_VOLUME (0x001E | ISTAT_MFM)
/*
* Externally callable routines */
/************************************************************
* NAME: mfm_inlt *
* Purpose: Initialize the media file manager. */ void mfm_init( istat_t *istat);
/*************************************************
* NAME: mfm_create
*
* Purpose: create a media file. */ MFM_GIST mfm_create ( Boolean diskfile, istat_t *istat);
/*************************************************
* NAME: mfm_open (mfm_open LIVES AGAIN, -reber 9/6/89)
* * Purpose: Given a file handle, preform an AvOpen on the file for read.
* * If you expect to write the media file with this handle, then you are * doing something wrong. It is policy that only converter routines inside * MFM write media files that already exist. The only way you can write a * media file is if you create it. Even then, once you mfm_flush that file, you can never write it again.
*/
MFM_GIST mfm_open( MFM_CRUX file, istat_t *istat);
/****************************************************
* NAME: mfm_handle (alias mfm_open, reber killed it. 8/19/89)
*
* Purpose: Given tapeuid and timecode range, hand back a handle.
* DOES NOT OPEN THE FILE.
*
* If you expect to write the media file with this handle, then you are
* doing something wrong. It is policy that only converter routines inside
* MFM write media files that already exist. The only way you can write a * media file is if you create it. Even then, once you mfm_flush that file,
* you can never write it again.
*/
MFM_CRUX mfm_handle( sourceuld_t tapeUID, Mchannel_t channel, Mtype_t Mtype, timecode_t start_tima, timecode_t end_time, istat
/*************************************************
* NAME: mfm _read
*
* Purpose: Given a file handle, and a frame_number , read the * frames requested, and return a pointer. */ char *mfm_read( MFM_GIST gist, long frame_num, char *buf, long bufsize, long frames_wanted, mediaSpecRead_t *readspec, Boolear /* MEM_GIST gist; register long frame_num; frame number representing frame to begin reading data from. An offset from timecode 00:00:00:00. char *buf; char pointer to read data into long bufsize; available storage on the pointer in bytes long frames_wanted; the number of units to read mediaSpecRead_t *readspec; specifics to fill in on the read
Boolean *repeatable; istat_t *istat; */ /***********************************************************************************************************
* NAME: mfm_write
*
* Purpose: Given a file handle, and a data pointer, * write COUNT bytes into the file.
*/ void mfm_write( MFM_GIST file, char *data, long count, istat_t *istat);
/*****************************************************************************
* NAME: mfm_close
* Purpose: Close a media file. */
MFM_CRUX mfm_close( MFM_GIST file, mediaSpecWrite_ptr writespec, istat_t *istat);
/************************************************************************************************
* NAME: mfm_delete
* Purpose: Delete a media file. */ void mfm_ delete ( MFM_ CRUX file, istat_ t *istat); /********************************************************************************************
* NAME: mfm_forgetit
*
* Purpose: Delete a media file, that is open for write, but write is not complete. */ void mfm_forgetit ( MFM_GIST gist, istat_t *istat);
/************************************************
* NAME: mfm_quit
*
* Purpose: Quitting application. */ void mfm_quit( istat_t *istat);
/************************************************
* NAME: mfm_get_video_framasize
*
* Purpose: Returns the size of a media frame (height & width).
*/ void mfm_get_video_frame_size ( MFM_CRUX file, short *height, short *width, istat_t *istat);
/********************************************************
* NAME: mfm_get_start_time
*
* Purpose: Returns the starting time code of the media file.
* Zero for slides. Actual time code for abridged. */ timecode_t mfm_get_start_time( MFM_CRUX file, istat_t *istat);
/************************************************
* NAME: mfm_get_end_time
*
* Purpose: Returns the ending time code of the media file.
* Calculated for slides. Actual time code for abridged. */ timecode_t mfm_get_end_time ( MFM_CRUX file, istat_t *istat);
/************************************************
* NAME: mfm_get_frame_rate
*
* Purpose: Returns the preferred display rate (in frames/sec)
* for this media file. It is the rate at which it was recorded.
* This rate is independent of the capture ratio.
*/ long mfm_get_frame rate ( MFM_CRUX file, istat_t *istat); /*********************************************************
* NAME: mfm_get_capture_rate
*
* Purpose: Returns the data capture rate (in frames/sec)
* for this media file.
* This rate is a floating point number and is used
* primarily for informational purposes.
* For example, if you start with a video that has a frame rate of 30 frames/sec
* and capture 1 in 4 frames; the effective capture rate is 17.5 frames/sec. */ float mfm_get_capture_rate ( MFM_CRUX file, istat_t *istat);
/****************************************************
* NAME: mfm_get_sourceuld
* * Purpose: return the source uid the media material was known to be derived from. *
*/ void mfm_get_sourceuid( MFM_CRUX file, sourceuid_t *uid, istat_t *istat);
/******************************************************
* NAME: mfm_get_filmkey
*
* Purpose: return the film key data from a media file if any exists.
*/ Boolean mfm_get_filmkey ( MFM_CRUX file, key_t *key, istat_t *istat);
/**************************************************
* NAME: mfm_get_CRUX_from_GIST
*
* Purpose: Return the associated audio mode for this media file. */
MFM_CRUX mfm_get_CRUX_from_GIST( MFM_GIST gist, istat_t *istat);
/***************************************************
* NAME: mfm_get_blank
*
* Purpose: Get the crux id associated with the blank version of the Mtype */
MFM_CRUX mfm_get_blank ( Mtype_t Mtype, istat_t *istat);
/*****************************************************
* NAME: mfm_get_volid
*
* Purpose: Get the volume reference number that the media file is located on. */ void mfm_get_volid( MFM_GIST gist, short *volid, istat_t *istat);
/************************************************
* NAME: mfm_preallocate
*
* Purpose: Tell mfm to preallocate the file tied to this gist to the specified size. */ void mfm_preallocate( MFM_GIST gist, u_long size, istat_t *istat);
#endif MFM _H
mfm_pvt .h huesday, December 5, 1989 10 : 58 AM
/*
* $Log: Engineering:PVCS:Sources:mfm_pvt.h_v $
*
* Rev 1.17 02 Dec 1989 16:39:34 SJR
* tried to more correctly handle abridged and converted file
*
* mfm_pvt.h - Media file manager PRIVATE definitions
*
* Copyright (c) 1988 Avid Technology Inc. */
#include "Soundlntf.h" #include "linklist.h"
/*
* There are some assumptions and rules that were established at time of this writing that guide the use
* of these file types and media formats. They are stated in the table below. Changes to this table
* will undoubtedly cause the code to fail. Hey, what's life without some risk...
* Welcome to Software Engineering.
* * FULL ABRIDGED SLIDE TEMPORARY * * Can I abridge the file? Yes NO NO NO * * Make a slide file from? NO NO NO NO * Slide files can only be created * * via mfm_create. * * Can a temporary exist as? Yes NO Yes HUH? * * Does a disk format exist for? Yes Yes Yes NO * * THOUGHTS * * Someone is going to have to manage slidenames and UIDs. It may be the case that the media file * manager should maintain a table of these, but I suspect that the tapeman will end up doing it. This * not really make sense though, since the tape manager is managing tapes, and slides have nothing to * do with tapes.... Or do they.... leaders??? trailers??? Hmmm ... * * This concludes the tutorial on media file structures. Any other information is left to be determined * by the reader. The author disavows any knowledge of this file whatsoever. All structures so far * discussed are strictly fictional, any slmiliarlties to other structures, past, present or future is * purely coincidental . No warranties are directly or indirectly implied or supplied. 8 /15/89 -reber
* */
#define MFM_MAJOR_VERSION 2 #define MFM_MINOR _VERSION 0 extern listID mfm_ files; /* Comprehensive Media files database handle */ extern threadlD mfm_disk; /* Sorted order of all disk resident media files */ enum (MM35_FILM, MM16_FILM, MM70_FILM, MM8_FILM, SUPER8_FILM); typedef long film_format_t; typedef struct { key_t starting_key; /* Key code for film */ short edge_offset; /* ??? film only */ film_format_t format; /* format of film source */ short edgenumbers_per_foot; } film_data_t; typedef struct
{ long bytes_per_sample; /* Bytes per sample */ long number_of_samples; /* Number of audio samples in file */ long samples_per_second; /* samples/sec */ long video_frames; /* Number of associated video frames */
AudicMode audio_mode; /* Audio mode as described in Soundlntf .h */
} audio_mf_t,
* audio_mf_ptr; typedef struct
{ short height; short width; /*************************** The following fields are not meaningful for a SLIDE file *************/ capture_data_t capture; /* capture information */
Boolean film_ mode; /* indicates validity of data in film_data_t */ film_data_t film; /* data specific for media derived from film */ /*************************** The previous fields are not meaningful for a SLIDE file *************/
} video_cm8 _t, * video_cm8 _ptr;
typedef struct { long pad(32); } media_type_pad_t;
#if ((sizeof (video_cm8_t) > sizeof (media_type_pad_t)) || (sizeof (audio_mf_t) > sizeof (media_type_pad_t)))
ERROR! You have increased size of media type fields to beyond pad size, #endif typedef union { vldeo_cm8_t video_cm8; /* video 8 bit color */ audio_mf_t audio; /* audio */ media_type_pad_t pad; /* padding for media type formats */ } media_t; typedef struct { long date; /* calendar data of creation */ char version[32]; /* Avid application version number */ char name (32] ; /* Avid application name */ } tag_data_t;
typedef struct
{ long magic; /* Base magic */ long headeroffset; /* offset from top of file to the header data. A constant for right now. this provides for extensibility in file structure in the future.
*/ long dataoffset; /* offset from top of file to the media data. Provides for extensibility in the future, and being able to handle data written by pre 2.0 versions of mfm.
* / long major; long minor; /* AVID revision number */
Mchannel_t channel; /* physical source channel number */
Mtype_t Mtype; /* Media Type */
Ftype_t Ftype; /* File Type */ name_t name; /* source name of media. IE: tapename */ sourceuid_t UID; /* unique identification number */ tag_data_t createdata; /* Data associated with this file's creation */ timecode_t start_tlme; /* starting time code of the media. */ tlmecode_t end_time; /* ending time code of the media */ long abridge_frame; /* frame number used for single-frame abridged files */ /* field is only valid when the Ftype is ABRIDGE */ media_t specific; /* specific media data according to media type */
} mfm_base_t,
* mfm_base_ptr,
**mfm_base_hdl;
typedef struct { long pad [128];
} mfm_base_pad_t,
* mfm_base_pad_ptr,
**mfm_base_pad_hdl;
#if( sizeof (mfm_base_pad_t) < sizeof ( mfm_base_t))
ERROR. mfm_base_t has grown to big. Either reduce the size of the mfm_base_t struct or make addition to mfmformat.c to reformat media files. If mfm_base_t remains at the current size then it will be overlapping the media data. reber 11/06/89 #endif
/* dbase_file_t is the header present on the disk resident file, that identifies the database from
* one run to the next. It is an actual dump of the database
* at application quit time. This is read in on application startup as an initial point for
* the database. Volume ID changes are then noted and appropriate action taken on the
* database. New media files opened and logged, or media files deleted from the database
* for those volumes that went offline. */ typedef struct
{ long magic; /* magic number id for media database file */ long major; long minor; /* Avid revision number */ long timeofcreation; /* boot time for which this file is associated. */ long noVolIDs; /* number of Volume IDs(OfflineVoldata_t) following this. */ long noentrys; /* number of media file headers in this file. */ } dbase_file_t;
mfm.c
Tuesday, December 5, 1989 10:50 AM
/* * Module Name: mfm.c * * Module Description: Main source file for media file system * * Revision History: * * $Log: Engineering:PVCS:Sources:mfm.c_v $ * * Rev 1.76 05 Dec 1989 09:43:08 SJR * handle file size setting and disk full conditions * * Rev 1.18 12 Oct 1989 09:53:52 SJR * Rewrite of mfm to support dynamic reference of media files by * sourcename and timecode range rather that direct pathnames and * filename. * * Rev 1.17 10 Oct 1989 15:27:00 SJR * added mfm_set_tapename as precursor to new mfm. * * bedell 10-mar-88 Original Creation *
* /------------------------------------------------------------------------------------------- -------------\ * l The following programs are the sole property of Avid Technology, Inc. l * l and contain its proprietary and confidential information. l * l Copyright © 1989, Avid Technology l * /------------------------------------------------------------------------------------------- -------------\
*/
#include <strings.h>
#include "avid base.h"
#include "istat.h"
#include "journal.h"
#include "disk_io.h"
#include "tools.h"
#include "media_os.h"
#include "host_rtn.h"
#include "env.h"
#include "memrtns.h"
#include "Soundintf.h"
#include "linklist.h"
#include "version_number.h"
#include "sourceman.h"
#include "CUtils.h"
#include "mfm.h"
#include "mfm_pvt.h"
#include "toolman.h"
#include "Macutils.h"
#include "jrutils.h"
#include "cluster.h"
#include "mfmformat.h"
#include "DebugWindow.h"
#include "uid.h"
/***** constants *****/
#define MFILES "MFlles" /* expected folder name for media file storage */
#define TEMP_SOURCENAME AVID_INTERNAL /* source name returned on source name queries on temp files */
#define TEMPINCREMENT 1024 /* byte increment for space in temporary files */
#define MEDIAFILEDBASE "Media Database"
#define MEDIAHEADER "MedlaHDR"
#define MEDIAVOLUMES "MedlaVOL"
#define MEDIAENTRIES "MEMentry"
#define BLACK_UIDHI 0xA199B9A9 /* uids for Black and Silence */
#define BLACK_UIDLO 0x00166C6E
#define SILENCE_UIDHI 0xA199BA9F
#define SILENCE_UIDLO 0x0016A635
/***** External variables *****/ /***** Global variables *****/ listID mfm_files = NULL; /* Comprehensive Media files database handle */ threadlD mfm_disk = NULL; /* Sorted order of all disk resident media files */ /***** Static Variables *****/ static threadlD mfm_hndl = NULL; /* All disk resident media files callers have handles too static threadlD mfm_cach = NULL; /* Thread used in mfm_hndl cache management */ static threadlD mfm_volumes = NULL; /* Media files sorted by volume number */ static cluster_t *dbasefile; /* Cluster pointer for media file database file */ static char *media_volume = NULL; /* enviroment variable indicating volume to write media to static MFM_CRUX BLACK = NULL; /* Crux to video black */ static MFM_CRUX SILENCE = NULL; /* Crux to audio silence */ static long HNDL_CACHEsize = 256; /* mfm_hndl cache size */ static Boolean double_cache = FALSE; /* indicates to double cache */
static env_variable_t mfm_environment [ ] = {
{ "MEDIA_VOLUME",env_string_f, (void *)&media_volume, 0,
"Volume to store new media files on" },
{ "DOUBLE_MEDIA_CACHE",env_boolean_f, (void * ) &double_cache, 0,
"Indication to double media cache" },
{ 0, env_none_f, NULL, 0, 0 }
}; static void mfm_journal( mfm_base_t base, char *journal_buf, istat_t *istat); static void MfilesToDatabaseT short Volld, long dirid, Istat_t *istat); static void read_dbase( short volid, long dirid, istat_t *istat); static void write_dbase( istat_t *istat); static void write_dbaseOnDrive ( short volid, long dirid, istat_t *istat); static Boolean CheckDataBase( short volid, long dirid, istat_t *istat); static MFMCrux_hdl open_flle( long Dirid, short Volld, char *filename, istat_t *istat); static char *read_blank( MFM_GIST gist, char *buf, long bufsize, long frames_wanted, Boolean *repeatable, mediaSpecRead_ static Boolean handle_previous_formats ( short Volld, long Dirid, name_t filename, istat_t *istat); static void reducecache( istat_t *istat); static MFM_CRUX siftFORhandle ( sourceuid_t tapeUID, Mchannel_t channel, Mtype_t Mtype, timecode_t start_time, timacode_t
/************************************************
*
* Media File Manager error massage table.
*/ static char *mfm_errmsg[] =
{
"Unable to create database file", "Unable to open database file", "Unable to read database from disk file", "Internal Media File Manager Error", "Tapename and tapeUID are not unique", "Data area of file header is NULL on a mfm_read", "Mfm_handle fulfilled request with media of another frame rate",
"Attempt to open unknown file type",
"Bad Madia File Handle",
"Media Type specified does not exist",
"Attempt at multiple writers on same media file",
"Attempt to write media file not yet open for write",
"Cant directly write abridged files",
"Must close parent video file before closing audio file",
"Attempt to delete media file which is open",
"Media file queried is not video media as expected",
"Attempt to write a video slide file for more than one frame",
"No film data available in the media file",
"Data in audio specification on mfm_create is invalid",
"Media file queried is not audio media as expected",
"Attempt to obtain data on a file that is open for write",
"Attempt to read a file that is not yet open",
"Empty media file encountered - DELETED",
"Setting of enviroment variable MEDIAVOLUME is nonexistant disk",
"Cannot fulfill media request, nothing online of the sort",
"Cannot obtain a CRUX id on a file being written",
"Cannot delete media files for BLACK or SILENCE",
"Must call mfm_delete to delete media file that is already written",
"Writespec must be defined on closing a file open for write",
"Unused Status",
"Current setting of MEDIA_VOLUME is incorrect",
}; errmsg_t mfm_errors = { mfm_errmsg, (sizeof (mfm_errmsg) /sizeof (char *)), "MFM" };
/************************************************
* NAME: mfm_init
*
* Purpose: Initialize the media file manager. */ void mfm_init( istat) istat_t *istat;
{ dbase_file_t data; long seconds; long ticks;
MFMCrux_t file; uid_t uid; add_error_subsystem( ISTAT_MFM, &mfm_errors) ; ResetErr ( istat) ;
env_add( mfm_environment, "MFM", istat); ChkErrGoto( istat, leave);
/* create the database to place media files in */ mfm_files = DLCreate( sizeof (MFM_rux_t), istat); ChkErrGoto( istat, leave); mfm_disk = DLCreateThread( mfm_flles, FALSE, FALSE, istat); ChkErrGoto( istat, leave);
DLSetThread( mfm_files, mfm_disk, istat); ChkErrGoto( istat, leave);
DLAddKey( mfm files, unique_id, CFIELD_OFFSET( header.disk.UID, &file), TRUE, istat); ChkErrGoto( istat, leave);
DlAddKey( mfm_files, longint, CFIELD_OFFSET ( Mtype, &file), TRUE, istat); ChkErrGoto( istat, leave);
DLAddKey( mfm_files, longint, CFIELD_OFFSET( channel, &file), TRUE, istat); ChkErrGoto( istat, leave);
DLAddKey( mfm files, timecode, CFIELD_OFFSET( header.disk.start_time, &file), TRUE, istat); ChkErrGoto( istat, leave)
DLAddKey( mfm_files, timecode, CFIELD_OFFSET( header.disk.end_time, (file), TRUE, istat); ChkErrGoto( istat, leave);
DLSort( mfm_files, Quicksort, istat); ChkErrGoto( istat, leave); mfm_hndl = DLCreateThread( mfm_files, FALSE, FALSE, istat); ChkErrGoto( istat, leave);
DLSetThread( mfm_files, mfm_hndl, istat); ChkErrGoto( istat, leave);
DLAddKey( mfm_files, unique_id, CFIELD_OFFSET ( header.disk.UID, &file), TRUE, istat); ChkErrGoto( istat, leave);
DlAddKey( mfm_files, longint, CFIELD_OFFSET( Mtype, &file), TRUE, istat); ChkErrGoto( istat, leave);
DlAddKey( mfm_files, longint, CFIELD_OFFSET( channel, &file), TRUE, istat); ChkErrGoto( istat, leave);
DLAddKey( mfm_files, timecode, CFIELO_OFFSET( header.disk.start_time, &file), TRUE, istat); ChkErrGoto( istat, leave);
DLAddKey( mfm_files, timecode, CFIEID_OFFSET( header.disk.end_time, &file), TRUE, istat); ChkErrGoto( istat, leave);
DLSort( mfm_files, Quicksort, istat); ChkErrGoto( istat, leave);
mfm_cach = DLCreateThread( mfm_files, FALSE, FALSE, istat); ChkErrGoto( istat, leave);
DLSetThread( mfm_files, mfm_cach, istat); ChkErrGoto( istat, leave);
DlAddKey( mfm_files, longint, CFIELD_OFFSET ( header.disk.touch_ticks, &file), TRUE, istat); ChkErrGoto( istat, leave);
DLSort( mfm_files, Quicksort, istat); ChkErrGoto( istat, leave);
/* thread of files sorted by Volume ID to help deal with volume changes */ mfm_volurnes = DLCreateThread( mfm_files, FALSE, FALSE, istat); ChkErrGoto( istat, leave);
DLSetThread( mfm_files, mfm_vol umes, istat); ChkErrGoto( istat, leave);
DlAddKey( mfm_files, shortint, CFIELD_OFFSET( header.disk.VolID, &file), TRUE, istat); ChkErrGoto( istat, leave); DlAddKey( mfm_files, unique_id, CFIELD_OFFSET( header.disk.UID, &file), TRUE, istat); ChkErrGoto( istat, leave); DLSort( mfm_files, Quicksort, istat); ChkErrGoto( istat, leave);
DLSetThread( mfm_files, MAINTHREAD, istat); ChkErrGoto( istat, leave);
/* scan all online volumes and either scan the MFILES folder or read in the * Media Database if its there and current. */ {
short sysvref; drive_info_t drive_info; vol_info_t vol_info; void *driveHandle; long dirid;
GetVRefNum (SysMap, &sysvref);
/* loop through all the drives reading in MediaDatabases first to build up the SourceManager * table, before trying to scan in other media files
*/ driveHandle = AVFirstDrive( &drive_info); do { if( !drive_info.ejectable) { AvGetVolInfo( drlve_info.driveNum, &vol_info, istat); if( *istat == ISTAT_OK && vol_info.deviceType != VOL_UNMOUNTED
&& vol_info.deviceTyρe == VOL_SCSI
&& vol_info.vRefNum != sysvref) { dirid = 0;
/* if this volume has folder MFILES, then we want to write{ a dbase there. */ if( AVCheckDirExists( MFILES, vol_info.vRefNum, &dirid, FALSE, istat) && *istat == ISTAT_OK)
{
/* if the database is there, and current (the mod date on the folder does
* not exceed the write date on the database) */
/* hack hack CheckForFolder should have a better way of returning the
* type of object loacted - file/folder.
* Also, this code should be more symetric to the same drive loop below.
* This CheckForFolder call is NOT included below, and it should be to insure
* balanced processing of the databases. */ if( FlleExists( vol_info.vRefNum, dirid, MEDIAFILEDBASE) ) { if( CheckDataBase ( vol_info.vRefNum, dirid, istat) && *istat == ISTAT_OK)
{ readdbaset vol_info.vRefNum, dirid, istat);
ChkErrGoto( istat, leave);
}
else {
/* read in the source manager info, even though the file is old.
* this else clause and the CheckForFolder call can be deleted at
* the point in time we believe there to be no more 10.11Beta media
* files in the world that could be dragged into a MFiles folder.
* reber 11/10/89 */
/* Attempt to open the database file, lets see where we were last... */ dbasefile = LoadClustert vol_info.vRefNum, dirid, MEDIAFILEDBASE, CLUSTER_MFM, istat);
ChkErrGoto( istat, leave);
/* tell source manager to read in his stuff */ SMReadNTable ( dbasefile, istat); ChkErrGoto( istat, leave); CloseCluster( dbasefile, istat); ChkErrGoto( istat, leave); } } } else ChkErrGoto( istat, leave); } else ChkErrGoto( istat, leave); } while( AVNextDrive( &driveHandle, &drive_info)); driveHandle = AVFirstDrive( &drive_info); do { lf( !drive_info.ejectable) {
AvGetVolInfo( drive_info.driveNum, &vol_info, istat); if( *istat == ISTAT_OK && vol_info.deviceType != VOL_UNMOUNTED
&& vol_info.deviceType == VOL SCSI
&& vol_info.vRefNum != sysvref) dirid = 0;
/* if this volume has folder MFILES, then we want to write a dbase there. */ if( AVCheckDirExlsts( MFILES, vol_info.vRefNum, &dirid, FALSE, istat) && *istat == ISTAT_OK) i
/* if the database is there, and current (the mod date on the folder does
* not exceed the write date on the database)
*/ if( (!CheckDataBase( vol_info.vRefNum, dirid, istat)) && *istat == ISTAT_OK)
{
/* database does not exist, or its out of date.
* Rescan the drive
*/ MfilesToDatabaset vol_info.vRefNum, dirid, istat); ChkErrGoto( istat, leave); } } } else ChkErrGoto( istat, leave); } } while ( AVNextDrive& &driveHandle, &drlve_info));
leave:
/* set up Black and Silence */
BLACK = (MFM_CRUX) &BLACK; SILENCE = (MEM_CRUX)&SILENCE;
/* add black and silence in the sourcemanager's table */ uid.high = BLACK_UIDHI; uid.low = BLACK_UIDLO; SMAddName( =BLACK", &uid, istat); ChkErrGoto( istat, leave); uid.high = SILENCE_UIDHI; uid.low = SIUENCE_UIDLO;
SMAddName( "SILENCE", &uid, istat);
ChkErrGoto( istat, leave); return;
}
/ ************** ******************************************************** *
* NAME: mfm_create
*
* Purpose: create a media file. */ MFM_GIST mfm_create ( diskfile, istat)
Boolean diskfile; /* unique tape id to separate duplicate tape names */ istat_t *istat;
{
MFMCrux_t mfmfile;
Boolean temp = FALSE; recordlD record;
MFMGist_hdl gist = NULL; long namecount = 1; istat_t local;
ResetErr( istat); mfmfile.magic = MFM_CRUX_MAGIC; mfmfile .permanent = diskfile; mfmfile.file_use = -1; /* one writer */ mfmfile.Mtype = still_writing;
if( !diskfile)
{ mfmfile.header.ram.data_limit = 0; mfmfile.header.ram.data_pos = 0; mfmfile.header.ram.data = NULL;
/*
* Link this new file b lock into the quene si open files.
*/ record = DLAddHead( mfm_files , (char *) &mfmfile, istat); } else
{ vol_info_t vol_info; short sysvref; long dirid; short MVID; /* media volume volume reference ID */
OSErr err; char *volname; short i; mfm_base_pad_t base; /* this just used as a spacer on the disk. Not filled * with anything meaningful in this routine. */ mfm_base_ptr BS;
volname = env_read_strlng( "MEDIA_VOLUME", istat); ChkErrGoto( istat, leave); err = GetVRefUseVolName( &MVID, volname); if( err != noErr) LogErrGoto( istat, MFM_UNKNOWN_MEDIA_VOLUME, leave);
GetVRefNum (SysMap, &sysvref);
AvGetVolInfo( MVID, &vol_info, istat); if( *istat == ISTAT_OK && vol_info.deviceType != VOL_UNMOUNTED
&& vol_info.deviceType == VOL_SCSI
&& vol_info.vRefNum != sysvref)
{ dirid = 0;
/* Check for folder MFILES, and if its not there then create it */
AVCheckDirExistst MFILES, vol_info.vRefNum, &dirid, TRUE, istat);
ChkErrGoto( istat, leave);
} else if( *istat != ISTAT_OK) ChkErrGoto( istat, leave); else LogErrGoto( istat, MFM_BAD_MEDIA_VOLUME, leave); mfmfile.header.disk.VolID = MVID; mfmfile.header.disk.Dirid = dirid;
MakeNilUid( &mfmfile.header.disk.UID); mfmfile.header.disk.dataoffset = sizeof ( mfm_base_pad_t);
do { sprintf( mfmfile.header.disk.filename, "creating&ld", namecount);
/* disk mac */ mfmfile.header.disk.raw_file = AVCreateFile( MVID, dirid, mfmfile.header.disk.filename, OPEN_WRITE, MEDIA_FT, &loc namecount++;
} while ( local == ISTAT_FILE_EXISTS); if( local != ISTAT_OK) LogErrGoto( istat, local, leave);
/* write in zeros for the file header. This makes the file length correct
* for mfm_write calls. Mfm_write always appends data onto the end of the file. * Writing in zeros here seemed more economical than constantly checking for a zero * length file in mfm_write to determine the first file write attempt and put the * data in the right place, reber 10/02/89 *
* added the setting of the magic and Mtype field in order to insure detection of files
* that were being digitized when we crashed on the next start up. Need fairly absolute
* identification to delete files on. reber 11/21/89 jcopyt 0, (char *) tbase, sizeof ( mfm_base_pad_t));
AVWriteFile( mfmflie.header.disk. raw_flle, 0, sizeof ( mfm_base_pad_t), &base, istat):
ChkErrGoto( istat, leave);
BS = (mfm_base_ptr) &base;
BS→magic = FILE_MAGIC;
BS→Mtype = still_writing;
AVWriteFile( mfmfile.header.disk.raw_flie, 0, sizeof ( mfm_base_t), BS, istat);
ChkErrGoto( istat, leave);
/*
* Link this new file block into the queue of open files.
*/ record = DLAddHead( mfm_files, (char *)&mfmfile, istat); ChkErrGoto( istat, leave); DLAddToThread( mfm_files, mfm_disk, record, istat); ChkErrGoto( istat, leave); DLAddToThread( mfm_files, mfm_volumes, record, istat); ChkErrGoto( istat, leave); }
/* ok now get the MFM_GIST id to hand back to the client */ gist = (MFMGist_hdl)AvNewHandle( sizeof (MFMGist_t), istat); ChkErrGoto( istat, leave); (*gist)→magic = MFM_GIST_MAGIC; (*gist)→cruxld = (MFM CRUX) record; leave: return( (MFM_GIST) gist) ; } /**************************************************************************
* NAME: mfm_open (mfm_open LIVES AGAIN, -reber 9/6/89)
*
* Purpose: Given a file handle, preform an AvOpen on the file for read.
*
* If you expect to write the media file with this handle, then you are * doing something wrong. It is policy that only converter routines inside * MEM write media files that already exist. The only way you can write a * madia file is if you create it. Even then, once you mfm_close that file, * you can never write it again. */
MEM_GIST mfm_open( file, istat)
MEM_CRUX file; istat_t *istat;
{
MFMCrux_ptr FL;
MFMGist_hdl gist = 0; istat_t local; ResetErr( istat); if( file == NULL) LogErrGototistat, MFM_BAD_FILE_HANDLE, leave); if ( file == BLACK || file == SILENCE) return ( (MFM_GIST) file) ; DLGet( mfm_files, (char **)&FL, (recordlD *) (&file), FALSE, FALSE, istat); ChkErrGoto( istat, leave); if( FL→magic != MEM_CRUX_MAGIC) LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave); if( FL→file_use == -1) LogErrGoto( istat, MFM_FILE_OPEN_FOR_WRITE, leave); if( FL→permanent)
{
/* The file is NOT open for write OR read. */ if( FL→file_use == 0) {
/* the file is not open yet, OPEN it. */
/* disk mac */
FL→header.disk.raw_file = (RAW_FILE *)AVOpenFile( FL→header.disk.VolID, FL→header.disk.Dirid,
FL→header.disk.filename, OPEN_READ, &local); if( local == ISTAT_FILE_NOT_FOUND) {
/*We have a mildly tragic case. Apparently our database was incorrect. * We handed out an MFM_CRUX to what we thought was an existing file. * A number of things could have happened. The user used the Finder while * the application was running and moved some media files around. Or, maybe * the database itself is actually wrong somehow. To correct the situation * we are going to pass back BLACK or SILENCE to the caller. An istat will * be sent to indicate the problem. */ /* FCS FCS need to handle rescanning of the drive somehow. Cant do it here * since mfm_open is time critical. Mfm has no idle routine that is called * so that rescanning could be done then. */
LogErr( istat, MEM_NO_MEDIA) ; switch( FL→Mtype) { case video_cm8:
{ return( (MFM_GIST) BLACK) ; break;
} case audio:
{ return( (MFM_GIST) SILENCE) ; break; } } } else if( local != ISTAT_OK) LogErrGoto( istat, local, leave); }
FL→header.disk.touch_ticks = TickCount(); }
FL→file_use++;
/* ok now get the MEM _GIST id to hand back to the client */ gist = (MFMGist_hdl)AvNewHandle ( sizeof (MFMGist_t) , istat) ; ChkErrGoto( istat, leave) ; (*gist) →magic = MEM_GIST_MAGIC; (*gist) → cruxid = file;
leave: return( (MFM_GIST)gist); }
/************************************************
* NAME: mfm_handle
*
* Purpose: Given tapeuid and timecode range, hand back a handle.
* DOES NOT OPEN THE FILE.
*
* If you expect to write the media file with this handle, then you are
* doing something wrong. It is policy that only converter routines inside
* MFM write media files that already exist. The only way you can write a
* media file is if you create it. Even then, once you mfm_close that file,
* you can never write it again. */
MFM_CRUX mfm_handle( tapeUID, channel, Mtype, start_time, end_time, istat) sourceuid_t tapeUID; /* tape date UID */
Mchannel_t channel; /* Physical source channel */
Mtype_t Mtype; /* media type you will wish to read. */ timecode_t start_time; /* Minimum start time read */ timecode_t end_time; /* Maximum end time read */ istat_t *istat;
{
MFM_CRUX returnID = NULL;
ResetErr( istat);
/* Temporary files here are not allowed. Only the caller of mfm_create that created the * temporary file will be using the file. Therefore mfm_open need not handle temporaries
returnID = siftFORhandle( tapeUID, channel, Mtype, start_time, end_time, istat) ; ChkErrGoto( istat, leave); if( returnID NULL) {
SMrelatlons_hdl relations; SMrelatlons_ptr RLchaser; long count;
/* call the sourcemanager and see if there is other source related to this one for the
* requested time range. If so, then try searching for media from those.
*/ relations = SMRelated( tapeUID, start_time, en_ time, &count, istat); if( count != 0 && *istat == ISTAT_OK)
{
HLock( relations);
RLchaser = *relations; while ( count !- 0 && returnID == NULL)
{ tlmecode_t start; timecode_t end; start = inc_ti me( RLchaser→uid2_start, diff_time ( RLchaser→uidl_start, start_time) ) ; end_time = inc_time ( RLchaser→uid2_start, diff_time ( RLchaser→uidl_start, end_time) ) ; returnID = siftFORhandle( RLchaser→uid2, channel, Mtype, start_time, end_time, istat) ; ChkErrGoto( istat, leave) ;
} HUnlock( relations) ;
AvDisposeHandle ( (Handle) relations, STD_CHECK) ; }
if( returnID == NULL II *istat == SRM_NO_SUCH_UID)
{ switch ( Mtype) { case video_cm8:
{ returnID = BLACK; break;
} case audio:
{ returnID = SILENCE; break;
}
{ LogErr( istat, MFM_NO_MEDIA) ;
} else
ChkErrGoto( istat, leave); }
DLSetThread( mfm_files, MAINTHREAD, istat); ChkErrGoto( istat, leave); return( (MEM_CRUX) returnID) ; leave: return( NULL); }
/************************************************ * NAME: mfm_read
* Purpose: Given a file handle, and a frame number, read the
* frames requested, and return a pointer. */ char *mfm_read( gist, frame_num, buf, bufsize, frames_wanted, readspec, repeatable, istat) MFM_GIST gist; register long frame_num; /* frame number representing frame to begin reading data from. An * offset from timecode 00:00:00:00. */ char *buf; /* char pointer to read data into */ long bufsize; /* available storage on the pointer in bytes */ long frames_wanted; /* the number of units to read */ mediaSpecRead_t *readspec; /* specifics to fill in on the read */ Boolean *repeatable; istat_t *istat;
{
MFMCrux_ptr foo; register MEMCrux_ρtr FL; ResetErr ( istat) ; if( gist == NULL)
LogErrGototistat, MFM_BAD_FILE_HANDLE, leave); if( gist == BLACK l l gist == SILENCE) returnt read_blank( gist, buf, bufsize, frames_wanted, repeatable, readspec, istat)); if( (*(MFMGist_hdl)gist)→magic != MFM_GIST_MAGIC) LogErrGoto(istat, MFM_BAD_FILE_HANDLE, leave);
DLGet( mfm_flles, (char **)&foo, (recordID *) (& (* (MFMGist_hdl)gist)→cruxid), FALSE, FALSE, istat);
ChkErrGoto( istat, leave);
FL = foo; if( FL→magic != MFM_CRUX_MAGIC)
LogErrGoto( istat, MEM_BAD_FILE_HANDLE, leave);
/* check file open at all, and if its open for write. */ if( FL→file use == 0) LogErrGoto( istat, MFM_FILE_NOT_OPEN_READ, leave); if( FL→file_use == -1) LogErrGoto( istat, MEM_FILE_OPEN_FOR_WRITE, leave); switch( FL→Mtype) { case video _cm8: { read_video_cm8( FL, &buf, bufsize, frame_num, frames_wanted, repeatable, readspec, istat); ChkErrGoto( istat, leave); break; } case audio: { read_audio_mf( FL, &buf, bufsize, frame_num, frames_wanted, repeatable, readspec, istat); ChkErrGoto(istat, leave); break; } default:
LogErrGoto( istat, MFM_UNKNOWN_MEDIA_TYPE, leave); } return( buf); leave: return( NULL) ; } /***********************************************************************
* NAME: mfm_write *
* Purpose: Given a file handle, and a data pointer,
* write COUNT bytes into the file.
*/
void mfm_write( gist, data, count, istat)
MEM_GIST gist; char *data; long count; /* amount of data to write in bytes */ istat_t *istat; {
MEMCrux_ptr foo; register MEMCrux_ptr FL; char **newhndl;
ResetErr ( istat) ; if ( gist == NULL I I gist == BLACK l l gist == SILENCE) LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave) ; if ( (* (MFMGist_hdl) gist) →magic != MEM _GIST_MAGIC) LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave) ;
DLGet ( mfm_flles, (char **) &foo, (recordID *) & ( * (MFMGist_hdl) gist) →cruxid, FALSE, FALSE, istat) ; ChkErrGoto( istat, leave) ; FL = foo; if ( FL→magic != MEM_CRUX_MAGIC)
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave) ; if ( !FL→permanent)
{ if( FL→file_use != -1) LogErrGoto( istat, MFM_FILE_NOT_OPEN_WRITE, leave);
/* if write request extends beyond current size allocated for this temp... */ if( FL→header.ram.data_pos + count > FL→header.ram.data_limit) { long morespace;
/* get some more space */ morespace = 0; while ( morespace < (FL→header.ram.data_pos + count)) morespace += TEMPINCREMENT; newhndl = (char **)AvNewHandleClear( morespace, istat); ChkErrGoto( istat, leave);
HLock( newhndl) ;
HLock( FL→header.ram.data);
/* copy in the old data */
BlockMove( (*FL→header.ram.data), ("newhndl), FL→header.ram.data_pos);
HUnlock( FL→header.ram.data);
HUnlock( newhndl);
/* dump the old handle, and tie reference in the file header to the new one */
AvDisposeHandle( (Handle)FL→header.ram.data, istat); ChkErrGoto( istat, leave);
FL→header.ram.data = newhndl; FL→header.ram.data_limit = morespace;
} HLock ( FL→header.ram.data);
BlockMove( data, (*FL→header.ram.data) + FL→header.ram.data_pos, count); FL→header. ram. data _pos += count; HUnlock ( FL→header.ram.data); } else { /* check to see that it is open for write. */ if( FL→file_use != -1) LogErrGoto( istat, MFM_FILE_NOT_OPEN_WRITE, leave); AVWriteFile( FL→header.disk. raw_file, FL→header.disk.raw_file→length, count, data, istat); } leave: return; } /***********************************************
* NAME: mfm_close
*
* Purpose: Close a media file. */
MFM_CRUX mfm_close( gist, writespec, istat)
MEM_GIST gist; mediaSpecWrite_ptr writespec; istat_t *istat;
{ path_t journal_buf;
MFMCrux_ptr FL;
MFM_CRUX crux = 0;
ResetErr ( istat) ;
if( gist == NULL)
LogErrGoto(istat, MFM_BAD_FILE_HANDLE, leave); lf( gist == BLACK || gist == SILENCE) return( (MFM_CRUX) gist) ; if( (*(MFMGist_hdl)gist)→magic != MEM_GIST_MAGIC)
LogErrGotoTistat, MFM_BAD_FILE_HANDLE, leave);
DLGet( mfm_files, (char **)&FL, (recordID *)& (* (MFMGist_hdl)gist)→cruxid, FALSE, FALSE, istat); ChkErrGoto( istat, leave); if( FL→magic != MEM_CRUX_MAGIC)
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave); if( FL→permanent)
{
/* if the file is open for write, then preform write closing. */ if( FL→file_use == -1)
{ mfm_base_hdl base; mfm_base_ptr BS; if( writespec == NULL) LogErrGoto( istat, MFM_WRITESPEC_CLOSE, leave);
FL→channel = writespec→channel;
FL→flle_use = 0;
FL→Mtype = writespec→Mtype;
FL→Ftype = writespec→Ftype;
FL→header.disk.touch_ticks = TickCount();
FL→header.disk.UID = writespec→sourceid; base = ((fm_base_hdl)AvNewHandle( sizeof (mfm_base_t), istat);
ChkErrGoto( istat, leave);
HLock ( base);
BS = *base;
/* set up the base with values stored in main memory header, and normal defaults */
BS→magic = FILE_MAGIC;
BS→headeroffset = CFIEIH_OFFSET( major, BS) ; /* the revision number is technically the
* beginning of the file header.
*/
BS→ dataoffset =sizeof (mfm_base_pad_t);
BS→major = MFM_MAJOR_VERSION;
BS→minor = MFM_MINOR_VERSION;
BS→channel = writespec→channel;
BS→Mtype = writespec→Mtype; BS→Ftype = writespec→Ftype;
SMGetName( writespec→sourceid, BS→name, istat); ChkErrGoto( istat, diskleave); getfilename( BS→name, wrltespec→sourceid, FL→Mtype, FL→channel, FL→header.disk. filename);
AVRenameFile( FL→header.disk.raw file, FL->header.disk.Dirid, FL→header.disk,filename, FALSE, istat); ChkErrGotot istat, diskleave); BS→UID = writespec→sourceid; GetDateTime( &BS→createdata.date); /* calendar data of creation */ strcpyt BS→createdata.version, AVID_REVISION_NUMBER) ; /* Avid application version number */ AVGetApplName ( BS→createdata.name); /* Avid application name */
BS→start_time = writespec→start_time;
FL→header.disk.start_time = BS→start_time; switch ( writespec→Mtype)
{ case video_cm8:
{ long frame_size; close_video_cm8( FL, &BS→specific.video_cm8, writespec, istat); ChkErrGoto( istat, diskleave);
/* this chunk of code should move INSIDE close_video_cm8 */
FL→header.disk,specific.video_cm8.capture.mask = BS→specific.video_cm8.capture.mask;
FL→header.disk.specific.vldao_cm8. capture. rate = BS→specific.video_cm8.capture. rate;
FL→headar.disk,speciflc.vldeo_cm8. capture. start_offset = BS→specific.video_cm8.capture.start_offset;
FL→header.disk.speccfic.video_cm8. capture. length = BS→specific.video_cm8.capture, length;
FL→header.disk.specific.video_cm8. capture.one_bits = BS→specific.video_cm8.capture.one_bits;
FL→header.disk.speciflc.vldeo_cm8.height = BS→specific.video_cm8.height;
FL→header.disk.speciflc.vldeo_cm8.width = BS→specific.vld eo_cm8.width; FL→header.dlsk.speciflc.vldθo_cm8.fllm_data = BS→speclflc.video_cm8.fllm_mode;
frame_size = FL→header.disk.specific.video_cm8.height * FL→header.disk.specific.video_cm8.width;
BS→end_tlme = inc_time( BS→start_time, (FL→header.disk. raw_file→length - FL→headar.disk.dataoffset) frame_size);
} break; case audio:
{
MFMCrux_ptr FLvldeo;
MFMCrux_ptr whocares; recordID saverecord = 0; long video_frames; if( writespec→specific.audio.videofile == NULL)
{
/* close audio call will calculate best guess in this case. */ video_frames = 0;
} else
{
/* get video frame number from the video file. */
/* save the current record. DL routines do not guarantee the dataptr across other DL calls. */ DLGet( mfm_files, (char * *) &whocares, &saverecord, FALSE, FALSE, istat); ChkErrGoto( istat, diskleave);
DLGet( mfm_files, (char **)&FLvideo, (recordID *) &writespec→specific.audio.videofile, FALSE, FALSE, ChkErrGoto( istat, diskleave); if( FLvideo→magic != MFM_CRUX_MAGIC)
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, diskleave); if ( FLvideo→file_use == -1)
LogErrGoto( istat, MFM_CLOSE_VIDEO_PARENT, diskleave); video_frames = diff timet FLvldeo→header.disk.start time, FLvldao→ header.disk.end_time); DLGet ( mfm_files, (char * * ) &FL, (recordID * ) &saverecord, FALSE, FALSE, istat) ;
ChkErrGoto ( istat, diskleave) ; } close_audio_mf ( FL, &BS→specific. audio, writespec, video_frames, istat) ;
ChkErrGoto(istat, diskleave); FL→header.disk.specific.audio.bytes_per_sample = BS→sρecific.audio.bytes_per_samρle;
FL→header.disk.specific.audio. number_of_samples = BS→specific.audio.number_of_samples
FL→header.disk.specific.audio.video_frames = BS→specific.audio.video_frames;
FL→header.disk.specific.audio.audio_mode = BS→specific.audio.audio_mode;
BS→end_time = inc_time( BS→start_time, BS→specific.audio.video_frames); break; } default: break; } AVWriteFile( FL→header.disk. raw_file, 0, sizeof ( mfm_base_t), BS, istat); ChkErrGoto( istat, diskleave);
/* ditto new data needed in database record */
FL→header.disk.start_time = BS→start_time;
FL→header.disk.end time = BS→end_time; if( writespec→trim_file_size != 0)
AVTruncateFile ( FL→header.disk.raw_file, writespec→trim_file_size, istat); else
AVTruncateFile( FL→header.disk. raw_file, FL→header.disk.raw_file→length, istat); ChkErrGoto( istat, leave);
AVCloseFile( FL→header.disk.raw_file, istat);
ChkErrGoto( istat, diskleave);
FL→header.disk. raw_file = NULL; /* paranoid */
/* tell rest of the world a new media file is present */ t mMedlaCreated( (*(MFMGist_hdl)gist)→cruxid, istat);
ChkErrGoto( istat, diskleave); diskleave:
BS→magic = 0;
HUnlock( base);
AvDisposeHandle( (Handle)base, STD_CHECK); }
else
{
/* closing a file open for read */
FL→file_use--; if( FL→file_use == 0)
{
AVCloseFile ( FL→header.disk.raw_file, istat) ; ChkErrGoto( istat, leave) ; } } } else
{
/* closing a temporary file */
/* if the file is open for write, then preform write closing. */ if ( FL→file_use == -1)
{
FL→channel = writespec→channel;
FL→file_use = 0;
FL→Mtype = writespec→Mtype;
FL→Ftype = writespec→Ftype;
FL→header.ram.frame_rate = writespec→frame_rate; switch( wrltespec->Mtype)
{ case video_cm8:
{ close_video_cm8( FL, &FL→header.ram.specific.video_cm8, writespec, istat);
ChkErrGoto( istat, leave); break;
} case audio:
{
MFMCrux_ptr FLvideo;
MFMCrux_ptr whocares; recordID saverecord = 0; long video_frames; video_frames = writespec→specific.audio.video_frames;
close_audio_mf ( FL, &FL→header.ram.specific.audio, writespec, video_frames, istat);
ChkErrGoto (istat, leave); break;
} default: break; } if( writespec→trlm_file_size != 0)
FL→header.ram.data_pos = writespec→trim_file_size;
/* tell rest of the world a new media file is present */ tmMediaCreated( (*(MFMS1st_hdl)gist)→cruxid, istat); ChkErrGoto( istat, leave); } else
{
/* closing a temp file that was open for read */ if( FL→file_use > 0) FL→file_use--;
}
} crux = (*(MEMGlst_hdl)gist)→cruxid; AvDisposeHandle( (Handle)gist, STD_CHECK); leave: return( crux); }
/************************************************
* NAME: mfm_delete
*
* Purpose: Delete a media file. */ void mfm_delete( file, istat) MEM_CRUX file; istat_t *istat;
{
MFMCrux_ptr FL; long filetype;
ResetErr( istat); if( file == NULL)
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave); if( file == BLACK || file == SILENCE)
LogErrGoto( istat, MFM_DELETE_BLANK, leave);
DLGet( mfm_files, (char **)&FL, (recordID *)&file, FALSE, FALSE, istat); ChkErrGoto( istat, leave); if( FL→magic != MFM_CRUX_MAGIC)
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave); if( FL→permanent)
{ lf ( FL→file_use != 0)
LogErrGoto( istat, MFM_DEL ETΕ_OPEN _FILE_ATTEMPT, leave) ;
FL→magic = 0;
AVDeleteFile( FL→header.disk.VolID, FL→header.disk.Dirid, FL→header.disk. filename, istat); ChkErrGoto( istat, leave);
} else
{
FL→magic = 0;
AvDisposeHandle( FL→header.ram.data, istat); ChkErrGoto( istat, leave);
}
DLRemove( mfm_files, (recordID)file, istat);
ChkErrGoto( istat, leave); tmMediaDeleted( file, istat); ChkErrGoto( istat, leave); leave:
return; }
/************************************************
* NAME: mfm_forgetit
*
* Purpose: Delete a media file, that is open for write, but write is not complete.
*/ void mfm_forgetit ( gist, istat) MFM_GIST gist; istat_t *istat;
{
MEMCrux_ptr FL; long filetype;
ResetErr( istat); if( gist == NULL)
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave); if( gist = BLACK || gist == SILENCE)
LogErrGoto( istat, MFM_DELETE_BLANK, leave); if( (*(MFMGist_hdl)gist)→magic != MFM_GIST_MAGIC) LogErrGototistat, MFM_BAD_FILE_HANDLE, leave);
DLGet( mfm_files, (char **)&FL, (recordID *) (& (* (MFMGist_hdl)glst)→cruxid), FALSE, FALSE, istat); ChkErrGoto( istat, leave); lf( FL→magic != MFM_CRUX_MAGIC)
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave); if( FL→file_use != -1) LogErrGoto( Istat, MFM_CALL_MFM_DELETE, leave); lf( FL→permanent)
{
FL→magic = 0;
AVCloseFile ( FL→header.disk.raw_file, Istat) ;
ChkErrGoto( istat, leave) ;
AVDeleteFile ( FL→header.disk.VolID, FL→header.disk.Dirid, FL→header.disk.filename, istat) ;
ChkErrGoto( istat, leave) ;
} else {
FL→magic = 0;
AvDisposeHandle( FL→header.ram.data, istat);
ChkErrGoto( istat, leave);
}
DLRemove( mfm_flies, (recordID) (* (MFMGist_hdl) gist) →cruxid, istat); ChkErrGoto( istat, leave);
(*(MFMGist_hdl)gist)→magic = 0; AvDisposeHandle ( (Handle)gist, istat); leave: return; }
/************************************************
* NAME: mfm_quit
* Purpose: Quitting application. */ void mfm_quit( istat) istat_t *istat; {
DLRemoveThread( mfm_flies, mfm_dlsk, istat); ChkErrGoto( istat, leave); DLRemoveThread( mfm_files, mfm_hndl, istat); ChkErrGoto( istat, leave); wrlte dbase( istat);
DLRemoveThread( mfm_files, mfm_volumes, istat);
ChkErrGoto( istat, leave); leave: return;
}
/*********************************************************************************************
******************************************************* ******************************** ******************** ********************** MFM INQUIRY CALLS ******************************** **************************************************** ******************************* ************************************************************************************************ ********/
/******* ************ *****************************
* NAME: mfm_get_video_framesize
*
* Purpose: Returns the size of a media frame (height & width) . */ void mfm_get_video_frame_slze( file, height, width, istat)
MEM_CRUX file; short *height; short *width; istat_t *istat;
{
MFMCrux_ptr FL;
ResetErr( istat); if( file == NULL || file == SILENCE II file == BLACK) c
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave);
DLGet( mfm_files, (char **)&FL, (recordID *)&file, FALSE, FALSE, istat); ChkErrGoto( istat, leave); if( FL→magic != MFM_CRUX_MAGIC)
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave); if( FL→Mtype != video_cm8) LogErrGoto( istat, MFM_NOT_VIDEO_MEDIA, leave); if( FL→permanent)
{
*height = FL→header.disk.specific.video cm8.height;
*width = FL→header.disk.specific.video_cm8.width;
} else
{
*height = FL→header.ram.specific.video_cm8.height;
*width = FL→header.ram.specific.video_cm8.width;
}
leave: return; }
/************************************************
* NAME: mfm_get_start_time
*
* Purpose: Returns the starting time code of the media file.
* Zero for slides. Actual time code for abridged. */ timecode_t mfm_get_start_time( file, istat) MEM_CRUX file; istat_t *istat; {
MFMCrux_ptr FL; timecode_t start_time;
ResetErr( istat); if( file == NULL)
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave); lf( file == BLACK || file == SILENCE) returnt ClearTimecode( FALSE, 30));
DLGet( mfm_files, (char **)&FL, (recordID *)&file, FALSE, FALSE, istat); ChkErrGoto( istat, leave); if( FL→magic != MFM_CRUX_MAGIC)
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave); if( FL→permanent) start_tlme = FL→header.disk.start_time; else start_time = ZeroTimecode ( FALSE); leave: return( start_time); } /******************************************************************* * NAME : mfm_get_end_time
* Purpose: Returns the ending time code of the media file.
* Calculated for slides. Actual time code for abridged. */ timecode_t mfm_get_end_time( file, istat)
MFM_CRUX file; istat_t *istat;
{
MFMCrux_ptr FL; timecode_t end_time;
ResetErr( istat); if( file == NULL)
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave); if( file == BLACK II file == SILENCE) return( inc_time( ClearTimecode( FALSE, 30), 1));
DLGet( mfm_files, (char **)&FL, (recordID *)&file, FALSE, FALSE, istat); ChkErrGoto( istat, leave); if( FL→magic != MEM_CRUX _MAGIC)
LogErrGoto( istat, MEM_BAD_FILE_HANDLE, leave); if( FL→permanent) end_time = FL→header.disk.end_time; else
{ long frame_size; switch ( FL→Mtype) { case video_cm8: { frame_size = FL→header. ram.specific.video_cm8.height * FL→header. ram.specific.video_cm8.width; break; } case audio:
{ frame_size = (FL→header. ram.specific.audio.number_of_samples
* FL→header.ram.specific.audio.bytes_per_sample)
break; } } lf( FL→headar.ram.data_pos != 0) end_time = CreateTimecode ( 0, 0, 0, FL→header.ram.data_pos / frame size, FALSE, FL→headar.ram.frame_rate); else end_time = ClearTimecode ( FALSE, FL→header.ram.frame rate);
} leave: return( end_time); }
/************************************************
* NAME: mfm_get_frame_rate
*
* Purpose: Returns the preferred display rate (in frames/sec)
* for this media file. It is the rate at which it was recorded.
* This rate is independent of the capture ratio.
*/ long mfm_get_frame_rate( file, istat)
MFM_CRUX file; istat_t
{ *istat; long frate;
MFMCrυx_ptr FL;
ResetErr( istat); frate = 0; if( file == NULL)
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave); if( file == BLACK || file == SILENCE) return( 30);
DLGet ( mfm _files, (char **) &FL, (recordID * ) &file, FALSE, FALSE, istat) ;
ChkErrGoto( istat, leave) ; if ( FL→magic ! = MFM_CRUX_MAGIC)
LogErrGoto ( istat, MFM_BAD_FILE _HANDLE, leave) ;
if( FL→permanent) frate = FL→header.disk.start_time.frames_sec; else frate = FL→header.ram.frame_rate; leave : return( frate); }
/************************************************
* NAME: mfm_get_capture_rate
*
* Purpose: Returns the data capture rate (in frames/sec)
* for this media file.
* This rate is a floating point number and is used
* primarily for informational purposes.
* For example, if you start with a video that has a frame rate of 30 frames/sec
* and capture 1 in 4 frames; the effective capture rate is 17.5 frames/sec. */ float mfm_get_capture_rate( file, istat) MFM_CRUX file; istat_t *istat;
{ float rate;
MFMCrux_ptr FL;
ResetErr( istat); rate = 0.0; if( file == NULL)
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave); lf( file == BLACK I I file == SILENCE) return( (float) 30);
DLGet( mfm_files, (char **)&FL, (recordID *)&file, FALSE, FALSE, istat); ChkErrGoto( istat, leave); if( FL→magic != MFM_CRUX_MAGIC)
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave); lf( FL→permanent) {
if ( FL→Mtype == video_cm8) rate = FL→header.disk.specific.video_cm8. capture .rate; else if ( FL→Mtype == audio) rate = FL→header.disk.start_time. frames_sec; else
LogErrGoto( istat, MFM_UNKNOWN_MEDIA_TYPE, leave) ;
} else rate = (float)FL→header.ram.frame_rate; leave: return ( rate); }
* NAME: mfm_get_sourceuid
* * Purpose: return the source uid the media material was known to be derived from.
*
*/ void mfm_get_sourceuld( file, uid, istat) MFM_CRUX file; sourceuld_t *uid; istat_t *istat; { MFMCrux_ptr FL;
ResetErr( istat);
MakeNilUid( uid); if ( file == NULL)
LogErrGoto( Istat, MEM_BAD_FILE_HANDLE, leave) if ( file == BLACK I I file == SILENCE) { if ( file == BLACK)
{ uid→high = BLACK_UIDHI; uid→low = BLACK_UIDLO;
} else
{ uid→high = SILENCE_UIDHI; uid→low = SILENCE_UIDLO;
} } else
{
DLGet( mfm_files, (char **)&FL, trecordID *)&flle, FALSE, FALSE, istat);
ChkErrGoto( istat, leave); if( FL→magic != MFM_CRUX_MAGIC)
LogErrGoto( istat, MEM_BAD_FILE_HANDLE, leave); if( !FL→permanent)
LogErrGoto( istat, MEM_BAD_FILE_HANDLE, leave);
*uid = FL→header.disk.UID;
leave: return; } /*******************************************************************************
* NAME: mfm_get_CRUX_from_GIST
*
* Purpose: Translate a gist id into the associated crux id. */
MFM_CRUX mfm_get_CRUX_from_GIST( gist, istat) MEM_GIST gist; istat_t *istat;
{
MFM_CRUX crux = 0;
MFMCrux_ptr FL; if( gist == NULL)
LogErrGoto(istat, MFM_BAD_FILE_HANDLE, leave); if( gist == BLACK || gist == SILENCE) return( (MFM_CRUX)gist) ;
if( (*(MFMGist_hdl)gist)→magic != MEM_GIST_MAGIC) LogErrGoto(istat, MFM_BAD_FILE_HANDLE, leave); crux = (*(MFMGist_hdl)gist)→cruxid;
DLGet( mfm_files, (char **)&FL, (recordID *)&crux, FALSE, FALSE, istat);
/* assume the worst */ crux = 0;
ChkErrGoto( istat, leave); if( FL→file_use == -1)
LogErrGoto( istat, MEM_GIST_TO_CRUX_WRITE, leave);
/* we assumed wrong */ crux = (*(MEMGist_hdl)gist)→cruxid; leave: return( crux);
}
/*****************************************************************************
* NAME: mfm_get_blank
* Purpose: Get the crux id associated with the blank version of the Mtype
*/ MFM_CRUX mfm_get_blank( Mtype, istat) Mtype_t Mtype; istat_t *istat;
{
ResetErr( istat); switch( Mtype)
{ case video_cm8: { return( (MFM_CRUX) BLACK) ; break;
}
case audio: { return( (MFM_CRUX) SILENCE) ; break;
}
LogErr( istat, MFM_UNKNOWN_MEDIA_TYPE) ; return ( NULL) ;
}
/************************************************
* NAME: mfm_get_filmkey
*
* Purpose: return the film key data from a media file if any exists.
*
*/ Boolean mfm_get_filmkey( crux, key, istat) MFM_CRUX crux; key_t *key; istat_t *istat;
{
MFMCrux_ptr FL; mfm_base_t base;
Boolean film_data_present = FALSE;
ResetErr( istat); if( crux == NULL)
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave); if( crux == BLACK || crux == SILENCE) goto leave;
DLGet( mfm_files, (char **)&FL, (recordID *)&crux, FALSE, FALSE, istat); ChkErrGoto( istat, leave); if( ! (FL→ permanent && FL→ header.disk.specific.video_cm8.film_data)) goto leave;
/* At this point it is clear that film data actually does exist and we need to go * get it off the disk. sigh. */
/* first, set the return Boolean */ film_data_present = TRUE; if( FL→ file_use == 0) {
FL→ header.disk.raw_file = AVOpenFile( FL→ header.disk.VolID, FL→ header.disk.Dirid, FL→ header.disk.filename, OPEN ChkErrGoto( istat, leave);
}
AVReadFile( FL→ header.disk.raw_file, &base, 0, sizeof ( mfm_base_t), istat); ChkErrGoto( istat, leave);
*key = base.specific.video_cm8.film.starting_key; if( FL→ file_use == 0)
{
AVCloseFile( FL→ header.disk.raw_file, istat);
ChkErrGoto( istat, leave);
FL→ header.disk.raw_file = NULL;
} leave: return( film_data_present);
}
/*********************************************************
* NAME: mfm_get_volid
*
* Purpose: Get the volume reference number that the media file is located on. */ void mfm_get volid( gist, volid, istat)
MFM_GIST gist; short *volid; istat_t *istat;
{
MEMCrυx_ptr FL;
MFM_CRUX crux = 0;
ResetErr( istat);
*volid = 0;
If( gist == NULL)
LogErrGoto(istat, MFM _BAD_FILE_HANDLE, leave) ; if ( gist == BLACK I I gist == SILENCE) LogErrGoto (istat, MFM_BAD_FILE_HANDLE, leave) ; if( (*(MFMGist_hdl)gist)→magic != MFM_GIST_MAGIC) LogErrGoto(istat, MFM_BAD_FILE_HANDLE, leave);
DLGet( mfm_files, (char **)&FL, (recordID *)& (* (MEMGist_hdl)gist)→cruxid, FALSE, FALSE, istat); ChkErrGoto( istat, leave); if( FL→magic != MFM_CRUX_MAGIC)
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave); if( FL→file_use != -1) LogErrGoto( istat, MFM_FILE_NOT_OPEN_WRITE, leave);
*volid = FL→header.disk.VolID; leave: return;
}
/************************************************
* NAME: mfm_preallocate
*
* Purpose: Tell mfm to preallocate the file tied to this gist to the specified size.
*/ void mfm_preallocate( gist, size, istat) MFM_GIST gist; u_long size; istat_t *istat;
{
MFMCrux_ptr FL;
MFM_CRUX crux = 0;
ResetErr( istat); if ( gist = = NULL)
LogErrGoto(istat, MFM_BAD_FILE_HANDLE, leave);
if( gist == BLACK || gist == SILENCE) LogErrGoto(istat, MFM_BAD_FILE_HANDLE, leave); if( (*(MFMGist_hdl)gist)→magic != MFM_GIST_MAGIC)
LogErrGoto(istat, MFM_BAD_FILE_HANDLE, leave);
DLGet( mfm_files, (char **)&FL, (recordID *) & (*(MFMGist_hdl)gist)→cruxid, FALSE, FALSE, istat);
ChkErrGoto( istat, leave); if( FL→magic != MEM_CRUX_MAGIC)
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave); if( FL→file_use != -1) LogErrGoto( istat, MFM_FILE_NOT_OPEN_WRITE, leave);
AVPreAllocate( FL→header.disk,raw_file, size, istat); leave: return; } /**************************************************************************************/
/**** Private Code ****/ /**************************************************************************************/
/************************************************
* NAME: mfm_journal
*
* Purpose: Write media file stats to the journal. */ static void mfm_journal( base, journal_buf, istat) mfm_base_t base; char *journal_buf; istat_t *istat;
{ char *typename; switch( base .Mtype)
{ case vldeo_cm8: typename = "8-bit video"; break; case audio: typename = "raw audio"; break; default: typename = "unknown type ******; break; }
sprintf( journal_buf, "MFM: Media type = %ld (%s) \n", base.Mtype, typename); journal_write( journal_buf, TRUE, istat); ChkErrGoto( istat, leave); ch( base.Ftype) { case SLIDE: typename = "slide"; break; case FULL: typename = "full"; break; case ABRIDGED: typename = "abridged"; break; default: typename = "unknown type ******; break;
} sprintf( journal_buf, "MFM: File type = %ld (%s) \n",base.Ftype, typename); journal_write( journal_buf, TRUE, istat); ChkErrGoto( istat, leave); sprintf( journal_buf, "MFM: Revision = %d.%d\n", base.major, base.minor); journal_write( journal_buf, TRUE, istat); ChkErrGoto( istat, leave); switch( base.Ftype)
{ case ABRIDGED: case FULL:
{ sprintf ( journal_buf, "MFM: Starting timecode = %s \n", print_time( base.start_time, typename) ) ; journal_write ( journal_buf, TRUE, istat) ; ChkErrGoto( istat, leave) ; sprintf( journal_buf, "MFM: Ending timecode = %s\n' print_time( base.end_time, typename)); journal_write( journal_buf, TRUE, istat); ChkErrGoto( istat, leave); if( base.Ftype == video_cm8 && base. specific.video_cm8.capture.mask == 0)
{ journal_write( "MFM: No capture mask. \n", TRUE, istat); ChkErrGoto( istat, leave); } else if( base.Ftype == video_cm8)
{ journal _write( "MFM: Capture information: \n", TRUE, istat); ChkErrGoto( istat, leave); sprintf( journal_buf, "MFM: Capture rate = %.2f\n",base.specific.video_cm8.capture.rate); journal_write( journal_buf, TRUE, istat); ChkErrGoto( istat, leave); sprintf( journal_buf, "MFM: Capture mask = 0x%1X\n",base.specific.video_cm8.capture.mask); journal_write( journal_buf, TRUE, istat); ChkErrGoto( istat, leave); } break;
}
} leave: return; }
/************************************************************
* NAME: MfilesToDatabase
*
* FUNCTION: Read files from directory and enter data into database.
*
* PARAMETERS:
* short Volld -volume id where directory is located.
*/ void MfilesToDatabase( VolId, dirid, istat) short Volld; long dirid; istat_t *istat; {
HParamBlockRec FileInfo;
OSErr Err;
MFMCrux_hdl file; name_t name; name_t flname; recordID record; istat_t local;
ResetErr( istat); strncpy( name, MFILES, sizeof( name_t)); flname(O) = '\0';
CtoPstr( flname);
Filelnfo.fileParam.ioVRefNum = Volld;
FileInfo.fileParam.ioCompletion = NULL;
Fllelnfo.fileParam.ioNamePtr = (StringPtr)flname;
Filelnfo.fileParam.ioFDirlndex = 1;
Filelnfo.fileParam.ioDirlD = dirid; for( Err = PBHGetFInfo( &Filelnfo, FALSE); Err != fnfErr; FileInfo.fileParam.ioFDirIndex++, Filelnfo.fileParam.ioDirlD = dirid, Err = PBHGetFInfo( &Filelnfo, FALSE))
{
/* Open the file, if its a file */ if( !BitTst( &Filelnfo.fileParam.ioFlAttrib, 3))
{ file = open_file( dirid, Volld, PtoCstr(flname), &local); if( local == MFM_BAD_FILE HANDLE) {
Boolean rescan; istat_t formatlocal; rescan = handle_previous_formats( Volld, dirid, flname, &formatlocal); if( formatlocal != ISTAT_OK) LogErrGoto( istat, formatlocal, leave); if( rescan) { recordID **records; recordID *RCchaser; long NumRecs; short j;
/* Delete elements in Database on this volume */
DLSetThread( mfm_files, mfm_volumes, istat);
ChkErrGoto( istat, leave); records = DLSift( mfm_files, INITIAL, EQ, (char *)&volId, TRUE, NULL, &NumRecs, istat);
ChkErrGoto( istat, leave); DLSetThread( mfm_files, MAINTHREAD, istat); ChkErrGoto( istat, leave); if( NumRecs != 0) { HLock( records); RCchaser = *records; for( j=0; j<NumRecs; j++, RCchaser++) { DLRemove( mfm_files, *RCchaser, &formatlocal); ChkErrGoto( &formatlocal, lockleave); lockleave : }
HUnlock( records);
AvDisposeHandle( (Handle) records, istat);
ChkErrGoto( istat, leave); if( formatlocal != ISTAT_OK) LogErrGoto( istat, formatlocal, leave);
}
/* Set FDirIndex to do rescan */ Filelnfo.fileParam.ioFDirlndex = 0; } } else if( local == ISTAT_OK) {
HLock ( file); record = DLAddHead( mfm_files, (char *)(*file), istat); HUnlock( file); ChkErrGoto( istat, leave);
DLAddToThread( mfm_files, mfm_disk, record, istat); ChkErrGoto( istat, leave);
DLAddToThread( mfm_files, mfm_volumes, record, istat); ChkErrGoto( istat, leave); AvDisposeHandle( (Handle) file, istat); ChkErrGoto( istat, leave);
} else if( local == MFM_DELETED_EMPTY_MFILE)
Filelnfo.fileParam.ioFDirlndex -= 1; else if( local != MFM_UNKNOWN_FILE TYPE)
LogErrGoto( istat, local, leave); } } leave: return; } /********************************************************************** * NAME: open_file
* Purpose: File is known to exist. Open it, make up file header and return * file header handle. * */ static MFMCrux_hdl open_file( Dirid, Volld, filename, istat) long Dirid; /* Apple id of dir containing media file */
short Volld; /* Apple Volume ID */ char *filename; /* media file filename */ istat_t *istat; { MEMCrux_hdl file;
MFMCrux_ptr FL; mfm_base_t base;
RAW_FILE *raw_file;
ResetErr( istat); if( AVGetFileType( Volld, Dirid, filename, Istat) != MEDIA_FT) LogErrGoto( istat, MFM_UNKNOWN_FILE_TYPE, leave); raw_file = (RAW_FILE *) AVOpenFile( Volld, Dirid, filename, OPEN_READ, istat); ChkErrGoto(istat, leave);
(void)AVReadFile( raw_file, (char *) &base, 0, sizeof (mfm_base_t), istat); ChkErrGoto(istat, leave);
AVCloseFilet raw_file, istat); ChkErrGoto( istat, leave);
/* Check for the magic constant to ensure that this is a valid media file. */ if( base.magic != FILE_MAGIC) {
LogErrGoto( istat, MFM_BAD_FILE_HANDLE, leave);
/* if its file that was still in the middle of digitizing, then nuke it. */ if( (strncmp( filename, "creating", 8) == 0) && base.Mtype == still_wrlting)
{
AVDeleteFile( Volld, Dirid, filename, istat);
ChkErrGoto( istat, leave);
LogErrGoto( istat, MFM_DELETED_EMPTY_MFILE, leave);
}
/* if the start and end time are the same, then nuke it. */ if ( compare_time( base.start_time, base, end_time) )
{
AVDeleteFile( Volld, Dirid, filename, istat);
ChkErrGoto( istat, leave);
LogErrGoto( istat, MFM_DELETED _EMPTY_MFILE, leave);
}
file = (MEMCrux_hdl)AvNewHandleClear( sizeof (MFMCrux_t), istat);
ChkErrGoto(istat, leave);
HLock( file); FL = *file;
FL→magic = MFM_CRUX_MAGIC;
FL→permanent = TRUE;
FL→header.disk.raw_file = raw_file; SMAddName( base.name, &base.UID, istat); ChkErrGoto( istat, error); /* Read the media specific information needed */ if( base.Mtype == audio)
{
FL→header.disk.specific.audio.bytes_per_sample = base.specific.audio.bytes_per_sample;
FL→header.disk.specific.audio.number_of_samples = base.specific.audio.number_of_samples;
FL→header.disk.specific.audio.video_frames = base.specific.audio.video_frames;
FL→header.disk.specific.audio.audio_mode = base.specific.audio.audio_mode;
} else if( base.Mtype == video cm8)
{
FL→header.disk.specific.video_cm8.capture.mask = base.specific.video_cm8. capture.mask;
FL→header.disk.specific.video_cm8.capture.rate = base.spedfic.video_cm8.capture. rate;
FL→header.disk.specific.video_cm8.capture.start_offset = base.specific.video_cm8. capture. start offset;
FL→header.disk,specific.video_cm8.capture, length = base,speciflc.video_cm8. capture, length;
FL→header.disk.specific.video_cm8.capture.one_bits = base.specific.video_cm8.capture.one bits;
FL→header.disk,specific.video_cm8.height = base.specific.video_cm8. height;
FL→header.disk.specific.video_cm8.width = base.specific.video_cm8.width;
FL→header.disk.specific.video_cm8.film_data = base. specific.video_cm8.film_mode;
} FL→channel = base.channel;
FL→file_use = 0;
FL→Mtype = base .Mtype;
FL→Ftype = base.Ftype;
FL→header.disk.Dirid = Dirid;
strncpy( FL→header.disk.filename, filename, sizeof( name_t));
FL→header.dlsk.VolID = Volld;
FL→header.disk.touch_ticks = TickCount();
FL→header.disk.UID = base.UID;
FL→header.disk.start_time = base.start_time;
FL→header.disk.end_time = base.end_time;
FL→header.disk.dataoffset = base.dataoffset; FL→header.disk.raw_file = NULL; if( debugPrint( ISTAT_MFM)) { path_t journal_buf; sprintf( journal_buf, "MFM: Opening media file \"%s\".\n", filename); journal_write( journal_buf, TRUE, istat); ChkErrGoto( istat, error); mfm_journal( base, journal_buf, istat);
ChkErrGoto( istat, error);
}
HUnlock( file); return( file); error:
HUnlock( file);
AvDisposeHandle( (Handle) file, STD_CHECK) ; leave: return ( NULL) ; }
/************************************************
* NAME: read_dbase
*
* Purpose: Read in the database on this particular volume, and add the
* information to the data base.
*
*/ static void read_dbase( volid, dirid, istat) short volid; long dirid; istat_t *istat;
{ long readsize;
MFMCrux_ptr DBchaser;
MEMCrux_hdl Dbase; recordID record; long mfmfile_entries; short i;
ResetErr( istat);
/* Attempt to open the database file, lets see where we were last... */ dbasefile = LoadCluster( volid, dirid, MEDIAFILEDBASE, CLUSTER_MFM, istat); ChkErrGoto( istat, leave); readsize = GetClusterInfoLen( dbasefile, MEDIAENTRIES, istat);
ChkErrGoto( istat, leave) ;
/* FCS FCS */ if( readsize == 0)
{ vol_info_t vol_info; char buffer [128];
AvGetVolInfo( volid, &vol_info, STD_CHECK) ; sprintf( buffer, "Media Database on %s has zero entries", vol_info.name); auto_request( buffer, "Get Reber", 1);
} mfmfile_entries = readsize / sizeof( MFMCrux_t);
/* first get some storage to store the disk representation of the database */ Dbase = (MFMCrux_hdl)AvNewHandle( readsize, istat); ChkErrGoto( istat, leave); HLock ( Dbase);
/* read */
ReadClusterlnfo( dbasefile, MEDIAENTRIES, *Dbase, readsize, istat); if( *istat != ISTAT_OK) LogErrGoto( istat, MFM_CANT_READ_DBASE_FROM_DISK, leave);
/* Ok, got the database read in. */
/* Now, transfer it into live linked list. */
DBchaser = *Dbase; fort i=0; i<mfmfile_entries; i++, DBchaser++)
{
DBchaser→header.disk.VoliD = volid;
DBchaser→header.disk.Dirid = dirid; record = DLAddHead( mfm_files, (char *) (DBchaser), istat); ChkErrGoto( istat, leave);
DLAddToThread( mfm_files, mfm_disk, record, istat); ChkErrGoto( istat, leave);
DLAddToThread( mfm_files, mfm_volumes, record, istat); ChkErrGoto( istat, leave);
}
/* dump the storage that we read in from disk */
HUnlock( Dbase);
AvDisposeHandle( (Handle)Dbase, STD_CHECK);
/* tell source manager to read in his stuff */ SMReadNTable( dbasefile, istat); ChkErrGoto( istat, leave);
CloseCluster( dbasefile, istat); leave: return; }
/************************************************
* NAME: write_dbase
*
* Purpose: Write out the media databases on the respective volumes.
*
*/ static void write_dbase( istat) istat_t *istat;
{ short sysvref; drive_info_t drive_info; vol_info_t vol_info; void *driveHandle; long dirid;
GetVRefNum (SysMap, &sysvref); driveHandle = AVFirstDrive( &drive_info) do {
if( !drive_info.ejectable)
{
AvGetVolInfo( drive info.driveNum, &vol_info, istat); if( *istat == ISTAT_OK && vol_lnfo.deviceTyρe != VOL_UNMOUNTED
&& vol_info.deviceType == VOL_SCSI
&& vol_info.vRefNum != sysvref)
{ dirid = 0;
/* if this volume has folder MFILES, then we want to write a dbase there. */
If( AVCheckDirExlsts( MFILES, vol_info.vRefNum, &dirid, FALSE, istat) && *istat == ISTAT_OK)
{ write_dbaseOnDrive ( vol_info.vRefNum, dirid, istat); ChkErrGoto( istat, leave); } { else ChkErrGoto( istat, leave); } } while ( AVNextDrive( &driveHandle, &drive_info)); leave: return;
}
/************************************************
* NAME: write_dbaseOnDrive
*
* Purpose: Write the database on a specific volume.
*
*/ static void write_dbaseOnDrive ( volid, dirid, istat) short volid; long dirid; istat_t *istat; { recordID **records; recordID *RCchaser; long NumRecs; short 1; cluster_t *dbasefile;
MFMCrux_hdl FLDbase;
MFMCrux_ptr FLchaser;
MFMCrux_ptr FL;
SMTableid SMtable;
Boolean nuffmemory;
/* first get a list of all the records on this volume. */ DLSetThread( mfm_files, mfm_volumes, istat); ChkErrGoto( istat, leave); records = DLSlft( mfm_flles, INITIAL, EQ, (char *)&volid, FALSE, NULL, &NumRecs, istat); ChkErrGoto( istat, leave);
DLSetThread( mfm_files, MAINTHREAD, istat); ChkErrGoto( istat, leave);
/* if there are no media files in the database on this volume, then return. If * there actually are files on the disk, lack of media database file will cause * rescan on next application run. */ if( NumRecs == 0) return;
/* check for a database file, if none then create one. */ dbasefile = LoadCluster( volid, dirid, MEDIAFILEDBASE, CLUSTER_MFM, istat); if( *istat == ISTAT_FILE_NOT_FOUND)
{
/* file does not exist. Assume we are in the right directory, and create one. */ dbasefile = NewClustert volid, dirid, MEDIAFILEDBASE, CLUSTER_MFM, istat); if( *istat != ISTAT_OK) LogErrGoto( istat, MFM_CANT_CREATE_DBASE_FILE, leave);
} else ChkErrGoto( istat, leave);
/* get enough storage to write all the records into */ nuffmemory = AvChkNewHandle( sizeof( MFMCrux_t) * NumRecs, istat);
ChkErrGoto( istat, leave); if( nuffmemory)
{
FIDbase = (MFMCrux_hdl)AvNewHandle ( sizeof ( MFMCrux_t) * NumRecs, istat) ;
ChkErrGoto( istat, leave) ;
} else {
auto_request ( "Not enough memory for MFM database write", "OK", 1);
FLDbase = (MFMCrux_hdl)AvNewHandle( sizeof ( MFMCrux_t), istat);
ChkErrGoto( istat, leave);
CMUpdatelnfoStart( dbasefile, MEDIAENTRIES, sizeof ( MFMCrux_t) * NumRecs, istat); if( *istat == dskFulErr) goto diskfull;
ChkErrGoto( istat, leave);
}
HLock ( FLDbase); FLchaser = "FLDbase;
/* Tell the Source Manager that we are setting up to write another file.
* Build a table of the related source uids to write into the file.
SMtable * SMDpenTable( istat);
HLock ( records);
ChkErrGoto( istat, leavelock);
/* for each of the records in the list previously returned by DLSift, get the
* data from the record and copy it into the storage FLDbase. This will be
* given to Update Cluster. */ for( 1=0, RCchaser = *records; i<NumRecs; i++, RCchaser++) { if( !DLGet( mfm_files, (char **)&FL, RCchaser, FALSE, FALSE, istat)) LogErrGoto( istat, MFM_INTERNAL_MFM, leave); ChkErrGoto( istat, leavelock); if( FL→permanent)
{ if( FL→file_use != 0)
/* FCS FCS */ dprintf( "open_count = %d\nfilename = %s\n", FL→file_use, FL→header.disk. filename); auto_request ( "mfm_quit called. All files not closed. \nSee Console.", "OK", 1);
/* i give up, nuke the sucker... */
AVCloseFile( FL→header.disk. raw_flie, istat);
ChkErrGoto( istat, leavelock);
FL→header.disk. raw_flie = NULL;
FL→file_use = 0;
} *F chaser++ = *FL ;
if( !nuffmemory)
{
CMUpdateInfoIncr( dbasefile, *FLDbase, sizeof( MFMCrux_t), istat); if( *istat == dskFulErr) goto diskfulllock;
ChkErrGoto( istat, leavelock);
FLchaser = (MFMCrux_ptr) *FLDbase;
} SMBuildTable( SMtable, FL→header.disk.UID, istat); ChkErrGoto( istat, leavelock); } else {
/* FCS FCS */ dprintf( "open_count = &d\nMtype = %d\nld bytes of data\n", FL→file_use, FL→Mtype, FL→header.ram.data_pos) auto_request( "mfm_qult called. Temp files still lingering. \nSee Console.", "OK", 1); }
DLRemove( m fm_flies, *RCchaser, istat);
ChkErrGoto( istat, leavelock);
}
if( nuffmemory) (
UpdateClusterlnfo( dbasefile, MEDIAENTRIES, *FLDbase, sizeof ( MFMCrux_t) * NumRecs, istat); if( *istat ==dskFulErr) goto diskfulllock; else ChkErrGoto( istat, leavelock); else } { CMUpdatelnfoEnd( dbasefile, istat); if( *istat == dskFulErr) goto diskfulllock; ChkErrGoto( istat, leave);
}
HUnlock( records);
AvDisposeHandle( (Handle) records, STD_CHECK) ;
HUnlock( FLDbase);
AvDisposeHandle( (Handle)FLDbase, STD_CHECK);
SMCloseNWriteTable ( SMtable, dbasefile, istat); ChkErrGoto( istat, leave);
CloseCluster ( dbasefile, istat); ChkErrGoto( istat, leave);
/* need to insure that the mod date on the folder matches exactly the mod date on
* the database file. Read the mod date off the database file, and SET the mod
* date on the folder to be the same. */
{
CInfoPBRec Info; name_t name; u_long foldermod; u_long filemod;
OSErr err; strcpy( name, MEDIAFILEDBASE); CtoPstr( name);
/* get the mod date on the database file. */ Info.hFileInfo.ioCompletion = NULL; Info.hFilelnfo.ioFDirlndex = 0; Info.hFilelnfo.ioNamePtr = (StringPtr)name; Info.hFilelnfo.icDirlD = dirid; Info.hFilelnfo.ioVRefNum = volid; err = PBGetCatlnfo ( &lnfo, FALSE); if( err != noErr) LogErrGoto( istat, err, leave); filemod = Info.hFilelnfo.ioFlMdDat;
/* get the directory modification date */ Info.dirlnfo.ioCompletion = NULL; Info.dirlnfo.loFDirlndex = -1; Info.dirlnfo.ioNamePtr = NULL; Info.dirlnfo.ioDrDirlD = dirid; Info.dirlnfo.ioVRefNum = volid; err = PBGetCatlnfo ( &lnfo, FALSE); if( err != noErr) LogErrGoto( istat, err, leave);
/* SET the folder mod date */ Info.dlrlnfo.ioDrMdDat = filemod;
err = PBSetCatlnfo ( &lnfo, FALSE); if( err != noErr) LogErrGotot istat, err, leave);
} return; diskfulllock: leavelock:
HUnlock( records);
AvDisposeHandle ( (Handle) records, STD_CHECK);
HUnlockt FLDbase);
AvDisposeHandle ( (Handle)FLDbase, STD_CHECK); diskfull: if( *istat ==dskFulErr)
ResetErr( istat);
CloseCluster( dbasefile, istat);
ChkErrGoto( istat, leave);
AVDeleteFile( volid, dirid, MEDIAFILEDBASE, istat);
ChkErrGoto( istat, leave);
} leave: return; }
/****************************************************
* NAME: getfilename
*
* Purpose: build a media filename for disk resident file from tapename
* tapeuid and current time. Format of the filename will
* be as follows:
*
* tapename IMtypeI channel. Iuid in hex l_Iday IhourIminIsecI = name
* 10 1 2 1 8 1 2 2 2 2 = 31 name length
* built from |tapenameImediaIphysicalI tapeuid I current time when digitized * ccannel
*/ void getfilename ( tapename, uid, Mtype, channel, buf) char *tapename; sourceuld_t uid;
Mtype_t Mtype;
Mchannel_t channel; char *buf;
{
DateTimeRec uiddate;
DateTimeRec nowdate; long now; char uidchar[12]; char nowchar[10]; char *thebuf;
/* WOW, now here is and intricate piece of code!! */
/* This does a bunch of date conversions, mash integers into characters * and build a string out of all this mush, which is the filename. */ thebuf = buf; lf( strlen( tapename) < 10)
{ strcpy( thebuf, tapename); thebuf += strlen( tapename);
} else strncpy( thebuf, tapename, 10); thebuf += 10; } switch( Mtype) { case video_cm8:
{ sprintf( thebuf, "V%021d", (long) channel); break;
} case audio:
{ sprintf( thebuf, "A%021d", (long) channel); break; } }
thebuf += 3;
*thebuf++ = '.'; sprintf( thebuf, "%01X", uid.high); thebuf += 8;
*thebuf++ = '-';
/* Get the current time of the day. Simpler to just check your watch,
* but Macs have no wrists ....
*/ GetDateTime( &now); Secs2Date ( now, &nowdate) ; if( nowdate.day < 10)
{
*thebuf++ * '0'; stci_d( thebuf++, nowdate.day, 1); } else stci_d( thebuf, nowdate.day, 2); thebuf += 2; { if( nowdate.hour < 10) {
*thebuf++ = '0'; stci_d( thebuf++, nowdate.hour, 1); }
else { stci_d( thebuf, nowdate.hour, 2); thebuf += 2; if( nowdate.minute < 10)
{
*thebuf++ = '0'; stci_d( thebuf++, nowdate.minute, 1);
} else {
stci_d( thebuf, nowdate.minute, 2); thebuf += 2;
} if( nowdate.second < 10)
{
*thebuf++ = '0'; stci_d( thebuf++, nowdate.second, 1);
} else
{ stci_d( thebuf, nowdate.second, 2); thebuf += 2;
+ *thebuf = '\0'; }
* NAME: read_blank
*
* Purpose: caller has asked to read from BLACK or SILENCE. Give it to em.
*
*/ static char "read_blank ( gist, buf, bufsize, frames_wanted, repeatable, readspec, istat) MFM_GIST gist; register char "buf; long bufsize; long frames_wanted;
Boolean *repeatable; mediaSpecRead_t *readspec; istat_t *istat;
{ register long data; register long *longs; register long bytes_to_write; register long edge_bytes; register long thestuff; register long size; long buf_frames;
If( gist == BLACK)
{ size = 12288; thestuff = 0xFFFFFFFF;
} else
{
/* it must be silence */ size = 735; thestuff = 0x80808080;
} buf_frames = bufsize / size; if( frames_wanted == 0) frames_wanted = buf_frames; frames_wanted = mint frames_wanted, buf_frames); if( readspec !- NULL)
{ readspec→logical_frames = frames_wanted; readspec→physical_frames = frames_wanted; readspec→specific.video_cm8.capture .mask = 0; readspec→speciflc.video_cm8.capture.shift = 0;
}
/* if client does not wish to handle repeatable frames, just fill for the
* the desired length.
*/ if( repeatable ==NULL l l *repeatable == FALSE) bytes_to_write = frames_wanted * size; else
{ /* otherwise the client claims to be able to handle a single
* frame and a repeat count. Write a single frame into the buffer, * and readjust the physical frame count copied. Just leave the * value of *repeatable as TRUE. It has to be true to get in to * this case, and that's the value we want to pass back.
*/ bytes_to_write = size; if( readspec != NULL) readspec→physlcal_frames = l ;
} edge _bytes = (4 - ( (long) buf & 3) ) & 3; if ( (bytes _to_wrlte - edge_bytes) <= 0)
{
/* just go write the bytes */ for( ; bytes_to_write != 0; bytes_to_write—) *buf++ = (char) thestuff;
} else { bytes_to_write -= edge_bytes;
/* write in lead bytes */ fort ; edge_bytes != 0; edge_bytes—) *buf++ = (char) thestuff; edge_bytes = bytes_to_write & 3;
/* bytes to write becomes longs to write */ longs = (long *)buf; fort bytes_to_write = bytes_to_write>>2; bytes_to_write != 0; bytes_to_write—) *longs++ = thestuff; buf = (char *)longs; for( ; edge_bytes != 0; edge_bytes—) *buf++ = (char)thestuff;
} return( buf); }
/************************************************
* NAME: handle_previous_formats
*
* Purpose: Handles media files not of the current format. This routine will
* completely scan and handle ALL files of alien format in the directory.
* The function value passed back (Boolean) Indicates to the caller that the names
* of files in the directoy have changed, and any incremental scan in progress
* or logging of filenames is now potentially incorrect. A rescan of the directory
* is necessary. *
*/ static Boolean handle_ρrevious_formats ( Volld, dirid, filename, istat) short Volld; long dirid; name_t filename; istat_t *istat; { typedef enum {
REFORMAT_IT,
REFORMAT_ALL,
DONT_FORMAT,
SKIP_EM }; typedef struct { name_t filename;
} files _t;
HParamBlockRec Filelnfo; path_t format_notify;
Boolean rescan = FALSE; listID filelist; path_t flname; files_t file; flles_t *FP;
OSErr Err; static long autoconvert = REFORMAT_IT; static short PrevVolid = 0; static long PrevDirid = 0; istat_t local; filelist = DLCreate( sizeof( files_t) istat); ChkErrGoto( istat, leave);
/* if we already processed this directory, then obviously the user already specified * desires on this directory. Return indicating no rescan necessary. */ if( Volld == PrevVolid && dirid ==PrevDirid) return( rescan); PrevVolid = Volld; PrevDirid = dirid; /* if the user previously indicated to skip reformatting all alien files, then return * indicating no rescan is necessary. We have already set the prev statics to indicate * this directory processed. */ if ( autoconvert == SKIP _EM) return( rescan) ; /* Changing of filenames can potentially change the ordering of files
* referenced by sequential incrementing the FDirIndex. To avoid this we first
* scan the entire directory logging all the filenames. Then from this list, the
* files can be sequentially queried and reformatted if necessary, without being
* concerned about a reordering problem.
*/ strncpy ( flname, filename, sizeof (name _t) ) ;
CtoPstr( flname) ;
Fllelnfo.fileParam.ioVRefNum = Volld;
FileInfo.fileParam.ioCompletion = NULL;
Filelnfo.fileParam.loNamePtr = (StringPtr)flname;
Filelnfo.fileParam.ioFDirlndex = 1;
Filelnfo.fileParam.ioDirlD = dirid; for( Err = PBHGetFInfo( &Filelnfo, FALSE); Err ! = fnfErr;
Filelnfo.fileParam.ioFDirlndex++, Filelnfo.fileParam.ioDirlD = dirid, Err = PBHGetFInfo( &Filelnfo, FALSE))
{
/* Open the file, if its a file */ if( !BitTst(&Filelnfo.fileParam.ioFlAttrib, 3))
{
PtoCstr( flname); strncpy( file.filename, flname, sizeof( name_t));
DLAddHead( filelist, (char *)&file, istat);
ChkErrGoto( istat, leave);
CtoPstr( flname);
}
}
DLGet( filelist, (char **)&FP, NULL, TRUE, FALSE, istat); ChkErrGoto( istat, leave); if( Button ())
{ do { vol_info_t vol_info; char buffer[256];
RAW_FILE *raw_file; mfm_base_t base; if( AVGetFileType( Volld, dirid, FP→filename, istat) == MEDIA_FT) { ChkErrGoto( istat, leave); raw_file = (RAW_FILE *) AVOpenFile( Volld, dirid, FP→filename, OPEN_READ, istat);
ChkErrGoto(istat, leave);
(void)AVReadFile( raw_file, (char *) &base, 0, sizeof (mfm_base_t), istat); ChkErrGoto(istat, leave);
/* Check for the magic constant to ensure that this is a valid media file. */ if( base.magic != FILE_MAGIC)
{
/* SOR-RY Beta media file doesn't belong here, move it outta here */ AvGetVolInfo( Volld, &vol_info, istat);
ChkErrGoto( istat, leave); sprintf( buffer, "Found BETA files in Mfiles folder.\nThis version does not support BETA media files.\nP) raw_file→fllename, vol_lnfo.name); auto_request( buffer, "OK", 1);
AVRenameFile( raw file, 0, raw_file→filename, TRUE, istat); ChkErrGoto( istat, leave); } AVCloseFile( raw_file, istat); ChkErrGoto( istat, leave);
} while( DLNext( filelist, (char **)&FP, NULL, istat) && *istat == ISTAT_OK && autoconvert != SKIP_EM); } else { do
{
RAW_FILE *raw_flle; long request; mfm_base_t base; if( AVGetFileType ( Volld, dirid, FP→filename, istat) == MEDIA_FT)
{
ChkErrGoto( istat, leave); raw_file = (RAW_FILE *)AVOpenFilet Volld, dirid, FP→filename, OPEN_READ, istat);
ChkErrGoto(istat, leave);
(void)AVReadFile( raw_file, (char *)&base, 0, sizeof (mfm_base_t), istat); ChkErrGoto(istat, leave);
/* Check for the magic constant to ensure that this is a valid media file. */ if( base.magic != FILE_MAGIC)
{ if ( autoconvert == REFORMAT_IT l l autoconvert == DONT_FORMAT)
{ sprintf( format_notify, "Media file: %5 is of BETA format.\n\nRaformatting makes
FP→fllename);
request = auto_request( format_notify, "Reformat itIReformat ALLIDont formatlSkip em all", 1); switch( request)
{ case 1: autoconvert = REFORMAT_IT; break; case 3: autoconvert = DONT_FORMAT; break; case 2: autoconvert = REFORMAT_ALL; break; case 4: autoconvert = SKIP_EM; break;
} } if( autoconvert == REFORMAT_ALL | | autoconvert == REFORMAT_IT)
{ rescan = TRUE; mfm_format_convert( &raw_file, istat);
ChkErrGoto( istat, leave);
} } AVCloseFile( raw_file, istat); ChkErrGoto( istat, leave); } } while( DLNext( filelist, (char **)&FP, NULL, istat) && *istat == ISTAT_OK && autoconvert != SKIP_EM);
} leave:
DLDelete( filelist, &local) ;
if( local != ISTAT_OK)
{
ChkErrGotot istat, gone); LogErrt istat, local);
} gone: returnt rescan); } /*****************************************************************************************
* NAME: CheckDataBase
*
* Purpose: Check for existance of the Media Database, and if it exists, check its
* write date against the mod date on its parent folder. */ static Boolean CheckDataBase ( volid, dirid, istat) short volid; long dirid; istat_t *istat;
CInfoPBRec Info; name_t name; u_long foldermod; u_long filemod;
OSErr err; strcpy( name, MEDIAFILEDBASE) ; CtoPstr( name);
/* see if the database file exists */ Info.hFilelnfo.ioCompletion = NULL; Info.hFilelnfo.ioFDirlndex = 0; Info.hFilelnfo.ioNamePtr = (StringPtr)name; Info.hFllelnfo.ioDirlD = dirid; Info.hFilelnfo.loVRefNum = volid; err = PBGetCatlnfo( &lnfo, FALSE);
/* if the file isn't there, then we're done. */ if( err == fnfErr) return( FALSE); if( err != noErr) LogErrGoto( istat, err, leave); filemod = Info.hFilelnfo.ioFlMdDat;
/* get the directory modification date */ Info.dirlnfo.ioCompletion = NULL; Info.dirlnfo.ioFDirlndex = -1; Info.dirlnfo.ioNamePtr = NULL; Info.dirlnfo.ioDrDirlD = dirid; Info.dirlnfo.ioVRefNum = volid; err = PBGetCatlnfo( &lnfo, FALSE) ; if( err != noErr) LogErrGoto( istat, err, leave);
/* We know the file exists, and we have the modification date * of the parent directory and file. Compare them. */ foldermod = Info.dirlnfo.ioDrMdDat; return( foldermod == filemod) ; leave: return( FALSE) ; } /***************************************************************************************** * NAME : reducecache
* Purpose: reduce the number of media files that are in the hndl cache . */ static void reducecache ( istat) istat_t *istat;
{ long 1; recordID record = NULL;
MFMCrux_ptr FL; if ( env_read_boolean ( "DOUBLE_MEDIA_CACHE" , istat) ) {
ChkErrGoto( istat, leave) ; env_write boolean ( "DOUBLE_MEDIA_CACHE", FALSE, istat) ; ChkErrGoto( istat, leave) ; HNDL_CACHEsize = HNDL_CACHEsize<<1; if ( HNDL_CACHEsize < 0) HNDL_CACHEsize = HNDL_CACHEsize>>1 ; }
else
{
ChkErrGoto( istat, leave) ;
DLSetThread( mfm_files, mfm_cach, istat) ;
ChkErrGoto( istat, leave) ;
DLSort ( mfm_files, Quicksort, istat) ;
ChkErrGoto( istat, leave) ; fort i=HNDL_CACHEsize>>2; i>0; i--)
{
DLGet{ mfn_files, (char **)&FL, &record, TRUE, FALSE, istat);
ChkErrGoto( istat, leave);
DLRmvFrmThread( mfm_files, mfm_cach, record, istat);
ChkErrGoto( istat, leave); DLRmvFrmThread( mfm_files, mfm_hndl, record, istat); ChkErrGoto( istat, leave); } }
leave: return; }
/*************************************************
* NAME: siftFORhandle
*
* Purpose: with the information given, see if media exists to fulfill
* the request. Return the CRUX of the file if found. */
MEM_CRUX siftFORhandle ( tapeUID, channel, Mtype, start_tlme, end_time, istat) sourceuid_t tapeUID; /* tape date UID */
Mchannel_t channel; /* Physical source channel */
Mtype_t Mtype; /* media type you will wish to read. */ timecode_t start_time; /* Minimum start time read */ timecode_t end time; /* Maκimum end time read */ istat_t *istat;
{ long NumRecs; recordID **records;
MFMCrux_ptr FL; recordID returnID = NULL; recordID searchID; long curr_diff = -1;
Boolean curr_abridged = FALSE; float curr_CFPS = 30.0; istat_t local;
/*
* See if this file is already open for read by searching the queue of open files.
* If already open, just return the file handle.
*
*/
DLSetThreadt mfm_files, mfm_hndl, istat); ChkErrGotot istat, leave); records = DLSift( mfm_files, INITIAL, EQ, (char *)&tapeUID, FALSE, NULL, &NumRecs, istat); ChkErrGoto( istat, leave); records = DLSiftt mfm_files, INTERSECT, EQ, (char *)&Mtype, FALSE, records, &NumRecs, istat); ChkErrGoto( istat, leave); records = DLSiftt mfm_files, INTERSECT, EQ, (char *)&channel, FALSE, records, &NumRecs, istat); ChkErrGoto( istat, leave); records = DLSiftt mfm_files, INTERSECT, LE, (char *)&start_time, FALSE, records, &NumRecs, istat); ChkErrGoto( istat, leave); /* records = DLSiftt mfm_files, INTERSECT, GE, (char *)&end_time, FALSE, records, &NumRecs, istat); ChkErrGoto( istat, leave); */ if ( NumRecs > 0) /* a number (at least one) of already open media files, satisfies the request. */ /* put a scoring algorithm in here, return the "best" media */ recordID *chaser; long diff; short i; HLock ( records); chaser - *records; fort i=0; i<NumRecs; 1++, chaser++) {
DLGet( mfm_files, (char **)&FL, chaser, FALSE, FALSE, istat); ChkErrGoto( istat, recordlockleave); diff = diff_time( end_time, FL→header.disk.end_time); if( diff >= 0) { /* if i have one already and its abridged then if the new one is not abriged use it
* / lf( returnID != 0 && curr_abridged)
{ if( FL→Ftype == FULL)
{ curr_abridged = FALSE; curr_diff = diff; returnID = *chaser; FL→header.disk.touch_ticks = TickCount();
} } else if( diff > curr_diff II curr_CFPS < 30.0 II curr_abridged)
/* If 1 have one already and we are looking for video then if the one I have does not have a 1:1 capture mask then compare capture rates use one with the higher capture rate
*/ if( returnID != 0 && Mtype == video_cm8) { lf( curr_CFPS < FL→header.disk.specific.video_cm8.capture. rate) { curr_CFPS = FL→header.disk.specific.video_cm8.capture. rate; curr_diff = diff; returnID = *chaser;
FL→header.disk.touch_ticks = TickCount();
}
} else
{ if( Mtype == video_cm8) curr_CFPS = FL→header.disk. specific.video_cm8.capture.rate; curr_diff = diff; curr_abridged = FL→Ftype == ABRIDGED; returnID = *chaser; FL→header.disk.touch_ticks = TickCount(); } } } }
}
HUnlock( records);
AvDisposeHandle ( (Handle) records, istat); if ( NumRecs > 0 && returnID != 0)
{
DLGet ( mfm_files, (char **) &FL, treturnID, FALSE, FALSE, istat) ;
ChkErrGoto( istat, leave) ;
FL→header.disk.touch_ticks = TickCount () ;
}
}
/* this statement should be In here when the last DLSift above is working again. At that point this else clause will work on the if ( NumRecs > 0) statement, -reber 10/31/89 else if( NumRecs == 0) { */ ift returnID == 0 II curr_abridged I I curr_CFPS < 30.0)
{ searchID = returnID;
/* no open file fulfills request. Lets try and find one on the disk resident list */
DLSetThreadt mfm_files, mfm_disk, istat); ChkErrGoto( istat, leave); records = DLSift( mfm_files, INITIAL, EQ, (char *)&tapeUID, FALSE, NULL, &NumRecs, istat); ChkErrGoto( istat, leave); records = DLSift( mfm_files, INTERSECT, EQ, (char *)&Mtype, FALSE, records, &NumRecs, istat); ChkErrGoto( istat, leave records = DLSift( mfm_files, INTERSECT, EQ, (char *)&channel, FALSE, records, &NumRecs, istat); ChkErrGoto( istat, leave records = DLSift( mfm_files, INTERSECT, LE, (char *)&start_time, FALSE, records, &NumRecs, istat); ChkErrGoto( istat,
/* records = DLSift( mfm_files, INTERSECT, GE, (char *)&end_tlme, FALSE, records, &NumRecs, istat); ChkErrGoto( istat, leave */ if( NumRecs > 0)
{ recordID *chaser; long dlff; short 1;
HLock ( records); chaser = *records; for( 1-0; i<NumRecs; i-H-, chaser++)
{
DLGet( mfm_files, (char **)&FL, chaser, FALSE, FALSE, istat);
ChkErrGoto( istat, recordlockleave); diff = diff_tlme( end tlme, FL→header.disk.end_tlme); ift diff >= 0)
{
/* if i have one already and its abridged then
if the new one is not abriged use it */ if ( searchID != 0 && curr_abridged) { if ( FL→Ftype == FULL)
{ curr_abridged = FALSE; curr_diff = diff; searchID = *chaser;
FL→header.disk.touch_ticks = TickCount () ;
} } else { if( diff > curr_diff II curr_CFPS < 30.0 || curr_abridged)
{ /* If i have one already and we are looking for video then if the one I have does not have a 1:1 capture mask then compare capture rates use one with the higher capture rate */ if( searchID != 0 && Mtype == video_cm8) if( curr_CFPS < FL→header.disk.specific.video_cm8.capture.rate) curr_CFPS = FL→header.disk.specific.video_cm8.capture.rate; curr_diff = diff; searchID = *chaser;
FL→header.disk.touch_ticks = TickCount(); } } else
{ if( Mtype == video_cm8) curr_CFPS = FL→header.disk.specific.video_cm8.capture. rate; curr_diff = diff; curr_abridged = FL→Ftype == ABRIDGED; searchID = *chaser; FL→header.disk.touch_ticks = TickCount();
} } } } }
HUnlock( records);
AvDisposeHandle( (Handle) records, istat); if(( returnID != searchID) II ((returnID == NULL) && (searchID ! = NULL)))
{ short count; returnID = searchID;
DLSetThread( mfm_files, mfm_hndl, istat);
ChkErrGoto( istat, leave); count = DLCount( mfm_files, STD_CHECK) ; if( DLCount( mfn_files, STD_CHECK) == HNDL_ CACHEsize)
{ reducecache( istat);
ChkErrGoto( istat, leave);
} ChkErrGoto( istat, leave);
DLAddToThread( mfm_flies, mfm_hndl, (recordID) returnID, istat) ChkErrGoto( istat, leave);
DLAddToThread( mfm_flies, mfm_cach, (recordID) returnID, istat) ChkErrGoto( istat, leave); } }
}
DLSetThread( mfm_files, MAINTHREAD, istat);
ChkErrGoto( istat, leave); return ( (MFM_CRUX) returnID); recordlockleave:
HUnlock( records);
AvDisposeHandle ( (Handle) records, &local); lf( local != ISTAT_OK && *istat == ISTAT_OK) LogErr( istat, local), leave: return ( NULL) ; }
sourceman . c
Saturday, December 2, 1989 3 : 10 PM
/* * Module Name: sourceman.c * * Module Description: Source management module * * Revision History: * * $Log: Engineering:PVCS:Sources:sourceman.c_v $ * * Rev 1.22 02 Dec 1989 15:10:28 SJR * exit film structs and calls, fix Black and Silence in source dialog * * Rev 1.21 30 Nov 1989 13:55:02 SJR * incorporate low memory incremental cluster file writes. Not yet tested. * * Rev 1.20 22 Nov 1989 17:09:44 SJR * film struct in sourcemanager * * Rev 1.19 16 Nov 1989 16:41:02 SJR * incremental cluster writes for low memory cases *
* Rev 1.18 15 Nov 1989 16:01:40 SJR * rewrote mfm_handle to correctly use muliple tapenames from SMRelated* * Rev 1.17 13 Nov 1989 10:39:20 SJR * use unique_id linklist key type for the uid_t field on the uid thread* * Rev 1.16 10 Nov 1989 16:00:04 SJR * was traversing the tapename list in the wrong direction - exitProc * * Rev 1.15 10 Nov 1989 15:32:24 SJR * fixed 1 count in exitProc * * Rev 1.14 10 Nov 1989 10:17:38 SJR * code review corrections and N media files * * Rev 1.13 06 Nov 1989 17:26:04 SJR * fixed return value in get_recordID_uid * * Rev 1.12 06 Nov 1989 17:16:42 SJR * SmRelated always returns the correct count. * * Rev 1.11 06 Nov 1989 12:22:28 SJR * support uid_t *
* Rev 1.9 03 Nov 1989 13:04:06 SJR * got rid of getting uid by name in sourcemanager * * Rev 1.8 31 Oct 1989 18:10:50 SJR * fixed sourceman with a hack using SMGetUid in the SuperDlalog * interaction. * * Rev 1.7 31 Oct 1989 17:53:14 stan * * Rev 1.6 31 Oct 1989 15:57:44 stan * fix ctopstr bug (boxes before tape name) * * Rev 1.5 31 Oct 1989 10:37:28 SJR * rewrite SMCloseNWrite and SMReadTable * * Rev 1.4 24 Oct 1989 12:00:00 SJR * support Source Manager * * Rev 1.3 19 Oct 1989 16:48:46 SJR * fixes to enable opening bins and playing * * Rev 1.2 16 Oct 1989 10:32:36 SJR * Set the thread before the Sift * * Rev 1.1 16 Oct 1989 00:47:14 SJR * late night mfm integration. * * Rev 1.0 12 Oct 1989 09:26:46 SJR * Initial revision. * * /--------------------------------------------------------------------------------------------------------------- --\ * I The following programs are the sole property of Avid Technology, Inc., | * I and contain its proprietary and confidential information. | * I Copyright © 1989, Avid Technology | * /--------------------------------------------------------------------------------------------------------------- --\ */
#include <ctype.h> #Include "avidbase.h" #include "istat.h" #include "memrtns.h" #include "sourceman.h" #include "DialogUtils.h" #include "ResourceDefs.h" #include "linklist.h" #include "sourceman_pvt.h" #include "tools.h" #include "ctabrtns.h" #Include "Macutils.h" #include "cluster.h" #include "uid.h"
#define SRM_RELATIONS "SMRelate" #define SRM_NAMES "SMNames"
/* hack hack hack */
/* Definite protocol violation here. Sourcemnager should know nothing of which uids belong to
* Black and Silence. There exists the condition however that the tape dialog puts up a list of
* all the known source names for selection in a tape dialog. Need to eliminate Black and
* Silence from this list. Actually I think the whole concept of certain static slides existing
* and how we handle them needs to be designed, -reber 12/02/89
These #defines were stole from mfm.c. They should be eliminated from this module. DO NOT USE THEM
*/ #define BLACK_UIDHI 0xA199B9A9 /* uids for Black and Silence */ #define BLACK_UIDLO 0x00166C6E #define SILENCE_UIDHI 0xA199BA9F #define SILENCE_UIDLO 0x0016A635
/***** External Procedures *****/ /***** External variables *****/ /****** Global variables *****/
/***** Static Variables *****/ static listID sm_List; /* LinkList listID for source manager table */ static threadlD CompressThread; /* Thread used in compressing list of uids * fed in from client when writing relations to disk */ static threadlD CompressRelatesThread; /* Thread used to contain list of all uids
* reference in main list record relation chains. */ static threadlD uld_thrd; /* Thread of entries sorted on uid */ static listID sm_relations; /* LinkList of source relations */
/*************************************************
*
* Source Manager error message table. */ static char *sm_errmsg[] =
{
"source name entered is not in valide ascii set",
"duplicate source uida exist in the source manager table",
"the source uid does not exist",
"source name does not exist",
"Source Manager MAGIC # mismatch",
"List Corruption",
}; errmsg_t sm_errors = { sm_errmsg, (sizeof (sm_errmsg) /sizeof (char *)), "SRCM"
};
/***** Defined Below *****/ static Boolean isNameValidt name_t nameStr); static recordID addNamet name_t sourcename, sourceuid_t *uid, istat_t *istat); static recordID getrecordlD_uid( sourceuid_t uid, istat_t *istat);
/*************************************************************/
/**** Public Code ****/
/*******************************************************/
/************************************************
* NAME: SMInit
*
* FUNCTION: Intialize the data structures for the source manager. */
SMInit ( istat) istat_t *istat;
{ sm_entry_t offsets; add_error_subsystem( ISTAT_SRCM, &sm_errors) ;
ResetErr t istat) ; sm_llst = DLCreate( sizeof ( sm_entry_t), istat); ChkErrGoto( istat, leave);
CompressThread = DLCreateThread( sm_list, FALSE, FALSE, istat); ChkErrGoto( istat, leave); uid_thrd = DLCreateThread( sm_list, TRUE, TRUE, istat); ChkErrGoto( istat, leave);
DLSetThread( sm_list, uid_thrd, istat); ChkErrGoto( istat, leave);
DlAddKey( sm_list, unlque_id, CFIELD_OFFSET( uid, &offsets), TRUE, istat); ChkErrGoto( istat, leave);
DLSort( sm_list, Quicksort, istat); ChkErrGoto( istat, leave);
DLSetThread( sm_list, MAINTHREAD, istat);
ChkErrGoto( istat, leave); sm_relations = DLCreate( sizeof ( SMrelations_t), istat);
ChkErrGoto( istat, leave);
CompressRelatesThread = DLCreateThread( sm_relations, FALSE, FALSE, istat); ChkErrGotot istat, leave); leave: return; }
/*************************************************
* NAME: SMRelated
*
* FUNCTION: Determine the relation between tapename and uid fed in with other
* tapenames in the tape database. Return a handle to a list of
* tapes_t. Count is number of tapes_t records on the handle.
*
* NOTE:
* Caller is responsible for Disposing of the handle.
* /
SMrelatlons_hdl SMRelated( sourceUID, start, end, count, istat) sourceuid_t sourceUID; timecode_t start; timecode_t end; long *count; lstat_t *istat;
{
SMrelations_hdl sourcelist = NULL; recordID **records = NULL; long NumRecs; istat _t local;
ResetErr ( istat) ; /* at seme point REAL SOON this code is to at least do some interaction with the actual * tape database. At this time I'm trying to get the new mfm to compile, and I just do * not have the time or the motivation to go off on this tangent right now. The tape * database at this time is simply an Apple Menu, and it needs to be converted over to * linklist.c routines, in order for this routine to Sift or whatever. There will also * need to be a Ul routine to the user so that relations can be established. It is those * relations that need to be examined here, so that a list of tapes can be returned. Even * after that functionality is in place, the mfm routines will need to be changed. Right * now they will note a multiple tapenames coming back and throw up an auto_request in such * a case. So.... for now just return what we were passed in the new data structure. HEY! * at least I put the protocol in place! Not much in code, but its the design that counts.., * Sigh.... -reber 9/1/89 */
DLSetThread( sm_list, uid_thrd, istat);
ChkErrGoto( istat, leave); records = DLSift( sm_list, INITIAL, EQ, (char *) &sourceUID, FALSE, NULL, &NumRecs, istat) ;
ChkErrGoto( istat, thrdleave);
*count = 0; switch ( NumRecs)
{ case 1: { sm_entry_ptr ET; SMrelations_ptr ETRel; SMrelations_ptr SLchaser;
long relationscount;
DLGet( sm_List, (char **)&ET, *records, FALSE, FALSE, istat);
ChkErrGoto( istat, thrdleave); if( ET→relations != NULL)
{
DLSetThreadt sm_relations, ET→relations, istat);
ChkErrGotot istat, leave); relationscount = DLCountt sm_relatlons, istat); ChkErrGotot istat, leave); sourcelist = (SMrelations_hdl)AvNewHandle( sizeof ( SMrelations_t) * relationscount, istat); ChkErrGotot istat, leave);
HLock ( sourcelist);
SLchaser = *sourcelist;
DLGet( sm_relations, (char **)&ETRel, NULL, TRUE, FALSE, istat);
ChkErrGoto( istat, leave); do
{
/* Check to see that the time range fits for the source */ if( diff_tlme( ETRel→uidl_start, start) >- 0 && diff_time( end, inc_tlme( ETRel→uidl_start, ETRel→Nframes) ) >= 0) {
/* we know the time range being requested for a relation fits inside the * timecode range of source for this particular relation */
/* copy the relation struct into the handle */
*SLchaser = "ETRel;
SLchaser++;
*count++;
}
} while( DLNext( sm relations, (char **)&ETRel, NULL, istat) && *istat =
_ = ISTAT OK); _
HUnlock( sourcelist); if( *count == 0)
{
AvDisposeHandle ( (Handle) sourcelist, STD_CHECK); sourcelist == NULL; } } break;
} case 0:
{
LogErrGoto( istat, SRM_NO_SUCH_UID, leave); break; } default: {
LogErrGoto( istat, SRM_DUPLICATE_SOURCE_UIDS, leave); break; } } thrdleave: if( records != NULL) AvDisposeHandle ( (Handle) records, &local); DLSetThread( sm_list, MAINTHREAD, &local); leave: returnt sourcelist); } /********************************************************************
* NAME: SMAddName
* FUNCTION: Add the name to the source manager list.
*
* NOTE:
* If uid points to NULL a new UID will be generated. If the uid
* points to a nonzero value, the name and uid will be added as a pair. */ void SMAddName ( sourcename, uid, istat) name_t sourcename; sourceuid_t *uid; istat_t *istatl { ResetErr( istat);
if( !isNameValid( sourcename))
LogErrGoto( istat, SRM_INVALID_NAME, leave); addName( sourcename, uid, istat); leave: return; }
/************************************************
* NAME: SMSetName
*
* FUNCTION: Given a sourceuid, cough up the sourcename. */ void SMGetName( uid, name, istat) sourceuid_t uid; name_t name; istat_t *istat;
{ sm_entry_ptr ET; recordID record; istat_t local;
name[0] = '\0'; record = getrecordlD_uid( uid, istat); ChkErrGoto( istat, leave);
DLGet( sm_list, (char **)&ET, &record, FALSE, FALSE, istat); ChkErrGoto( istat, leave); strncpyt (char *)name, (char *)&ET→name, sizeof ( name_t)); leave: return;
}
/********************************************************************************
* NAME: SMGetUid
*
* FUNCTION: Given a sourcename, cough up the source uid.
* Reber says - this goes away real soon. Don't use it. */
void SMGetUid( uid, name, istat) sourceuid_t *uid; name_t name; istat_t *istat; { recordID **records; long NumRecs; sm_entry_ptr ET; sm_entry_t offsets; threadlD names; istat_t local; names = DLCreateThread( sm_list, TRUE, TRUE, istat); ChkErrGoto( istat, leave);
DLSetThread( sm_list, names, istat); ChkErrGoto( istat, leave);
DLAddKey( sm_list, character, CFIELD_OFFSET( name, &offsets), TRUE, istat); ChkErrGoto( istat, leave);
DLSort( sm_list, Quicksort, istat); ChkErrGoto( istat, leave); records = DLSift( sm_list, INITIAL, EQ, (char *)name, FALSE, NULL, &NumRecs, istat);
ChkErrGoto( istat, thrdleave); if( NumRecs == 0) LogErrGoto( istat, SRW_NAME_NOT_FOUND, thrdleave);
HLock ( records);
DLGet( sm_list, (char **)&ET, "records, FALSE, FALSE, istat);
HUnlockt records);
ChkErrGoto( istat, thrdleave);
AvDisposeHandle ( (Handle) records, istat); ChkErrGoto( istat, thrdleave);
*uid = ET→uid; thrdleave:
DLSetThread( sm_list, MAINTHREAD, &local); DLRemoveThread( sm_list, names, &local); leave: return; } /****************************************************************************************
* NAME: SMOpenTable
*
* FUNCTION: Open a table to enable setting up a list of
* UIDs to be written to disk. */
SMTableid SMOpenTable ( istat) istat_t *istat;
{ return( DLCreate( sizeof ( SMTable_t) , istat));
}
/*************************************************
* NAME: SMBuildTable
*
* FUNCTION: Build a table of UIDs to be written to disk.
*
*/ void SMBuildTable ( tableid, uid, istat) SMTableid tableid; sourceuid_t uid; istat_t *istat;
{
SMTable_t record;
getrecordlD_uid( uid, istat); ChkErrGoto( istat, leave); record.uid = uid;
DLAddHead( tableid, (char *) trecord, istat); ChkErrGoto( istat, leave); leave: return; }
/************************************************
* NAME: SMCloseNWriteTable
*
* FUNCTION: Write Source Manager UID table out to file.
*
*/
void SMCloseNWriteTable ( tableid, cluster, istat)
SMTableid tableid; cluster_t *cluster; istat_t *istat;
{ sourceuid_t prevUID;
SMTable_ptr TID; long TotalRelates; long sizeneeded; recordID MainListRecordlD;
Boolean nuffmemory;
/* first we must traverse the client built table, and move only the unique source uids * onto a compressed thread. Compressed thread has no duplicate entries. */
/* Clean out the compress Threads - there may be leftovers from previous writes */
DLSetThread( sm_list, CompressThread, istat);
ChkErrGoto( istat, leave);
DLCleanThread( sm_list, CompressThread, istat);
ChkErrGoto( istat, leave);
DLCleanThread( sm_list, CompressRelatesThread, istat);
ChkErrGoto( istat, leave);
/* previous processed uid = 0 */ MakeNllUid( &prevUID);
/* fort all the records in the client built table) */ DLGet( tableid, (char **)&TID, NULL, TRUE, FALSE, istat); ChkErrGoto( istat, leave); do
{ sm_entry_ptr MLID;
/* if the clienttable record uid is not equal to the previous processed uid */ lf( !EqualUid( &TID→uid, &prevUID))
{
/* previous processed uid = client table record uid */ prevUID = TID→uid;
/* get the main list record ID for the uid in the client table record */ MainListRecordID = getrecordID_uld( TID→uid, istat); ChkErrGoto( istat, leave);
/* add the main list record ID to the compressed thread */ DLAddToThread( sm_list, CompressThread, MainListRecordID, istat); if( *istat == LNK_RECORD_EXISTS) ResetErr( istat); else ChkErrGoto( istat, leave);
/* if there are relations attached to main list record ID */ DLGet( sm_list, (char **)&MLID, &MainListRecordID, FALSE, FALSE, istat); ChkErrGoto( istat, leave); if( MLID→relations != NULL) recordID RelateRecordlD = 0; SMrelations_ptr REL;
/* note relates were found - determined by TotalRelates being nonzero */
/* traverse the relates thread found in the main list record */ DLSetThreadt sm_relations, MLID→relations, istat); ChkErrGoto( istat, leave);
DLGet( sm_relations, (char **)&REL, &RelateRecordID, TRUE, FALSE, istat);
ChkErrGoto( istat, leave); do
{
DLAddToThread( sm_relations, CompressRelatesThread, RelateRecordID, istat); if( *lstat ==LNK_RECORD_EXISTS) ResetErr( istat); else ChkErrGoto( Istat, leave);
} whilet DLNext( sm_relations, (char **)&REL, &RelateRecordID, istat) && *istat ==ISTAT_OK);
ChkErrGoto( istat, leave);
}
}
/* end for statement */
} whilet DLNext( tableid, (char**) &TID, NULL, istat) && *istat == ISTAT_OK) ;
ChkErrGoto( istat, leave);
DLSetThread( sm_relations, CompressRelatesThread, istat); ChkErrGoto( istat, leave);
TotalRelates = DLCount( sm_relations, istat); ChkErrGoto( istat, leave);
/* if relates were found */ if( TotalRelates)
{
SMDiskRelates_hdl RelateSpace;
SHDiskRelates_ptr RELhdr;
SMrelations_ptr RELspace;
SMrelations_ptr RELrecord;
/* allocate space based on total relates */ sizeneeded = sizeof ( SMrelations_t) * (TotalRelates - 1) + sizeof ( SMDlskRelates_t); nuffmemory = AvChkNewHandle ( sizeneeded, istat); ChkErrGotot istat, leave); if( nuffmemory) { RelateSpace = (SMDiskRelates_hdl)AvNewHandle( sizeneeded, istat); ChkErrGotot istat, leave); } else { RelateSpace = (SMDiskRelates_hdl)AvNewHandle( sizeof ( SMDiskRelates_t), istat); ChkErrGoto( istat, leave); CMUpdatelnfoStart( cluster, SRM_RELATIONS, sizeneeded, istat);
ChkErrGoto( istat, leave);
}
HLock ( RelateSpace);
RELhdr = *RelateSpace;
RELhdr→magic = SMAN_MAGIC;
RELhdr→revision = SMAN_REVISION;
RELhdr→num_relates = TotalRelates;
RELspace = (SMrelations_ptr) &RELhdr→entries; if( !nuffmemory)
{
CMUpdateInfoIncr( cluster, *RelateSpace, (long) ((char * ) &RELhdr→entries - (char *) &RELhdr→magic), istat);
ChkErrGoto( istat, leave);
HUnlock( RelateSpace);
AvDisposeHandle( (Handle)RelateSpace, istat);
ChkErrGotot istat, leave);
/* casting is incorrect, but we know what we are doing......... */
RelateSpace = (SMDiskRelates_hdl)AvNewHandle( sizeof ( SMrelations_t), istat);
ChkErrGotot istat, leave);
HLock ( RelateSpace);
RELspace = (SMrelations_ptr) *RelateSpace;
}
/* traverse the compressed relates thread */
DLGet( sm_celations, (char **) &RELrecord, NULL, TRUE, FALSE, istat);
ChkErrGoto( istat, leave); do
{
/* for each record found */
/* write the specific relates data into the allocated space for each relation*/
*RELspace ++ = *RELrecord;
if( !nuffmemory)
CMUpdatelnfolnor( cluster, *RelateSpace, sizeof ( SMrelations_t), istat);
ChkErrGoto( istat, leave);
RELspace = (SMrelations_ptr) *RelateSpace; } /* add instance for each uid mentioned in a relate record to the main list compressed * thread. Compress as we go via the record already exists error condition */
MainListRecordID = getrecordID_uid( RELrecord→uidl, istat);
ChkErrGoto( istat, leave);
DLAddToThread( sm_list, CompressThread, MainListRecordID, istat); lf( *istat == LNK_RECORD_EXISTS) ResetErr( istat); else ChkErrGoto( istat, leave);
MainListRecordID = getrecordlD_uid( RELrecord→uid2, istat); ChkErrGoto( istat, leave);
DLAddToThread( sm_list, CcmpressThread, MainListRecordID, istat); if( *istat ==LNK_RECORD_EXISTS) ResetErr( istat); else ChkErrGoto( istat, leave);
} whilet DLNext( sm_relations, (char **)&RELrecord, NULL, istat) && *istat == ISTAT_OK);
if( nuffmemory)
{
/* write the cluster to disk */
UpdateClusterInfo ( cluster, SRM_RELATIONS, *RelateSpace, sizeneeded, istat); ChkErrGotot istat, leave);
} else
{
CMUpdatelnfoEnd( cluster, istat);
ChkErrGoto( istat, leave);
}
HUnlock( RelateSpace);
AvDisposeHandle ( (Handle)RelateSpace, STD_CHECK),
}
/* write the compressed thread records to disk as the name/uid table */
{
SMDiskTable_hdl TableSpace;
SM)lskTable_ptr TBLhdr;
SMTable_ptr TBLrecord;
SMTable_ptr TBLentries; long num_entries;
DLSetThreadt sm_list, CompressThread, istat); ChkErrGoto( istat, leave); num_entries = DLCountt sm_list, istat);
ChkErrGoto( istat, leave); sizeneeded = sizeof ( SMTable_t) * (num_entries - 1) + sizeof ( SMDiskTable_t) ; nuffmemory = AvChkNewHandle( sizeneeded, istat);
ChkErrGoto( istat, leave); if( nuffmemory)
{
TableSpace = (SMDiskTable_hdl)AvNewHandle( sizeneeded, istat) ; ChkErrGoto( istat, leave) ; } else
{
TableSpace = (SMDiskTable_hdl)AvNewHandle( sizeof( SMTable_t), istat);
ChkErrGoto( istat, leave);
CMUpdateInfoStart( cluster, SRM_NAMES, sizeneeded, istat);
ChkErrGoto( istat, leave);
}
HLock( TableSpace); TBLhdr = *TableSpace; TBLhdr →magic = SMAN_MAGIC; TBLhdr→revlsion = SMAN_REVISION; TBLhdr→ num_entries = num_entries; TBLentries = (SMTable_ptr)&TBLhdr→entries;
/* hack hack this code needs to be a lot smarter about handling disk full error conditions.
* Hopefully get to this before FCS FCS. -reber 11/29/89
*/ if( !nuffmemory)
{
CMUpdateInfolncr( cluster, *TableSpace, (long) ((char *)&TBLhdr→entries - (char *)&TBLhdr→magic), istat);
HUnlock( TableSpace);
ChkErrGoto( istat, leave);
AvDisposeHandle( (Handle)TableSpace, istat);
ChkErrGotot istat, leave);
/* casting is incorrect, but we know what we are doing. .... */
TableSpace = (SMDiskTable_hdl)AvNewHandle( sizeof ( SMTable_t), istat);
ChkErrGoto( istat, leave);
HLock ( TableSpace);
TBLentries = (SMTable_ptr) *TableSpace;
}
/* traverse the compressed relates thread */
DLGet( sm_list, (char **) &TBLrecord, NULL, TRUE, FALSE, istat);
ChkErrGotot istat, leave); do
{
/* for each record in the thread */
/* write the data into the handle space to be written to the disk cluster file */
*TBLentries++ = *TBLrecord; if( !nuffmemory) {
CMUpdatelnfoIncr( cluster, *TableSpace, sizeof ( SMTable_t), istat);
ChkErrGoto( istat, leave);
TBLentries = (SMTable_ptr) *TableSpace;
}
} while ( DLNext( sm_list, (char **) &TBLrecord, NULL, istat) && *istat == ISTAT_OK); ChkErrGoto( istat, leave);
if( nuffmemory)
{
/* write the cluster to disk */
UpdateClusterInfo( cluster, SRM_NAMES, *TableSpace, sizeneeded, istat);
ChkErrGoto( istat, leave);
} else
{
CMUpdatelnfoEnd( cluster, istat);
ChkErrGotot istat, leave);
}
HUnlock( TableSpace);
AvDisposeHandle( (Handle)TableSpace, STD_CHECK);
} leave : return; } /***************************************************
* NAME: SMReadNTable
*
* FUNCTION: Read Source Manager UID table from file.
*
*/ void SMReadNTable ( cluster, istat) cluster_t *cluster;
istat_t *istat;
{ long sizeneeded; long i;
SMDiskTable_hdl TableSpace; SMDiskTable_ptr TBLhdr; SMTable_ptr TBLentries;
SMDiskRelates_hdl RelateSpace; SMDiskRelates_ptr RELhdr; SMrelations_ptr RELentries; SMrelations_ptr RELrecord; sizeneeded = GetClusterlnfoLen ( cluster, SRM_NAMES, istat); ChkErrGoto( istat, leave);
/* if there is any sourcemanager information to read in */ if( sizeneeded)
{
TableSpace = (SMDiskTable_hdl)AvNewHandle( sizeneeded, istat);
ChkErrGoto( istat, leave);
HLock ( TableSpace); TBLhdr = *TableSpace;
ReadClusterInfo ( cluster, SRM_NAMES, TBLhdr, sizeneeded, istat); ChkErrGoto( istat, leave);
/* hack hack */
/* Should be checking revision number here and doing reformat if necessary. */ if( TBLhdr→magic != SMAN_MAGIC) LogErrGotot istat, SRM_BAD_FILE_HANDLE, leave);
TBLentries = (SMTable_ptr) &TBLhdr→entries; fort i=TBLhdr→num_entries; i>0; i-, TBLentries++)
{ addName( TBLentries→name, &TBLentries→uid, istat);
ChkErrGoto( istat, leave);
} HUnlock( TableSpace); AvDisposeHandle ( (Handle)TableSpace, STD_CHECK) ; sizeneeded = GetClusterlnfoLen( cluster, SRM_RELATIONS, istat); ChkErrGoto( istat, leave);
if( sizeneeded)
{
/* there are relation records to read in */
RelateSpace = (SMDiskRelates_hdl)AvNewHandle( sizeneeded, istat);
ChkErrGoto( istat, leave);
HLock ( RelateSpace); RELhdr = *RelateSpace;
ReadClusterlnfo( cluster, SRM_RELATIONS, RELhdr, sizeneeded, istat); ChkErrGoto( istat, leave);
/* hack hack */
/* Should be checking revision number here and doing reformat if necessary. */ if( RELhdr→magic != SMAN_MAGIC) LogErrGotot istat, SRM_BAD_FILE_HANDLE, leave);
DLSetThread( sm_relations, MAINTHREAD, istat);
ChkErrGoto( istat, leave);
RELentries = (SMrelations_ptr) &RELhdr→entries; for ( i=RELhdr→num_relates; i>0; i - , RELentries++)
{
SMSetRelate ( RELentries, istat);
ChkErrGoto( istat, leave);
}
HUnlock ( RelateSpace);
AvDisposeHandle ( (Handle)RelateSpace, STD_CHECK);
}
} leave: return; }
/********************************************************
* NAME: SMSetRelate *
* FUNCTION: Add the relation information to the SMrelations list.
*
*/ void SMSetRelate ( relation, istat)
SMrelatlons_ptr relation; istat_t *istat; sm_entry_ptr ET; recordID siblingRecord; long NunRecs; recordID **SiftRecords;
SiftRecords = DLSift( sm_relations, INITIAL, EQ, (char *) &relation→uid1, FALSE, NULL, &NumRecs, istat); ChkErrGoto( istat,
SiftRecords = DLSift( sm_relations, INITIAL, EQ, (char *)trelation→uid2, FALSE, NULL, &NumRecs, istat); ChkErrGoto( istat,
SiftRecords = DLSift( sm_relations, INTERSECT, EQ, (char *) trelation→uidl_start, TRUE, NULL, &NumRecs, istat); ChkErrGoto
SiftRecords = DLSift( sm_relations, INTERSECT, EQ, (char *) trelation→uid2_start, TRUE, NULL, &NumRecs, istat); ChkErrGoto ift NumRecs > 1) LogErrGotot istat, SRM_LIST_CORRUPTION, leavedispose) ; if( NumRecs == 1)
{
SMrelations_ptr NframesChange;
/* Already have a relation starting at specified timecode for this
* sibling. See which relation has longer number of frames for length,
* and update to that number. */
HLock ( SiftRecords);
DLGet( sm relations, (char **)&NframesChange, *SiftRecords, FALSE, FALSE, istat);
HUnlock( SiftRecords);
ChkErrGoto( istat, leavedispose); if ( NframesChange→Nframes < relation→Nframes) NframesChange→Nframes = relation→Nframes;
} else
{
DLAddHead( sm_relations, (char *) relation, istat);
ChkErrGoto( istat, leavedispose);
} leavedispose:
AvDisposeHandle ( (Handle) SiftRecords, STD_CHECK); leave: return;
}
static SuperItem iList[] = { ok_button, DEFAULT_BUT, 0, 0, 0, 0,
NEWTEXT_ITEM, 0, 0, 0, 0, 0,
NEWBUT_ITEM, 0, 0, NewHitRtn, 0, 0,
EXISTING_ITEM, 0, 0, OldHitRtn, 0, 0,
LIST_ITEM, 0, (ProcPtr)DrawList, ClickList, 0, 0,
-1, 0, 0, 0, 0, 0);
/************************************************
* NAME: SMNamaDialog
*
* FUNCTION: Put up the Source Name Dialog and get a sourceuid.
*
*/ void SMNameDialog( prompt, uid, istat) char *prompt; sourceuid_t *uid; istat_t *istat;
{ name_t name; short i;
ResetErr( istat); for( i=0; i<sizeof( name_t); i++, name [i] = '\0'); iList [NEWTEXT_INDEX] .valPtr = (char *)name; iList [LIST_INDEX].valPtr = (char *)uld; /* the list entry */
CtoPstr( prompt);
ParamText( prompt, "\000", "\000", "\000");
PtoCstr( prompt);
SuperDlalog ( 200, iList, setupProc, exitProc, (ProcPtr)myFilter, 0) ; if ( dialogmode == CREATING_NEW_ENTRY)
{
MakeNilUid( uid);
SMAddName( name, uid, istat);
ChkErrGoto( istat, leave);
}
leave : return; }
/************************************************
* NAME: setupProc
*
* FUNCTION: setup the source name dialog on request from SuperDialog
*/ static setupProc ( dlalptr) DialogPtr dlalptr;
{ int iType, 1;
Handle iHdl;
Rect iBox;
Rect tRect, databounds; sm_entry_ptr ET;
Point call; uid_t BLACK; uid_t SILENCE;
GetDItem( dlalptr, LIST_ITEM, &iType, &iHdl, &iBox) ; tRect = iBox; tRect.right -= 15;
SetRect( &databounds, 0, 0, 1, 0); tlistHdl = LNew( &tRect, &databounds, 0, 0, dialptr, TRUE, FALSE, FALSE, TRUE);
(*tlistHdl) →selFlags += 10nlyOne; cell.h = 0; cell.v = 0;
BLACK.high = BLACK_UIDHI; BLACK. low = BLACK_UIDLO; SILENCE.high = SILENCE_UIDHI; SILENCE.low = SILENCE_UlDLO; if( DLGet( sm_list, (char **)&ET, NULL, TRUE, FALSE, STD_CHECK)) {
do
{ if( ! (EqualUid( &ET→uid, &BLACK) I I EqualUid( &ET→uid, &SILENCE)))
{
LAddRow( 1, 0, tlistHdl);
LSetCell(ET→name, strlen(ET→name), cell, tlistHdl);
} } while ( DLNext( sm_list, (char **)&ET, NULL, STD_CHECK) ) ;
LSetSelect( TRUE, cell, tlistHdl); }
InvalRect( &tRect);
HideDItem( dialptr, NEWTEXT_ITEM) ; HideDItem( dialptr, NEWPROMPT_ITEM) ; dialogmode = SELECT_FROM_EXISTING; if( DLCount( sm_list, STD_CHECK) == 0)
NewHitRtn( 0, iBoκ, 0); /* hit the new button */
HiliteDItem( thePort, EXISTING_ITEM, 255);
IList [NEWTEXT_INDEX].valPtr[0) = EOS; /* the edittext item */ }
/*********************** **** ************ *** **** **
* NAME: exitProc
*
* FUNCTION: The Source name dialog is going down, take appropriate action
*
*/ static exitProc ( dialptr) DialogPtr dialptr; { int iType;
Handle iHdl;
Rect iBox; name_t name;
Point cell; sm_entry_ptr ET; short i; uid_t BLACK; uid_t SILENCE;
if( dialogmode == SELECT_FROM_EXISTING)
{ cell.h = 0; cell.v = 0;
LGetSelect( TRUE, &cell, tlistHdl);
BLACK.high = BLACK_UlDHI; BLACK.low = BLACK_UIDLO; SILENCE.high = SILENCE_UIDHI; SILENCE.low = SILENCE_UIDLO;
DLGet( sm_list, (char **)&ET, NULL, FALSE, TRUE, STD_CHECK); for( i=cell.v; 1>0; i—)
{ if( !(EqualUid( &ET→uid, &BLACK) || EqualUid( &ET→uid, &SILENCE)))
DLPrevt sm_list, (char **)&ET, NULL, STD_CHECK) ; else i++;
}
*(uid_t *) iList [LIST_INDEX] .valPtr = ET→uid;
} else
{
GetDItem( dlalptr, NEWTEXT_ITEM, &iType, &iHdl, tiBox);
GetlText( iHdl, name);
PtoCstr( name); if( !isNameValidt name)) { auto_request Not a valid media source name\nLength must be less than 32 characters . \nUse letters and digits only return FALSE;
} }
LDispose (tlistHdl); return TRUE;
}
/************ **************************************
* NAME: NewHltRtn
*
* FUNCTION: Handle user clicking on the NEW button of our dialog
*
*/ static NewHltRtn( item, aBox, argl) int item; Rect aBox; long argl;
{ dialogmode = CREATING _NEW_ENTRY;
SetRGBenvt 0, &RGBwhite, &RGBblack) ; LActivate ( FALSE, tlistHdl); RestoreRGBenv ();
HiliteDItem( thePort, OK_ITEM, 0) ; HiliteDItem( thePort, NEWBUT_ITEM, 255); HiliteDItem( thePort, EXISTING_ITEM, 0) ;
ShowDItem (thePort, NEWPROMPT_TEM) ; ShowDItem (thePort, NEWTEXT_ITEM) ; return item; }
/************************************************
* NAME: OldHitRtn
*
* FUNCTION: Handle user clicking on current list of source names
*
*/ static OldHitRtn ( item, aBox, argl) int item; Rect aBox; long argl; { int iType; Handle iHdl; Rect iBox;
dialogmode = SELECT_FROM_EXISTING; iList [NEWTEXT_INDEX] .valPtr(0] = EOS; /* the edittext item */
SetRGBenv( 0, &RGBwhite, &RGBblack); LActivate( TRUE, tlistHdl); RestoreRGBenv () ;
HiliteDItem( thePort, NEWBUT_ITEM, 0) ; HiliteDItem( thePort, EXISTING_ITEM, 255) ;
HldeDItem( thePort, NEWPROMPT_ITEM) ; HideDItem( thePort, NEWTEXT_ITEM) ; return item; }
/*************************************************
* NAME: DrawList
*
* FUNCTION: render the list of known sourcenames
*
*/ static pascal void DrawList( dig, item) WindowPtr dig; int item;
{ int iType. i, method;
Handle iHdl;
Rect iBox;
SetRGBenv( 0, &RGBwhite, &RGBblack);
GetDItem( dig, item, &IType, &lHdl, &lBox); DrawBox( dig, item);
EraseRect( &lBox) ;
LUpdate( thePort→visRgn, tlistHdl);
RestoreRGBenv () ; } /**********************************************************************
* NAME: myFilter
*
* FUNCTION: filter events on the dialog.
*
*/ static EventRecord myEvent; static myFilter ( dig, theEvent, item) WindowPtr dig; EventRecord *theEvent; int *item;
TEHandle teh; char charCode; myEvent = *theEvent;
GlobalToLocal ( &myEvent.where) ; if( dialogmode == SELECT_FROM_EXISTING)
{ ift SomethinglsSelected ())
HiliteDItem( thePort, OK_ITEM, 0); else
{
HiliteDItem( thePort, OK_ITEM, 255); return FALSE;
} } if( myEvent.what == keyDown)
{ charCode = theEvent→message & charCodeMask; if ( charCode == ' \r ' )
*item = OK_ITEM; return TRUE; }
teh = ( (DialogPeek)dlg)→textH; if ( (*teh)→teLength > 31 && charCode >= 0x20 && charCode != ' \b' )
{
SysBeep (5) ; auto_request ("No more than 31 characters| can be entered into this field", "OK", 1); return TRUE;
} } return FALSE; }
/************************************************
* NAME: ClickList
*
* FUNCTION: handle a click in the list of known source names
*
*/ static ClickList ( item, aBox, argl) int item; Rect aBox; long argl;
{
Boolean doubleclick; if( dialogmode == CREATING_NEW_ENTRY) OldHitRtn( item, aBox, argl);
SetRGBenvt 0, &RGBwhite, &RGBblack); doubleclick = LClick( myEvent.where, myEvent.modifiers, tlistHdl);
RestoreRGBenv () ; if( doubleclick && SomethingIsSelected()) return OK_ITEM; else return item; }
/************************************************
* NAME: ScmethinglsSelected
*
* FUNCTION: handle a click in the list of known source names
*
*/ static Boolean SomethinglsSelected() {
Point cell; cell.v = 0; cell.h = 0; return( LGetSelect( TRUE, &cell, tlistHdl)) ; }
/**************************************************************************************** ******************************* ********** ********************************* ********** ********************************** Source Name Dialog ********** *********************************** ********** *********************************** **********
**************************************************************************************//**********************************************
* NAME: isNameValid
*
* FUNCTION: Determine if user's entered source name is a valid one.
*
*/ static Boolean IsNameValid( nameStr) name_t nameStr;
{ char *c;
Boolean returnID = TRUE; if( !lsalnum( *nameStr)) returnID = FALSE; else for( c = nameStr; *c != NULL; C++)
( if( !(lsalnumt*c) II *c == ' ')) returnID = FALSE; } return( returnID); }
/************************************************
* NAME: addName
*
* FUNCTION: add Source Name to list. Return internal recordID.
*
* NOTE:
* If uid points to NULL a new UID will be generated. If the uid
* points to a nonzero value, the name and uid will be added as a pair. */ recordID addName( sourcename, uid, istat) name_t sourcename; sourceuid_t *uid; istat_t *istat; { sm_entry_t entry;
Boolean add = FALSE; recordID record = NULL;
ResetErr( istat); if( IsNilUid( uid)) { add = TRUE; MakeUid( uid);
} else { record = getrecordlD_uid( *uid, istat); if( *istat == SRM_NO_SUCH_UID)
{
ResetErr( istat); add = TRUE; } {
if( acid) { entry.uid = *uid; strncpy( entry.name, sourcename, sizeof ( name_t)); entry.name [31] = '\0'; entry.relations = NULL; record = DLAddHead( sm_list, (char *)tentry, istat); ChkErrGoto( istat, leave); } leave: return( record); }
/************************************************
* NAME: getrecordlD_uid
*
* FUNCTION: Given a sourcuid, cough up the recordID it is in.
*
*/ static recordID getrecordID_uid( uid, istat) sourceuid_t uid; lstat_t *istat;
{ static recordID **records = NULL; long NumRecs = 0; long recordID = 0; istat _t local;
if( IsNllUid( &uid)) LogErrGoto( istat, SRM_NO_SUCH_UID, leave);
DLSetThread( sm_list, uid_thrd, istat); ChkErrGoto( istat, leave); records = DLSlft( sm_list, INITIAL, EQ, (char *)&uid, FALSE, records, &NumRecs, istat); ChkErrGoto ( istat, leave); if( NumRecs > 1)
{ sm_entry_ptr SM;
DLGet( sm_list, (char **)&SM, NULL, TRUE, FALSE, istat);
ChkErrGoto( istat, leave); do {
} while( DLNext( sm_list, (char **)&SM, NULL, istat));
LogErrGoto( istat, SRM_DUPLICATE_SOURCE_UIDS, leave);
} if( NumRecs == 0) LogErrGoto( istat, SRM_NO_SUCH_UID, leave); recordID = **records; leave:
DLSetThread( sm_list, MAINTHREAD, &local); if( *istat == ISTAT_OK && local != ISTAT_OK) LogErr( istat, local); return( recordID);
}
sourceman_pvt .h
Saturday, December 2, 1989 3:11 PM
/*
* Module Name: sourceman_pvt.h
* Module Description: internal structures and data for source manager.
* \-----------------------------------------------------------------------------------------------------------------------------\
* I The following programs are the sole property of Avid Technology, Inc., |
* I and contain its proprietary and confidential information. I
* I Copyright ©1989, Avid Technology Inc. I
* \---------------------------------------------------------------------------------------------------------------------------\ **************************************************************************************** ************
************ Extensive Functional Spec exists for this module
************ Ask steve Reber for SourceMan Functional Spec
************ Highly re commended for before calling into or changing code. ****************************************************************************************
* * $Log: Engineering:PVCS:Sources:sourceman_pvt.h_v $ * * Rev 1.5 02 Dec 1989 15:11:10 SJR * exit film structs and calls, fix Black and Silence in source dialog* * Rev 1.4 22 Nov 1989 17:10:12 SJR * film struct in sourcemanager * * Rev 1.3 21 Nov 1989 18:03:40 SJR * updates for film information in the record. * * Rev 1.2 31 Oct 1989 10:37:52 SJR * rewrite SMCloseNWrite and SMReadTable * * Rev 1.1 24 Oct 1989 12:00:20 SJR * support Source Manager * * Rev 1.0 12 Oct 1989 09:27:38 SJR * Initial revision. * */
#define SMAN_MAGIC 0x534D414E /* SMAN = source manager magic number */ #define SMAN_REVISION 1 typedef struct
{ sourceuid_t uid; name_t name; threadlD relations;
} sm_entry_t,
* sm_entry_ptr,
**sm_entry_hdl; typedef struct { sourceuid_t uid; /* this struct doubles as a type used in threads */ name_t name; /* during SMCloseNWrite for determining uids lists
* and compressing them. In this use the name field
* is not used. The struct is also used as the format
* written in the disk file for the name/uid table. In
* this use the name field IS used. */
} SMTable_t, * SMTable_ptr, **SMTable_hdl; typedef struct
{ long magic; long revision; long num_entries;
SMTable_t entries;
} SMDiskTable_t,
* SM_iskTable_ptr,
**SMDiskTable_hdl; typedef struct { long magic; long revision; long num_relates;
SMrelations_t entries; /* their will be num_uids of these */ ) SMDiskRelates_t, * SNDlskRelates_ptr, **SMDiskRelates_hdl;
linklist .h
Monday, November 6, 1989 12:24 PM
/* *
* $Log: Engineering:PVCS:Sources:linklist.h_v $
* Rev 1.10 06 Nov 1989 12:24:10 SJR
* support uid_t
* Rev 1.9 24 Oct 1989 12:02:00 SJR
* changes to Boolean return values in DLNeκt and DLPrev
* Rev 1.8 12 Oct 1989 09:37:26 SJR
* Rev 1.7 11 Sep 1989 16:25:22 SJR
* new code for sorting and sifting. *************************************************************************************************************** ************
************ Extensive Functional Spec exists for this module
************ Ask steve Reber for DataList Functional Spec
************ Highly recommended for before calling into or changing code. ************* **************************************************************************************************************
*/ #ifndef LNK_H #define LNK_H 1 typedef long listID; typedef long recordID; typedef long threadlD; /* Main linked list threadlD */
#define MAINTHREAD 0
/***************************************************************************************/
/**** Error Codes ****/ /***************************************************************************************/
#define LNK_ZERO_SIZE (0 I ISTAT_LIST) /* List size of zero */
#define LNK_NO_RECORDS (1 I ISTAT_LIST) /* No records in the list */
#define LNK_KEYOFFSET_OUT_OF_RANGE (2 I ISTAT_LIST) /* keyoffset <0 OR > size */
#define LNK_AT_END (3 I ISTAT_LIST) /* attempt to increment past end of list */
#define LNK_AT_TOP (4 I ISTAT_LIST) /* attempt to backup past top of list */
#define LNK_GET_HEAD_TAIL (5 I ISTAT_LIST) /* attempt on DLGet with both Head and Tail set */
#define LNK_INVALID_ON_THREAD _0 (6 I ISTAT_LIST) /* attempt to sort or add key values to the main list */
#define LNK_NOT_IMPLEMENTED (7 I ISTAT_LIST) /* feature not implemented */
#define LNK_NO_SORT_ORDER (8 I ISTAT_LIST) /* cant get to next record in a heapsort */
#define LNK_THREAD_NOT_SORTED (9 I ISTAT_LIST) /* thread needs to be sorted for operation */
#define LNK_USE_DLINSERT (10 I ISTAT_LIST) /* cant use this call to add records to main list */
#define LNK_RECORD_EXISTS (11 I ISTAT_LIST) /* record already exists on specific thread */
#define LNK_USE_DLREMOVE (12 I ISTAT_LIST) /* cant use this call to remove records from the main list */
#define LNK_RECORD_DOES_NOT_EXIST (13 I ISTAT_LIST) /* record does not exist on thread */
#define LNK_INCL_DE_THREAD (14 I ISTAT_LIST) /* thread specified as mainlist duplicate */
#define LNK_NO_KEYS (15 I ISTAT_LIST) /* no keys specified yet */
#define LNK_NO_MORE_KEYS (16 I ISTAT_LIST) /* requesting sift on key that doesn't exist */
#define LNK_OPERATION_INVALID_ THREAD (17 I ISTAT_LIST) /* operation is illegal on threads other than MAINTHREAD */
/* types of key fields supported */ typedef enum
{
NoKey, timecode, longint, shortint, character, unique_id
} Ktype_t;
/* sorting algorithms supported */ typedef enum
{
NoSort,
QulckSort,
HeapSort
} SortType_t;
/* logical record set operands for searching */ typedef enum
{
INITIAL,
UNION,
INTERSECT
} SiftSetType_t;
/* arithmetric keyvalue operators for record inclusion on searches */ typedef enum
{
EQ,
GT,
GE,
LT,
LE,
NE
} ValueEquiv_t; /****************************************************************************************
* NAME: DLCreate
*
* FUNCTION: Initialize a Data List for service. O
*
* PARAMETERS:
* long size -size of the data element to be in the list, sizeof ( yourstruct) .
*
* Returns a listID that you use in further operations with this list. */ listID DLCreate( long size, lstat_t *istat); /****************************************************************************************
* NAME: DLDelete
*
* FUNCTION: Remove a Data List from service.
*
* PARAMETERS:
* listID id -id of the list you want to delete.
* */ void DLDelete( listID id, istat_t *istat);
/************************************************
* NAME: DLClean
*
* FUNCTION: Remove all records in a list.
*
* PARAMETERS:
* listID id -id of the list you want to delete.
*
*/ void DLClean ( listID id, istat_t *istat);
/************************************************
* NAME: DLInsert
*
* FUNCTION: Insert a record after the current record in the data list.
* Current record is most recent record acted on. Last inserted,
* or result of a DLNext, DLPrev and DLGet call.
*
* PARAMETERS:
* listID id -id of the data list.
* char *dataptr -pointer to the data to be placed in the data record.
* recordID record -record to insert after, if 0 insert after current record.
*
* RETURNS
* Id assigned to record just inserted. */ recordID DLInsert ( listID id, char *dataptr, recordID record, istat_t *istat);
/****************************************************************************************
* NAME: DLRemove *
* FUNCTION: Remove the current record in the data list.
* PARAMETERS:
* listID id -id of the data list.
* recordID id -id of record to remove - remove current if = 0. *
*/ void DLRemove( listID id, recordID record, istat_t "istat);
/****************************************************************************************
* NAME: DLAddHead
*
* FUNCTION: Add record as the head record in the data list. *
* PARAMETERS :
* listID id -id of the data list. * char *dataptr -pointer to the data to be placed in the data record. * * RETURNS * Id assigned to record just inserted, */ recordID DLAddHead( listID id, char *dataptr, istat_t *istat);
/*************************************************
* NAME: DLAddTail
*
* FUNCTION: Add record as the tail/ record in the data list.
*
* PARAMETERS:
* listID id -id of the data list.
* char *dataptr -pointer to the data to be placed in the data record.
* RETURNS
* Id assigned to record just inserted. */ recordID DLAddTail( listID id, char *dataptr, istat_t *istat);
/*********************************************************************************
* NAME: DLRmvHead
*
* FUNCTION: Remove head record from the data list.
*
* PARAMETERS:
* listID id -id of the data list.
*
* RETURNS
* Id assigned to record just inserted. */ void DLRmvHead( listID id, istat_t *istat); /*********************************************************************************
* NAME: DLRmvTail
*
* FUNCTION: Remove the tail record from the data list.
* PARAMETERS:
* listID id -id of the data list.
*
*/ void DLRmvTail( listID id, istat_t *Istat);
/*********************************************************************************
* NAME: DLSetThread
*
* FUNCTION: Use to set the particular link path being used through the data list. This
* effects the following calls:
* DLNext DLPrev DLRemove DLAddHead DLAddTail DLRmvHead DLRmvTail DLEnqueue
* PARAMETERS:
* listID id -id of the data list,
* threadlD id -id of the thread to set
*
*/ void DLSetThread( listID id, threadlD thread, istat_t *istat);
/************************************************
* NAME: DLCreateThread
*
* FUNCTION: Use to create a particular link path to be used through the data list.
*
* PARAMETERS:
* listID id -id of the data list
* Boolean include -thread is to include all records from master list intially
* Boolean echo -record additions and deletions from master are to be echoed on
* this thread.
*
* RETURNS:
* Thread Id to be used in calls to DLSetThread.
*
* NOTE:
* To actually use this thread, call DLSetThread. threadlD DLCreateThreadt listID id, Boolean include. Boolean echo, istat_t *istat);
/*******************************************************************************************
* NAME: DLRemoveThread
*
* FUNCTION: Use to remove a particular link path in a data list. *
* PARAMETERS:
* listID id -id of the data list
* threadlD thread -thread to remove
* */ void DLRemoveThread( listID id, threadlD thread, istat_t *istat); /***********************************************
* NAME: DLCleanThread
*
* FUNCTION: Clean a thread of all its records.
*
* PARAMETERS:
* listID id -id of the data list
* threadlD thread -thread to remove *
*/ void DLCleanThread( listID id, threadlD thrdid, istat_t *istat);
/************************************************
* NAME: DLAddKey
*
* FUNCTION: Add an additional key to sort on, to a particular thread.
*
* PARAMETERS:
* listID id -id of the data list
* Ktype_t keytype -indicator of how to interpret key for sorting
* long keyoffset -offset into client's data to find key
* Boolean increasing -sort keys in increasing order *
*/ void DLAddKey( listID id, Ktype_t keytype, long keyoffset, Boolean increasing, istat_t *istat);
/*****************************************************************************
* NAME: DLRmvKey *
* FUNCTION: Remove a key from a particular thread. *
* PARAMETERS: listID id -id of the data list
* long keyoffset -offset into client's data to find key
*
* */ void DLRmvKey ( listID id, long keyoffset, istat_t *istat);
/********************************************************************************* * NAME: DLNext * * FUNCTION: Move forward in data list, following current thread to next record. * * PARAMETERS: * listID id -id of the data list * char **dataptr -data of the record * recordID *record -returned record identifier * * RETURNS: * FALSE if unable to move to next record, ie: at end of list. * * NOTE: * If you change data in key fields of any thread currently defined, then the data list * sort is livalid. */
Boolean DLNext( listID id, char **dataptr, recordID *record, istat_t *istat);
/************************************************
* NAME: DLPrev
*
* FUNCTION: Move backward in data list, following current thread to previous record.
*
* PARAMETERS:
* listID id -id of the data list
* char **dataptr -data of the record
* recordID *record -returned record identifier
*
* RETURNS:
* FALSE if unable to move to previous record, ie: at head of list.
*
* NOTE:
* If you change data in key fields of any thread currently defined, then the data list * sort is invalid.
*/
Boolean DLPrev( listID id, char **dataptr, recordID *record, istat_t *istat); /********************************************************************************* * NAME: DLGet * * FUNCTION: Get the data associated with a specific record. * * PARAMETERS: * listID id -id of the data list * char **dataptr -data of the record * recordID *record -record identifier input and output * this pointer is changed ONLY when HEAD * or TAIL are true, * Boolean HEAD -ignore record id, get head record * Boolean TAIL -ignore record id, get tail record * * RETURNS: * True if any records in the data list. * * NOTE: * if record, HEAD, and TAIL are all NULL or FALSE then the current record is gotten. * recordID is returned in *record, if record is NOT NULL. * * If you change data in key fields of any thread currently defined, then the data list * sort is invalid. * / Boolean DLGet( listID id, char **dataptr, recordID *record. Boolean Head, Boolean Tail, istat_t *istat); /*********************************************************************************
* NAME: DLAddToThread *
* FUNCTION: Instance a record from the main list on a thread. * * PARAMETERS: * listID id -id of the data list * threadlD thread -thread to add record reference to * recordID record -record identifier * */ void DLAddToThreadt listID id, threadlD thread, recordID record, istat_t *istat);
/*********************************************************************************
* NAME: DLRmvFrmThread
*
* FUNCTION: Drop a record id from a thread. DOES NOT delete the record data. * * PARAMETERS: * listID id -id of the data list * threadlD thread -thread to remove record reference from * recordID record -record identifier *
*/ void DLRmvFrmThread( listID id, threadlD thread, recordID record, istat_t *istat); /*********************************************************************************
* NAME: DLSort
* * FUNCTION: Sort a thread of records according to the Sort method.
* PARAMETERS:
* listID id -id of the data list
* long sort -sorting method
*
*/ void DLSort( listID id, long sort, istat_t *istat); /*********************************************************************************
* NAME: DLSift
* FUNCTION: Search for record(s) fitting certain criteria for key values
* previously sorted on. * * PARAMETERS: * listID id -id of the data list * SiftSetType_t op -logical operation between existing record set * and this search, * ValueEquiv_t siftop -arithmetric operation on keyvalue for record inclusion * char *value -pointer to actual value to evaluate against * Boolean holdkey -hold on using current key, or evaluate on the next key * recordID **records -handle to current batch of sifted records * long *nimrecs -number of records in records array
*
* RETURNS handle co updated batch of records. Numrecs indicates how many.
*
* NOTE: CALLER is RESPONSIBLE for disposing of the handle "records" once done referencing it.
*
* Example: get all records with first key fields whose value is LT foo.
* records = DLSift( list, INITIAL, LT, (char *)&foo, FALSE, NULL, &NumRecs, &istat); */ recordID** DLSift( listID id, SiftSetType_t op, ValueEquiv_t siftop, char *value, Boolean holdkey, recordID **records, lo n g *
/************************************************
* NAME: DLCount
*
* FUNCTION: Return the count of records on the current thread
*
* PARAMETERS:
* listid id -id of the list (really the headerhdl)
*
*/ long DLCount( listID id, istat_t "istat);
#endif LNK _H