-- File: WFileToolDir.mesa
-- last edit, Stewart, July 4, 1982 5:29 pm.

DIRECTORY
Directory USING [
DeleteFile, Error, GetNext, ignore, Lookup, PropertyType,
PutProperty, RemoveFile, VolumeError],
DirectoryExtras USING [ForgetVolumes],
File USING [Capability, LimitPermissions, Unknown],
IO USING [PutF, string],
KernelFile USING [MakeTemporary],
RTFiles USING [IsFileInUse],
Segments USING [DestroyFile, FHandle, FP, FPHandle, InsertFile,
LockFile, NewFile, NullFP, ReleasableFile, ReleaseFile,
SetFileTimes, UnlockFile, Write],
Space USING [Handle, Create, Kill, LongPointer, Map, virtualMemory],
STP USING [defaultOptions],
Stream USING [
Block, CompletionCode, EndOfStream, GetProcedure,
Handle, SubSequenceType],
Streams USING [
CreateStream, Destroy, Handle, PutBlock, PutByte],
String USING [AppendChar, AppendString, UpperCase],
Time USING [Packed],
VFTOps USING [
FreeBufferPages, GetBufferPages, Handle,
InvertIndicator, PostComment, Task],
Volume USING [
Close, ID, InsufficientSpace, nullID],
VolumeExtras USING [OpenVolume];

WFileToolDir: PROGRAM
IMPORTS
Directory, DirectoryExtras, File, IO, KernelFile, RTFiles, Segments,
Space, Stream, Streams, String, VFTOps, Volume, VolumeExtras
EXPORTS VFTOps
SHARES Segments =
BEGIN


-- Also in VFileSupport

PageSize: CARDINAL = 256;
Byte: TYPE = [0..377B];
bufPages: CARDINAL = 6; -- also in VFileSUpportB
wordsInBuffer: CARDINAL = bufPages*PageSize;
bytesInBuffer: CARDINAL = wordsInBuffer*2;
Buffer: TYPE = PACKED ARRAY [0..bytesInBuffer) OF Byte;
WordBuffer: TYPE = PACKED ARRAY [0..wordsInBuffer) OF WORD;

-- by convention, the remote name is where the file was
--retrieved FROM or stored ONTO

RemoteNameProperty: Directory.PropertyType = LOOPHOLE[213B];

wildString: CHARACTER = '*;
wildChar: CHARACTER = '#;

WriteDiskForRetrieve: PUBLIC PROCEDURE [
self: VFTOps.Handle, from: Stream.Handle, name: STRING, fpHint: Segments.FPHandle, create: Time.Packed, remoteName: LONG STRING, t: VFTOps.Task]
RETURNS [bytes: LONG CARDINAL] =
BEGIN
buffer: LONG POINTER TO Buffer = VFTOps.GetBufferPages[self, bufPages];
block: Stream.Block ← [buffer, 0, bytesInBuffer];
bytesTransferred: CARDINAL;
why: Stream.CompletionCode;
fh: Segments.FHandle;
savedSST: Stream.SubSequenceType;
to: Streams.Handle ← NIL;
remotePropertySet: BOOLFALSE;
bytes ← 0;
DO
ENABLE UNWIND => {
 VFTOps.FreeBufferPages[self, buffer];
IF to # NIL THEN {OPEN Segments;
  LockFile[fh]; Streams.Destroy[to]; UnlockFile[fh]; DestroyFile[fh]}};
block.startIndex ← 0;
block.stopIndexPlusOne ← bytesInBuffer;
[bytesTransferred, why, savedSST] ← from.get[
 from, block, STP.defaultOptions !
 Stream.EndOfStream => {
  why ← endOfStream; bytesTransferred ← nextIndex; CONTINUE}];
block.stopIndexPlusOne ← bytesTransferred;
bytes ← bytes + bytesTransferred;
VFTOps.InvertIndicator[self];
IF to = NIL THEN {OPEN Segments;
 fh ← IF fpHint^ = NullFP THEN NewFile[name, Write] ELSE InsertFile[fpHint];
 to ← Streams.CreateStream[fh, Write]};
IF NOT remotePropertySet THEN {
cap: File.Capability ← File.LimitPermissions[originalFC: fh.cap, maxPermissions: Directory.ignore];
 remotePath: STRING ← [125];
remotePropertySet ← TRUE;
 String.AppendChar[s: remotePath, c: '[];
 String.AppendString[to: remotePath, from: t.host];
 String.AppendChar[s: remotePath, c: ']];
FOR i: NAT IN [0..remoteName.length) DO
  String.AppendChar[s: remotePath, c: remoteName[i]];
ENDLOOP;
-- debugging
--self.log.PutF["rfp: %s\n", IO.string[remotePath]];
-- WARNING THIS CHANGES THE CREATE DATE if the capability's permissions
--are not Directory.ignore
-- remotename should be of the form [ivy]<directory>name!vers
Directory.PutProperty[cap, RemoteNameProperty,
DESCRIPTOR[remotePath, SIZE[StringBody[remotePath.maxlength]]], TRUE];
};
[] ← Streams.PutBlock[to, buffer, bytesTransferred/2];
IF bytesTransferred MOD 2 # 0 THEN
 Streams.PutByte[to, buffer[block.startIndex+bytesTransferred-1]];
IF why = endOfStream THEN EXIT;
ENDLOOP;
Segments.LockFile[fh];
Streams.Destroy[to];
Segments.SetFileTimes[file: fh, create: create];
Segments.UnlockFile[fh];
IF Segments.ReleasableFile[fh] THEN Segments.ReleaseFile[fh];
VFTOps.FreeBufferPages[self, buffer];
RETURN
END;

NoMoreRoom: PUBLIC PROC RETURNS [ERROR] = {RETURN[Volume.InsufficientSpace]};

DestroyFile: PUBLIC PROC [self: VFTOps.Handle, fp: Segments.FPHandle, name: STRING] = {
badFile: File.Capability;
failed: BOOLEANFALSE;
IF RTFiles.IsFileInUse[fp^] THEN {
self.log.PutF["%s is in use. Removing from directory and making temporary.\r", IO.string[name]];
Directory.RemoveFile[fileName: name, file: fp^];
KernelFile.MakeTemporary[fp^];
}
ELSE Directory.DeleteFile[name ! File.Unknown => {badFile ← file; failed ← TRUE; CONTINUE}];
IF failed THEN {
self.log.PutF["File.Unknown; will remove directory entry... \n"];
Directory.RemoveFile[name, badFile];
};
};

GetBufferPages: PUBLIC PROC [self: VFTOps.Handle, pages: CARDINAL] RETURNS [buf: LONG POINTER] = {
IF self.bufAllocated THEN ERROR;
IF self.buffer = NIL THEN {
OPEN Space;
self.bufSpace ← Create[size: self.bufSize ← pages, parent: virtualMemory];
Map[self.bufSpace];
self.buffer ← LongPointer[self.bufSpace];
};
IF pages # self.bufSize THEN ERROR;
self.bufAllocated ← TRUE;
buf ← self.buffer
};

FreeBufferPages: PUBLIC PROC [self: VFTOps.Handle, buf: LONG POINTER] = {
IF ~self.bufAllocated OR buf # self.buffer THEN ERROR;
self.bufAllocated ← FALSE;
Space.Kill[self.bufSpace];
};

FileExists: PUBLIC PROC [self: VFTOps.Handle, name: STRING, cap: Segments.FPHandle] RETURNS [yes: BOOLEAN] =
BEGIN
openedVolume: Volume.ID ← Volume.nullID;
Fail: PROC = {yes ← FALSE; cap^ ← Segments.NullFP};
yes ← TRUE;
cap^ ← Directory.Lookup[fileName: name, permissions: Directory.ignore !
Directory.Error =>
SELECT type FROM
  fileNotFound, volumeNotFound => {Fail[]; CONTINUE};
ENDCASE;
Directory.VolumeError =>
SELECT type FROM
  directoryNeedsScavenging => {
  VFTOps.PostComment[self, "Directory Needs Scavenging"]; Fail[]; CONTINUE};
  rootNotFound => {
  VFTOps.PostComment[self, "Root Not Found"]; Fail[]; CONTINUE};
  volumeNotOpen =>
  {VolumeExtras.OpenVolume[volume: volume, readOnly: TRUE]; openedVolume ← volume; RETRY};
ENDCASE];
IF openedVolume # Volume.nullID THEN
{Volume.Close[openedVolume]; DirectoryExtras.ForgetVolumes[]};
END;

Enumerate: PUBLIC PROC [
self: VFTOps.Handle,
files: STRING,
proc: PROC [fp: Segments.FPHandle, file: STRING] RETURNS [BOOLEAN],
wantWholeName: BOOLEAN ] =
BEGIN
PreProcessFile: PROC [fp: Segments.FPHandle, file: STRING] RETURNS [BOOLEAN] = {
IF MaskFilename[file, 0, files, 0] THEN RETURN[proc[fp, file]];
RETURN[FALSE]};
MaskFilename: PROC [file: STRING, fileIndex: CARDINAL, mask: STRING,
  maskIndex: CARDINAL] RETURNS [outcome: BOOLEAN] =
BEGIN
-- local variables
  i, j: CARDINAL;
-- process each character in mask
FOR i IN [maskIndex..mask.length) DO
SELECT mask[i] FROM
  wildString => -- matches any string of zero or more characters
  BEGIN
  FOR j IN [fileIndex..file.length] DO
   IF MaskFilename[file, j, mask, i+1] THEN RETURN[TRUE];
   ENDLOOP;
  RETURN[FALSE];
  END;
  wildChar => -- matches any single character
  IF fileIndex = file.length THEN RETURN[FALSE]
  ELSE fileIndex ← fileIndex + 1;
ENDCASE =>
  IF fileIndex = file.length OR
   String.UpperCase[file[fileIndex]] # String.UpperCase[mask[i]] THEN
   RETURN[FALSE]
  ELSE fileIndex ← fileIndex + 1;
ENDLOOP;
-- filename passes mask if entire filename has been consumed
  outcome ← fileIndex = file.length;
END;
curName: STRING ← [100];
nextName: STRING ← [100];
dirName: STRING ← [100];
filePart: STRING ← [40];
passedOutName: STRING ← [40];
fp: Segments.FP;
FOR i: CARDINAL IN [0..files.length) DO
IF files[i] = '> THEN {
 String.AppendString[dirName, filePart];
 String.AppendChar[dirName, '>];
 filePart.length ← 0}
ELSE String.AppendChar[filePart, files[i]];
ENDLOOP;
IF filePart.length = 0 THEN String.AppendChar[filePart, '*];
FOR i: CARDINAL IN [0..filePart.length) DO
IF filePart[i] = wildString OR filePart[i] = wildChar THEN EXIT
REPEAT FINISHED => {
IF FileExists[self, files, @fp] THEN
[] ← proc[@fp, IF wantWholeName THEN files ELSE filePart];
RETURN};
ENDLOOP;
IF dirName.length = 0 THEN String.AppendChar[dirName, '*];
DO
fp ← Directory.GetNext[dirName, curName, nextName ! Directory.Error => CONTINUE];
IF nextName.length = 0 THEN EXIT;
passedOutName.length ← 0;
FOR i: CARDINAL IN [0..nextName.length) DO
IF nextName[i] = '> THEN passedOutName.length ← 0
ELSE String.AppendChar[passedOutName, nextName[i]];
ENDLOOP;
IF MaskFilename[passedOutName, 0, filePart, 0] THEN {
IF proc[@fp, IF wantWholeName THEN nextName ELSE passedOutName] THEN RETURN};
curName.length ← 0;
String.AppendString[curName, nextName];
nextName.length ← 0;
ENDLOOP;
END;

END. -- of FileToolDir
-- Smokey: Oct 13, 1980 12:39 PM
-- Karlton: 11-Dec-80 15:38:39
-- Mark: 12-Mar-81 19:50:44
16-Jan-82 15:57:24, Stewart, created from FileToolDir.mesa
25-Jan-82 23:17:49, Stewart, objects
27-Jan-82 19:12:59 Stewart, removed SetWorkingDir
March 28, 1982 4:21 pm, Stewart, Cedar 3.0 cleanup
July 4, 1982 5:29 pm, Stewart, RTFiles