Skip to content

Commit

Permalink
Merge branch 'main' into fix/no-cfgs-prompt
Browse files Browse the repository at this point in the history
Signed-off-by: Billie Simmons <BillieJean.Simmons@ibm.com>
  • Loading branch information
JillieBeanSim authored Oct 31, 2024
2 parents 648ed3a + 7064bc0 commit 28f5043
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 63 deletions.
1 change: 1 addition & 0 deletions packages/zowe-explorer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen
- Fixed issue where switching the authentication methods would cause `Cannot read properties of undefined` error. [#3142](https://github.com/zowe/zowe-explorer-vscode/issues/3142)
- Fixed an issue where calling `vscode.workspace.fs.readFile` with a PDS member URI would throw an error when the PDS already existed as a filesystem entry. [#3267](https://github.com/zowe/zowe-explorer-vscode/issues/3267)
- Fixed issue where Zowe Explorer would present the "No configs detected" notification when initialized in a workspace without a Zowe team configuration. [#3280](https://github.com/zowe/zowe-explorer-vscode/issues/3280)
- Reduced the number of MVS API calls performed by `vscode.workspace.fs.readFile` when fetching the contents of a data set entry. [#3278](https://github.com/zowe/zowe-explorer-vscode/issues/3278)

## `3.0.2`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ describe("readFile", () => {
throw FileSystemError.FileNotFound(uri as Uri);
});
const lookupParentDir = jest.spyOn(DatasetFSProvider.instance as any, "_lookupParentDirectory").mockReturnValueOnce(null);
const remoteLookupForResourceMock = jest.spyOn(DatasetFSProvider.instance, "remoteLookupForResource").mockResolvedValue(testEntries.pds);
const fetchDatasetAtUriMock = jest.spyOn(DatasetFSProvider.instance, "fetchDatasetAtUri").mockResolvedValue(testEntries.pds);

let err;
try {
Expand All @@ -311,7 +311,7 @@ describe("readFile", () => {
expect(_lookupAsFileMock).toHaveBeenCalledWith(testUris.ps);
_lookupAsFileMock.mockRestore();
lookupParentDir.mockRestore();
remoteLookupForResourceMock.mockRestore();
fetchDatasetAtUriMock.mockRestore();
});

it("throws an error if the entry does not exist and the error is not FileNotFound", async () => {
Expand Down Expand Up @@ -339,7 +339,7 @@ describe("readFile", () => {
profile: testProfile,
path: "/USER.DATA.PS",
});
const fetchDatasetAtUriMock = jest.spyOn(DatasetFSProvider.instance, "fetchDatasetAtUri").mockImplementation();
const fetchDatasetAtUriMock = jest.spyOn(DatasetFSProvider.instance, "fetchDatasetAtUri").mockResolvedValueOnce(new DsEntry("USER.DATA.PS"));

await DatasetFSProvider.instance.readFile(testUris.ps);
expect(_lookupAsFileMock).toHaveBeenCalledWith(testUris.ps);
Expand All @@ -348,28 +348,26 @@ describe("readFile", () => {
_getInfoFromUriMock.mockRestore();
});

it("calls remoteLookupForResource if entry does not exist locally", async () => {
it("calls fetchDatasetAtUri if entry does not exist locally", async () => {
const _lookupAsFileMock = jest
.spyOn(DatasetFSProvider.instance as any, "_lookupAsFile")
.mockImplementationOnce(() => {
throw FileSystemError.FileNotFound(testUris.pdsMember);
})
.mockReturnValue(testEntries.pdsMember);

const fetchDatasetAtUriMock = jest.spyOn(DatasetFSProvider.instance, "fetchDatasetAtUri").mockImplementation();
const fetchDatasetAtUriMock = jest
.spyOn(DatasetFSProvider.instance, "fetchDatasetAtUri")
.mockResolvedValueOnce(new DsEntry("USER.DATA.PDS(MEMBER)"));
const _getInfoFromUriMock = jest.spyOn(DatasetFSProvider.instance as any, "_getInfoFromUri").mockReturnValueOnce({
profile: testProfile,
path: "/USER.DATA.PS",
});
const remoteLookupForResourceMock = jest
.spyOn(DatasetFSProvider.instance, "remoteLookupForResource")
.mockResolvedValue(testEntries.pdsMember);

await DatasetFSProvider.instance.readFile(testUris.pdsMember);
expect(_lookupAsFileMock).toHaveBeenCalledWith(testUris.pdsMember);
expect(remoteLookupForResourceMock).toHaveBeenCalledWith(testUris.pdsMember);
expect(fetchDatasetAtUriMock).toHaveBeenCalledWith(testUris.pdsMember, { isConflict: false });
_getInfoFromUriMock.mockRestore();
fetchDatasetAtUriMock.mockRestore();
});

it("throws error if parent exists and file cannot be found", async () => {
Expand All @@ -380,17 +378,14 @@ describe("readFile", () => {
profile: testProfile,
path: "/USER.DATA.PS",
});
const remoteLookupForResourceMock = jest
.spyOn(DatasetFSProvider.instance, "remoteLookupForResource")
.mockReset()
.mockResolvedValueOnce(null as any);

const fetchDatasetAtUriMock = jest.spyOn(DatasetFSProvider.instance, "fetchDatasetAtUri").mockResolvedValueOnce(null);
await expect(DatasetFSProvider.instance.readFile(testUris.pdsMember)).rejects.toThrow();
expect(_lookupAsFileMock).toHaveBeenCalledWith(testUris.pdsMember);
expect(remoteLookupForResourceMock).toHaveBeenCalledWith(testUris.pdsMember);
expect(fetchDatasetAtUriMock).toHaveBeenCalledWith(testUris.pdsMember, { isConflict: false });

_getInfoFromUriMock.mockRestore();
_lookupAsFileMock.mockRestore();
remoteLookupForResourceMock.mockRestore();
fetchDatasetAtUriMock.mockRestore();
});

it("returns the data for an entry", async () => {
Expand Down
113 changes: 67 additions & 46 deletions packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,40 +339,62 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem
* @param uri The URI pointing to a valid file to fetch from the remote system
* @param editor (optional) An editor instance to reload if the URI is already open
*/
public async fetchDatasetAtUri(uri: vscode.Uri, options?: { editor?: vscode.TextEditor | null; isConflict?: boolean }): Promise<void> {
public async fetchDatasetAtUri(
uri: vscode.Uri,
options?: { editor?: vscode.TextEditor | null; isConflict?: boolean }
): Promise<FileEntry | null> {
ZoweLogger.trace(`[DatasetFSProvider] fetchDatasetAtUri called with ${uri.toString()}`);
const file = this._lookupAsFile(uri) as DsEntry;
// we need to fetch the contents from the mainframe since the file hasn't been accessed yet
let dsEntry = this._lookupAsFile(uri, { silent: true }) as DsEntry | undefined;
const bufBuilder = new BufferBuilder();
const metadata = file.metadata ?? this._getInfoFromUri(uri);
const profileEncoding = file.encoding ? null : file.metadata.profile.profile?.encoding;
const resp = await ZoweExplorerApiRegister.getMvsApi(metadata.profile).getContents(metadata.dsName, {
binary: file.encoding?.kind === "binary",
encoding: file.encoding?.kind === "other" ? file.encoding.codepage : profileEncoding,
responseTimeout: metadata.profile.profile?.responseTimeout,
returnEtag: true,
stream: bufBuilder,
});
const data: Uint8Array = bufBuilder.read() ?? new Uint8Array();

if (options?.isConflict) {
file.conflictData = {
contents: data,
etag: resp.apiResponse.etag,
size: data.byteLength,
};
} else {
file.data = data;
file.etag = resp.apiResponse.etag;
file.size = file.data.byteLength;
file.mtime = Date.now();
}
const metadata = dsEntry?.metadata ?? this._getInfoFromUri(uri);
const profileEncoding = dsEntry?.encoding ? null : dsEntry?.metadata.profile.profile?.encoding;
try {
const resp = await ZoweExplorerApiRegister.getMvsApi(metadata.profile).getContents(metadata.dsName, {
binary: dsEntry?.encoding?.kind === "binary",
encoding: dsEntry?.encoding?.kind === "other" ? dsEntry?.encoding.codepage : profileEncoding,
responseTimeout: metadata.profile.profile?.responseTimeout,
returnEtag: true,
stream: bufBuilder,
});
const data: Uint8Array = bufBuilder.read() ?? new Uint8Array();
//if an entry does not exist for the dataset, create it
if (!dsEntry) {
const uriInfo = FsAbstractUtils.getInfoForUri(uri, Profiles.getInstance());
const uriPath = uri.path.substring(uriInfo.slashAfterProfilePos + 1).split("/");
const pdsMember = uriPath.length === 2;
this.createDirectory(uri.with({ path: path.posix.join(uri.path, "..") }));
const parentDir = this._lookupParentDirectory(uri);
const dsname = uriPath[Number(pdsMember)];
const ds = new DsEntry(dsname, pdsMember);
ds.metadata = new DsEntryMetadata({ path: path.posix.join(parentDir.metadata.path, dsname), profile: parentDir.metadata.profile });
parentDir.entries.set(dsname, ds);
dsEntry = parentDir.entries.get(dsname) as DsEntry;
}
//update entry's contents, attributes
if (options?.isConflict) {
dsEntry.conflictData = {
contents: data,
etag: resp.apiResponse.etag,
size: data.byteLength,
};
} else {
dsEntry.data = data;
dsEntry.etag = resp.apiResponse.etag;
dsEntry.size = dsEntry.data.byteLength;
dsEntry.mtime = Date.now();
}

ZoweLogger.trace(`[DatasetFSProvider] fetchDatasetAtUri fired a change event for ${uri.toString()}`);
this._fireSoon({ type: vscode.FileChangeType.Changed, uri });
ZoweLogger.trace(`[DatasetFSProvider] fetchDatasetAtUri fired a change event for ${uri.toString()}`);
this._fireSoon({ type: vscode.FileChangeType.Changed, uri });

if (options?.editor) {
await this._updateResourceInEditor(uri);
if (options?.editor) {
await this._updateResourceInEditor(uri);
}
return dsEntry;
} catch (error) {
//Response will error if the file is not found
//Callers of fetchDatasetAtUri() do not expect it to throw an error
return null;
}
}

Expand All @@ -383,41 +405,40 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem
*/
public async readFile(uri: vscode.Uri): Promise<Uint8Array> {
let ds: DsEntry | DirEntry;
const urlQuery = new URLSearchParams(uri.query);
const isConflict = urlQuery.has("conflict");
try {
ds = this._lookupAsFile(uri) as DsEntry;
} catch (err) {
if (!(err instanceof vscode.FileSystemError) || err.code !== "FileNotFound") {
throw err;
}

// do a remote lookup if the entry does not yet exist locally
ds = await this.remoteLookupForResource(uri);
}

if (ds == null) {
throw vscode.FileSystemError.FileNotFound(uri);
// we need to fetch the contents from the mainframe if the file hasn't been accessed yet
if (!ds || (!ds.wasAccessed && !urlQuery.has("inDiff")) || isConflict) {
//try and fetch its contents from remote
ds = (await this.fetchDatasetAtUri(uri, { isConflict })) as DsEntry;
if (!isConflict && ds) {
ds.wasAccessed = true;
}
}

if (FsAbstractUtils.isDirectoryEntry(ds)) {
throw vscode.FileSystemError.FileIsADirectory(uri);
}

//not found on remote, throw error
if (ds == null) {
throw vscode.FileSystemError.FileNotFound(uri);
}

const profInfo = this._getInfoFromUri(uri);

if (profInfo.profile == null) {
throw vscode.FileSystemError.FileNotFound(vscode.l10n.t("Profile does not exist for this file."));
}

const urlQuery = new URLSearchParams(uri.query);
const isConflict = urlQuery.has("conflict");

// we need to fetch the contents from the mainframe if the file hasn't been accessed yet
if ((!ds.wasAccessed && !urlQuery.has("inDiff")) || isConflict) {
await this.fetchDatasetAtUri(uri, { isConflict });
if (!isConflict) {
ds.wasAccessed = true;
}
}

return isConflict ? ds.conflictData.contents : ds.data;
}

Expand Down

0 comments on commit 28f5043

Please sign in to comment.