<<>> <> <> <<>> DIRECTORY Jukebox USING [Handle, Tune, bytesPerChirp, RunArray, FindJukebox, OpenJukebox, CloseJukebox, OpenTune, LengthRange, EnergyRange, RunArrayRange, RunComponent, MissingChirp, pagesPerChirp, ArchiveCloseTune, CreateTune, RunData, WindowOrigin, FindChirp], TuneAccess USING [ByteBlock, userHeaderLength, ReadTuneHeader, ByteSequence, ReadAmbientLevel, ReadRunArray, ReadChirpSamples, GetCreateDate, GetWriteDate, GetReadDate, WriteTuneHeader, InterpretRunArrayElement], Rope USING [ROPE, Fetch, Length, Concat, FromChar], IO USING [STREAM, UnsafeBlock, UnsafePutBlock, UnsafeGetBlock, PutChar, PutF, int, rope, Close, GetChar], FS USING [StreamOpen, FileInfo, Write], Convert USING [RopeFromTime], BasicTime USING [Now], FileNames USING [ConvertToSlashFormat], VM USING [Interval, Allocate, Free, AddressForPageNumber], TuneArchive; TuneArchiveImpl: CEDAR PROGRAM IMPORTS Jukebox, TuneAccess, Rope, IO, FS, Convert, BasicTime, FileNames, VM EXPORTS TuneArchive = BEGIN OPEN TuneArchive; ArchiveTune: PUBLIC PROC [jukeboxName: Rope.ROPE, tuneID: INT, toFile: Rope.ROPE] RETURNS [tuneInformation:TuneInformation, internalJukeboxName: Rope.ROPE] = TRUSTED { stream: IO.STREAM _ NIL; jukebox: Jukebox.Handle _ NIL; tune: Jukebox.Tune _ NIL; weOpened: BOOLEAN _ FALSE; -- all declared here for catch phrase use { ENABLE UNWIND => { IF stream # NIL THEN IO.Close[stream]; IF tune # NIL THEN Jukebox.ArchiveCloseTune[jukebox, tune]; <> IF jukebox # NIL AND weOpened THEN jukebox _ Jukebox.CloseJukebox[jukebox]; }; <> tuneInformation _ NEW[TuneInformationHeader]; { -- just so as to be able to do an assignment before all the declarations byteBlock: TuneAccess.ByteBlock _ NEW[TuneAccess.ByteSequence[MAX [TuneAccess.userHeaderLength, Jukebox.bytesPerChirp]]]; chirpContents: ChirpContents _ NEW[ChirpContentsRecord]; runArray: REF Jukebox.RunArray _ NEW[Jukebox.RunArray]; <> unsafeOfTuneInformation: IO.UnsafeBlock _ [LOOPHOLE[tuneInformation], 0, tuneInformationBytes]; byteBlockPointer: LONG POINTER _ LOOPHOLE[byteBlock]; unsafeOfByteBlock: IO.UnsafeBlock _ [byteBlockPointer + SIZE[TuneAccess.ByteSequence[0]]]; -- yetch unsafeOfChirpContents: IO.UnsafeBlock _ [LOOPHOLE[chirpContents], 0, chirpContentsBytes]; unsafeOfRunArray: IO.UnsafeBlock _ [LOOPHOLE[runArray]]; jukebox _ Jukebox.FindJukebox[jukeboxName]; IF jukebox = NIL THEN { jukebox _ Jukebox.OpenJukebox[jukeboxName]; weOpened _ TRUE }; stream _ FS.StreamOpen[toFile, $create]; tune _ Jukebox.OpenTune[self: jukebox, tuneId: tuneID, write: FALSE]; internalJukeboxName _ BuildTuneInformation[jukebox, tune, tuneInformation]; IO.UnsafePutBlock[stream, unsafeOfTuneInformation]; FOR i: INT IN [0..tuneInformation.jukeboxNameLength) DO IO.PutChar[stream, internalJukeboxName.Fetch[i]] ENDLOOP; byteBlock _ TuneAccess.ReadTuneHeader[jukebox, tune, TuneAccess.userHeaderLength, byteBlock]; unsafeOfByteBlock.count _ TuneAccess.userHeaderLength; IO.UnsafePutBlock[stream, unsafeOfByteBlock]; FOR j: INT IN [0..tuneInformation.sizeInChirps) DO BuildChirpRecord[jukebox, tune, j, chirpContents, runArray, byteBlock]; IO.UnsafePutBlock[stream, unsafeOfChirpContents]; unsafeOfRunArray.count _ chirpContents.runArrayLength*2; IO.UnsafePutBlock[stream, unsafeOfRunArray]; unsafeOfByteBlock.count _ chirpContents.sampleBytes; IO.UnsafePutBlock[stream, unsafeOfByteBlock] ENDLOOP; IO.Close[stream]; Jukebox.ArchiveCloseTune[jukebox, tune]; -- comments as previous use of ArchiveCloseTune IF weOpened THEN jukebox _ Jukebox.CloseJukebox[jukebox] }}}; BuildTuneInformation: PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, info: TuneInformation] RETURNS [internalJukeboxName: Rope.ROPE] = { <> info.jukeboxNameLength _ jukebox.jukeboxName.Length; TRUSTED {info.tuneNumber _ tune.tuneId}; info.createDate _ TuneAccess.GetCreateDate[tune]; info.writeDate _ TuneAccess.GetWriteDate[tune]; info.readDate _ TuneAccess.GetReadDate[tune]; info.archiveDate _ BasicTime.Now[]; TRUSTED {info.sizeInChirps _ tune.size}; RETURN [jukebox.jukeboxName] }; BuildChirpRecord: PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, chirpNumber: INT, chirpContents: ChirpContents, runArray: REF Jukebox.RunArray, byteBlock: TuneAccess.ByteBlock] = { <> ENABLE Jukebox.MissingChirp => { chirpContents.ambientLevel _ 0; chirpContents.runArrayLength _ 1; chirpContents.sampleBytes _ 0; TRUSTED {runArray[0] _ [silence[Jukebox.bytesPerChirp]]}; GOTO Return }; <<>> usefulArrayLength: Jukebox.RunArrayRange _ 0; usefulSamples: Jukebox.LengthRange _ 0; accountedSamples: Jukebox.LengthRange _ 0; thisLength: Jukebox.LengthRange; skipNextArrayElement: BOOLEAN; silence: BOOLEAN; runArray _ TuneAccess.ReadRunArray[jukebox, tune, chirpNumber, runArray]; WHILE accountedSamples RETURN }; ReadArchiveHeader: PUBLIC PROC [readableFileStream: IO.STREAM] RETURNS [tuneInformation: TuneArchive.TuneInformation, archivedJukeboxName: Rope.ROPE _ NIL] = { unsafeOfTuneInformation: IO.UnsafeBlock; unsafeBytesRead: INT; tuneInformation _ NEW[TuneInformationHeader]; unsafeOfTuneInformation _ [LOOPHOLE[tuneInformation], 0, tuneInformationBytes]; TRUSTED {unsafeBytesRead _ readableFileStream.UnsafeGetBlock[unsafeOfTuneInformation]}; IF unsafeBytesRead # tuneInformationBytes THEN ERROR; archivedJukeboxName _ NIL; FOR i: INT IN [1..tuneInformation.jukeboxNameLength] DO archivedJukeboxName _ archivedJukeboxName.Concat[Rope.FromChar[readableFileStream.GetChar]] ENDLOOP }; PrintArchiveInfo: PUBLIC PROC [out: IO.STREAM, archiveFileName: Rope.ROPE, tuneInformation: TuneArchive.TuneInformation, archivedJukeboxName: Rope.ROPE] = { length: INT; fullFName, attachedTo: Rope.ROPE; [bytes: length, fullFName: fullFName, attachedTo: attachedTo] _ FS.FileInfo[name: archiveFileName]; fullFName _ FileNames.ConvertToSlashFormat[fullFName]; IF attachedTo # NIL THEN attachedTo _ FileNames.ConvertToSlashFormat[attachedTo]; out.PutF["File \"%g\":\n", IO.rope[fullFName]]; IF attachedTo # NIL THEN out.PutF["(attached to \"%g\")\n", IO.rope[attachedTo]]; out.PutF[" archive of tune number %d from jukebox \"%g\"\n", IO.int[tuneInformation.tuneNumber], IO.rope[archivedJukeboxName]]; out.PutF[" made on %g", IO.rope[Convert.RopeFromTime[tuneInformation.archiveDate]]]; IF length = -1 THEN out.PutF["\n***FS Error whilst trying to determine length of archive file\n\n"] ELSE out.PutF["; length %d bytes\n\n", IO.int[length]]; out.PutF[" Tune length %d chirps\n", IO.int[tuneInformation.sizeInChirps]]; out.PutF[" Created: %g\n", IO.rope[Convert.RopeFromTime [tuneInformation.createDate]]]; out.PutF[" Last written: %g\n", IO.rope[Convert.RopeFromTime [tuneInformation.writeDate]]]; out.PutF[" Last read: %g\n", IO.rope[Convert.RopeFromTime [tuneInformation.readDate]]] }; RestoreTune: PUBLIC PROC [fromFile: Rope.ROPE, jukeboxName: Rope.ROPE _ NIL, tuneID: INT _ -1] RETURNS [tuneIDused: INT, restoredJukeboxName: Rope.ROPE] = TRUSTED { fileStream: IO.STREAM _ NIL; jukebox: Jukebox.Handle _ NIL; tune: Jukebox.Tune _ NIL; weOpened: BOOLEAN _ FALSE; -- all declared here for catch phrase use chirpVMWindow: VM.Interval _ VM.Allocate[Jukebox.pagesPerChirp]; { ENABLE UNWIND => { IF fileStream # NIL THEN IO.Close[fileStream]; IF tune # NIL THEN Jukebox.ArchiveCloseTune[jukebox, tune]; IF jukebox # NIL AND weOpened THEN jukebox _ Jukebox.CloseJukebox[jukebox]; VM.Free[chirpVMWindow] }; tuneInformation: TuneArchive.TuneInformation; archivedJukeboxName: Rope.ROPE; byteBlock: TuneAccess.ByteBlock _ NEW[TuneAccess.ByteSequence[TuneAccess.userHeaderLength]]; byteBlockPointer: LONG POINTER _ LOOPHOLE[byteBlock]; unsafeOfByteBlock: IO.UnsafeBlock _ [byteBlockPointer + SIZE[TuneAccess.ByteSequence[0]], 0, TuneAccess.userHeaderLength]; -- yetch unsafeBytesRead: INT; fileStream _ FS.StreamOpen[fromFile]; [tuneInformation, archivedJukeboxName] _ ReadArchiveHeader[readableFileStream: fileStream]; IF jukeboxName = NIL THEN jukeboxName _ archivedJukeboxName; IF tuneID = -1 THEN tuneID _ tuneInformation.tuneNumber; IF tuneID < -1 THEN tuneID _ -1; -- means "create a tune from unallocated ID space" jukebox _ Jukebox.FindJukebox[jukeboxName]; IF jukebox = NIL THEN { jukebox _ Jukebox.OpenJukebox[jukeboxName]; weOpened _ TRUE }; tune _ Jukebox.CreateTune[self: jukebox, tuneId: tuneID]; RestoreTuneInformation[tune, tuneInformation]; <> tuneIDused _ tune.tuneId; restoredJukeboxName _ jukebox.jukeboxName; unsafeBytesRead _ fileStream.UnsafeGetBlock[unsafeOfByteBlock]; IF unsafeBytesRead # TuneAccess.userHeaderLength THEN ERROR; TuneAccess.WriteTuneHeader[jukebox, tune, byteBlock]; FOR j: INT IN [0..tuneInformation.sizeInChirps) DO RestoreChirp[jukebox, tune, j, fileStream, chirpVMWindow] ENDLOOP; Jukebox.ArchiveCloseTune[jukebox, tune]; -- special one; enables the restored timestamp information to persist IF weOpened THEN jukebox _ Jukebox.CloseJukebox[jukebox]; IO.Close[fileStream]; VM.Free[chirpVMWindow] }}; RestoreTuneInformation: PROC [tune: Jukebox.Tune, tuneInformation: TuneInformation] = TRUSTED { <> tune.createDate _ tuneInformation.createDate; tune.appendDate _ tuneInformation.writeDate; tune.playDate _ tuneInformation.readDate <> }; RestoreChirp: PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, chirpNumber: INT, fileStream: IO.STREAM, chirpVMWindow: VM.Interval] = TRUSTED { chirpContents: ChirpContents _ NEW[ChirpContentsRecord]; unsafeOfChirpContents: IO.UnsafeBlock _ [LOOPHOLE[chirpContents], 0, chirpContentsBytes]; unsafeBytesRead: INT _ fileStream.UnsafeGetBlock[unsafeOfChirpContents]; chirpVMWindowpointer: LONG POINTER _ LOOPHOLE[VM.AddressForPageNumber[chirpVMWindow.page]]; unsafeOfSamples: IO.UnsafeBlock _ [chirpVMWindowpointer, 0, chirpContents.sampleBytes]; unsafeOfRunData: IO.UnsafeBlock _ [chirpVMWindowpointer + (Jukebox.bytesPerChirp/2), 0, chirpContents.runArrayLength*2]; runData: Jukebox.RunData _ LOOPHOLE[unsafeOfRunData.base]; <> <<>> IF unsafeBytesRead # chirpContentsBytes THEN ERROR; unsafeBytesRead _ fileStream.UnsafeGetBlock[unsafeOfRunData]; IF unsafeBytesRead # chirpContents.runArrayLength*2 THEN ERROR; unsafeBytesRead _ fileStream.UnsafeGetBlock[unsafeOfSamples]; IF unsafeBytesRead # chirpContents.sampleBytes THEN ERROR; runData.ambientLevel _ chirpContents.ambientLevel; IF chirpContents^ = [0, 1, 0] AND runData.runArray[0] = [silence[Jukebox.bytesPerChirp]] THEN RETURN; -- this will have the effect of leaving a 'missing chirp' <> { chirpWindow: Jukebox.WindowOrigin _ Jukebox.FindChirp[self: jukebox, tune: tune, chirp: chirpNumber, signalMissingChirp: FALSE, signalEOF: FALSE]; FS.Write[file: chirpWindow.file, to: chirpWindow.base, nPages: Jukebox.pagesPerChirp, from: VM.AddressForPageNumber[chirpVMWindow.page]] } }; END.