Skip to content

Commit 322fdf9

Browse files
Merge pull request #685 from gjsjohnmurray/fix-683
fix #683 better isfs handling of server-side changes
2 parents 271f923 + 8c23860 commit 322fdf9

File tree

8 files changed

+98
-43
lines changed

8 files changed

+98
-43
lines changed

src/api/index.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,11 @@ export class AtelierAPI {
338338
return this.cookies;
339339
}
340340

341+
// Not Modified
342+
if (response.status === 304) {
343+
throw { statusCode: response.status, message: response.statusText };
344+
}
345+
341346
const buffer = await response.buffer();
342347
const data: Atelier.Response = JSON.parse(buffer.toString("utf-8"));
343348

@@ -431,7 +436,7 @@ export class AtelierAPI {
431436
}
432437

433438
// api v1+
434-
public getDoc(name: string, format?: string): Promise<Atelier.Response<Atelier.Document>> {
439+
public getDoc(name: string, format?: string, mtime?: number): Promise<Atelier.Response<Atelier.Document>> {
435440
let params = {};
436441
if (!format && config("multilineMethodArgs") && this._config.apiVersion >= 4) {
437442
format = "udl-multiline";
@@ -442,7 +447,11 @@ export class AtelierAPI {
442447
};
443448
}
444449
name = this.transformNameIfCsp(name);
445-
return this.request(1, "GET", `${this.ns}/doc/${name}`, null, params);
450+
const headers = {};
451+
if (mtime && mtime > 0) {
452+
headers["IF_NONE_MATCH"] = new Date(mtime).toISOString().replace(/T|Z/g, " ").trim();
453+
}
454+
return this.request(1, "GET", `${this.ns}/doc/${name}`, null, params, headers);
446455
}
447456

448457
// api v1+

src/commands/compile.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
workspaceState,
1414
} from "../extension";
1515
import { DocumentContentProvider } from "../providers/DocumentContentProvider";
16-
import { currentFile, CurrentFile, currentWorkspaceFolder, outputChannel, workspaceFolderUri } from "../utils";
16+
import { currentFile, CurrentFile, currentWorkspaceFolder, outputChannel, uriOfWorkspaceFolder } from "../utils";
1717
import { RootNode } from "../explorer/models/rootNode";
1818
import { PackageNode } from "../explorer/models/packageNode";
1919
import { ClassNode } from "../explorer/models/classNode";
@@ -137,10 +137,17 @@ What do you want to do?`,
137137
} else {
138138
if (error.errorText && error.errorText !== "") {
139139
outputChannel.appendLine("\n" + error.errorText);
140-
vscode.window.showErrorMessage(
141-
`Failed to save file '${file.name}' on the server. Check output channel for details.`,
142-
"Dismiss"
143-
);
140+
vscode.window
141+
.showErrorMessage(
142+
`Failed to save file '${file.name}' on the server. Check 'ObjectScript' output channel for details.`,
143+
"Show",
144+
"Dismiss"
145+
)
146+
.then((action) => {
147+
if (action === "Show") {
148+
outputChannel.show(true);
149+
}
150+
});
144151
} else {
145152
vscode.window.showErrorMessage(`Failed to save file '${file.name}' on the server.`, "Dismiss");
146153
}
@@ -231,9 +238,15 @@ async function compile(docs: CurrentFile[], flags?: string): Promise<any> {
231238
.catch((error: Error) => {
232239
if (!config("suppressCompileErrorMessages")) {
233240
vscode.window
234-
.showErrorMessage("Compilation failed. Check output channel for details.", "Dismiss")
235-
.then((data) => {
236-
outputChannel.show(true);
241+
.showErrorMessage(
242+
"Compilation failed. Check 'ObjectScript' output channel for details.",
243+
"Show",
244+
"Dismiss"
245+
)
246+
.then((action) => {
247+
if (action === "Show") {
248+
outputChannel.show(true);
249+
}
237250
});
238251
}
239252
// Even when compile failed we should still fetch server changes
@@ -373,7 +386,7 @@ export async function importFolder(uri: vscode.Uri, noCompile = false): Promise<
373386
}
374387
let globpattern = "*.{cls,inc,int,mac}";
375388
const workspace = currentWorkspaceFolder();
376-
const workspacePath = workspaceFolderUri(workspace).fsPath;
389+
const workspacePath = uriOfWorkspaceFolder(workspace).fsPath;
377390
const folderPathNoWorkspaceArr = uripath.replace(workspacePath + path.sep, "").split(path.sep);
378391
if (folderPathNoWorkspaceArr.includes("csp")) {
379392
// This folder is a CSP application, so import all files

src/commands/export.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import path = require("path");
33
import * as vscode from "vscode";
44
import { AtelierAPI } from "../api";
55
import { config } from "../extension";
6-
import { mkdirSyncRecursive, notNull, outputChannel, workspaceFolderUri } from "../utils";
6+
import { mkdirSyncRecursive, notNull, outputChannel, uriOfWorkspaceFolder } from "../utils";
77
import { NodeBase } from "../explorer/models/nodeBase";
88

99
const filesFilter = (file: any) => {
@@ -185,7 +185,10 @@ export async function exportList(files: string[], workspaceFolder: string, names
185185
}
186186
const { atelier, folder, addCategory, map } = config("export", workspaceFolder);
187187

188-
const root = [workspaceFolderUri(workspaceFolder).fsPath, typeof folder === "string" && folder.length ? folder : null]
188+
const root = [
189+
uriOfWorkspaceFolder(workspaceFolder).fsPath,
190+
typeof folder === "string" && folder.length ? folder : null,
191+
]
189192
.filter(notNull)
190193
.join(path.sep);
191194
const run = async (fileList) => {

src/commands/viewOthers.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,17 @@ export async function viewOthers(forceEditable = false): Promise<void> {
200200
.catch((err) => {
201201
if (err.errorText && err.errorText !== "") {
202202
outputChannel.appendLine("\n" + err.errorText);
203-
vscode.window.showErrorMessage(`Failed to get other documents. Check output channel for details.`, "Dismiss");
203+
vscode.window
204+
.showErrorMessage(
205+
`Failed to get other documents. Check 'ObjectScript' output channel for details.`,
206+
"Show",
207+
"Dismiss"
208+
)
209+
.then((action) => {
210+
if (action === "Show") {
211+
outputChannel.show(true);
212+
}
213+
});
204214
} else {
205215
vscode.window.showErrorMessage(`Failed to get other documents.`, "Dismiss");
206216
}

src/providers/DocumentContentProvider.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { AtelierAPI } from "../api";
66

77
import { getFileName, getFolderName } from "../commands/export";
88
import { config, FILESYSTEM_SCHEMA, FILESYSTEM_READONLY_SCHEMA, OBJECTSCRIPT_FILE_SCHEMA } from "../extension";
9-
import { currentWorkspaceFolder, workspaceFolderUri } from "../utils";
9+
import { currentWorkspaceFolder, uriOfWorkspaceFolder } from "../utils";
1010

1111
export class DocumentContentProvider implements vscode.TextDocumentContentProvider {
1212
public get onDidChange(): vscode.Event<vscode.Uri> {
@@ -16,7 +16,7 @@ export class DocumentContentProvider implements vscode.TextDocumentContentProvid
1616
public static getAsFile(name: string, workspaceFolder: string): string {
1717
const { atelier, folder, addCategory, map } = config("export", workspaceFolder);
1818

19-
const root = [workspaceFolderUri(workspaceFolder).fsPath, folder].join(path.sep);
19+
const root = [uriOfWorkspaceFolder(workspaceFolder).fsPath, folder].join(path.sep);
2020
const fileName = getFileName(root, name, atelier, addCategory, map);
2121
if (fs.existsSync(fileName)) {
2222
return fs.realpathSync.native(fileName);
@@ -26,7 +26,7 @@ export class DocumentContentProvider implements vscode.TextDocumentContentProvid
2626
public static getAsFolder(name: string, workspaceFolder: string, category?: string): string {
2727
const { atelier, folder, addCategory } = config("export", workspaceFolder);
2828

29-
const root = [workspaceFolderUri(workspaceFolder).fsPath, folder].join(path.sep);
29+
const root = [uriOfWorkspaceFolder(workspaceFolder).fsPath, folder].join(path.sep);
3030
const folderName = getFolderName(root, name, atelier, addCategory ? category : null);
3131
if (fs.existsSync(folderName)) {
3232
return fs.realpathSync.native(folderName);
@@ -49,7 +49,7 @@ export class DocumentContentProvider implements vscode.TextDocumentContentProvid
4949
// if wFolderUri was passed it takes precedence
5050
if (!wFolderUri) {
5151
workspaceFolder = workspaceFolder && workspaceFolder !== "" ? workspaceFolder : currentWorkspaceFolder();
52-
wFolderUri = workspaceFolderUri(workspaceFolder);
52+
wFolderUri = uriOfWorkspaceFolder(workspaceFolder);
5353
}
5454
let uri: vscode.Uri;
5555
if (wFolderUri.scheme === FILESYSTEM_SCHEMA || wFolderUri.scheme === FILESYSTEM_READONLY_SCHEMA) {

src/providers/FileSystemProvider/File.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export class File implements vscode.FileStat {
1010
public data?: Uint8Array;
1111
public constructor(name: string, fileName: string, ts: string, size: number, data: string | Buffer) {
1212
this.type = vscode.FileType.File;
13-
this.ctime = new Date(ts).getTime();
14-
this.mtime = new Date(ts).getTime();
13+
this.ctime = Number(new Date(ts + "Z"));
14+
this.mtime = this.ctime;
1515
this.size = size;
1616
this.fileName = fileName;
1717
this.name = name;

src/providers/FileSystemProvider/FileSystemProvider.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { Directory } from "./Directory";
66
import { File } from "./File";
77
import { fireOtherStudioAction, OtherStudioAction } from "../../commands/studio";
88
import { StudioOpenDialog } from "../../queries";
9-
import { redirectDotvscodeRoot } from "../../utils/index";
9+
import { redirectDotvscodeRoot, workspaceFolderOfUri } from "../../utils/index";
10+
import { workspaceState } from "../../extension";
1011

1112
declare function setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timeout;
1213

@@ -146,7 +147,12 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
146147
}
147148

148149
public async readFile(uri: vscode.Uri): Promise<Uint8Array> {
149-
return this._lookupAsFile(uri).then((file: File) => file.data);
150+
return this._lookupAsFile(uri).then((file: File) => {
151+
// Update cache entry
152+
const uniqueId = `${workspaceFolderOfUri(uri)}:${file.fileName}`;
153+
workspaceState.update(`${uniqueId}:mtime`, file.mtime);
154+
return file.data;
155+
});
150156
}
151157

152158
private generateFileContent(fileName: string, content: Buffer): { content: string[]; enc: boolean } {
@@ -197,12 +203,16 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
197203
// The actual writing is done by our workspace.onDidSaveTextDocument handler.
198204
// But first check a case for which we should fail the write and leave the document dirty if changed.
199205
if (fileName.split(".").pop().toLowerCase() === "cls") {
200-
return api.actionIndex([fileName]).then((result) => {
206+
api.actionIndex([fileName]).then((result) => {
201207
if (result.result.content[0].content.depl) {
202208
throw new Error("Cannot overwrite a deployed class");
203209
}
204210
});
205211
}
212+
// Set a -1 mtime cache entry so the actual write by the workspace.onDidSaveTextDocument handler always overwrites.
213+
// By the time we get here VS Code's built-in conflict resolution mechanism will already have interacted with the user.
214+
const uniqueId = `${workspaceFolderOfUri(uri)}:${fileName}`;
215+
workspaceState.update(`${uniqueId}:mtime`, -1);
206216
return;
207217
},
208218
(error) => {
@@ -307,8 +317,12 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
307317
} else {
308318
throw vscode.FileSystemError.FileNotFound(uri);
309319
}
320+
} else if (child instanceof File) {
321+
// Return cached copy unless changed, in which case return updated one
322+
return this._lookupAsFile(uri, child);
323+
} else {
324+
entry = child;
310325
}
311-
entry = child;
312326
}
313327
return entry;
314328
}
@@ -325,8 +339,8 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
325339
throw vscode.FileSystemError.FileNotADirectory(uri);
326340
}
327341

328-
// Fetch from server and cache it
329-
private async _lookupAsFile(uri: vscode.Uri): Promise<File> {
342+
// Fetch from server and cache it, optionally the passed cached copy if unchanged on server
343+
private async _lookupAsFile(uri: vscode.Uri, cachedFile?: File): Promise<File> {
330344
uri = redirectDotvscodeRoot(uri);
331345
if (uri.path.startsWith("/.")) {
332346
throw vscode.FileSystemError.NoPermissions("dot-folders not supported by server");
@@ -338,7 +352,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
338352
const name = path.basename(uri.path);
339353
const api = new AtelierAPI(uri);
340354
return api
341-
.getDoc(fileName)
355+
.getDoc(fileName, undefined, cachedFile?.mtime)
342356
.then((data) => data.result)
343357
.then((result) => {
344358
const fileSplit = fileName.split(".");
@@ -373,6 +387,9 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
373387
})
374388
)
375389
.catch((error) => {
390+
if (error?.statusCode === 304 && cachedFile) {
391+
return cachedFile;
392+
}
376393
throw vscode.FileSystemError.FileNotFound(uri);
377394
});
378395
}

src/utils/index.ts

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export interface ConnectionTarget {
6161
* @param workspace The workspace the file is in.
6262
*/
6363
function getServerDocName(localPath: string, workspace: string): string {
64-
const workspacePath = workspaceFolderUri(workspace).fsPath;
64+
const workspacePath = uriOfWorkspaceFolder(workspace).fsPath;
6565
const filePathNoWorkspaceArr = localPath.replace(workspacePath + path.sep, "").split(path.sep);
6666
return filePathNoWorkspaceArr.slice(filePathNoWorkspaceArr.indexOf("csp")).join("/");
6767
}
@@ -73,7 +73,7 @@ function getServerDocName(localPath: string, workspace: string): string {
7373
*/
7474
export function isImportableLocalFile(file: vscode.TextDocument): boolean {
7575
const workspace = currentWorkspaceFolder(file);
76-
const workspacePath = workspaceFolderUri(workspace).fsPath;
76+
const workspacePath = uriOfWorkspaceFolder(workspace).fsPath;
7777
const filePathNoWorkspaceArr = file.fileName.replace(workspacePath + path.sep, "").split(path.sep);
7878
return filePathNoWorkspaceArr.includes("csp");
7979
}
@@ -259,18 +259,7 @@ export function isCSP(uri: vscode.Uri): boolean {
259259
export function currentWorkspaceFolder(document?: vscode.TextDocument): string {
260260
document = document ? document : vscode.window.activeTextEditor && vscode.window.activeTextEditor.document;
261261
if (document) {
262-
const uri = document.uri;
263-
if (uri.scheme === "file") {
264-
if (vscode.workspace.getWorkspaceFolder(uri)) {
265-
return vscode.workspace.getWorkspaceFolder(uri).name;
266-
}
267-
} else if (schemas.includes(uri.scheme)) {
268-
const rootUri = uri.with({ path: "/" }).toString();
269-
const foundFolder = vscode.workspace.workspaceFolders.find(
270-
(workspaceFolder) => workspaceFolder.uri.toString() == rootUri
271-
);
272-
return foundFolder ? foundFolder.name : uri.authority;
273-
}
262+
return workspaceFolderOfUri(document.uri);
274263
}
275264
const firstFolder =
276265
vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length
@@ -283,7 +272,21 @@ export function currentWorkspaceFolder(document?: vscode.TextDocument): string {
283272
}
284273
}
285274

286-
export function workspaceFolderUri(workspaceFolder: string = currentWorkspaceFolder()): vscode.Uri {
275+
export function workspaceFolderOfUri(uri: vscode.Uri): string {
276+
if (uri.scheme === "file") {
277+
if (vscode.workspace.getWorkspaceFolder(uri)) {
278+
return vscode.workspace.getWorkspaceFolder(uri).name;
279+
}
280+
} else if (schemas.includes(uri.scheme)) {
281+
const rootUri = uri.with({ path: "/" }).toString();
282+
const foundFolder = vscode.workspace.workspaceFolders.find(
283+
(workspaceFolder) => workspaceFolder.uri.toString() == rootUri
284+
);
285+
return foundFolder ? foundFolder.name : uri.authority;
286+
}
287+
}
288+
289+
export function uriOfWorkspaceFolder(workspaceFolder: string = currentWorkspaceFolder()): vscode.Uri {
287290
return (
288291
vscode.workspace.workspaceFolders.find((el): boolean => el.name.toLowerCase() === workspaceFolder.toLowerCase()) ||
289292
vscode.workspace.workspaceFolders.find((el): boolean => el.uri.authority == workspaceFolder)
@@ -309,7 +312,7 @@ export async function portFromDockerCompose(): Promise<{ port: number; docker: b
309312
return { docker: false, port: null };
310313
}
311314
const result = { port: null, docker: true, service };
312-
const workspaceFolderPath = workspaceFolderUri().fsPath;
315+
const workspaceFolderPath = uriOfWorkspaceFolder().fsPath;
313316
const workspaceRootPath = vscode.workspace.workspaceFolders[0].uri.fsPath;
314317

315318
const cwd: string = await new Promise((resolve, reject) => {

0 commit comments

Comments
 (0)