fix stream-file urls to use new remoteuri (#1984)

This commit is contained in:
Mike Sawka 2025-02-17 17:55:57 -08:00 committed by GitHub
parent 1929c70e04
commit e15d38a795
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 92 additions and 64 deletions

View File

@ -87,7 +87,10 @@ export function shFrameNavHandler(event: Electron.Event<Electron.WebContentsWill
}
if (
event.frame.name == "pdfview" &&
(url.startsWith("blob:file:///") || url.startsWith(getWebServerEndpoint() + "/wave/stream-file?"))
(url.startsWith("blob:file:///") ||
url.startsWith(getWebServerEndpoint() + "/wave/stream-file?") ||
url.startsWith(getWebServerEndpoint() + "/wave/stream-file/") ||
url.startsWith(getWebServerEndpoint() + "/wave/stream-local-file?"))
) {
// allowed
return;

View File

@ -240,7 +240,9 @@ electron.ipcMain.on("webview-image-contextmenu", (event: electron.IpcMainEvent,
});
electron.ipcMain.on("download", (event, payload) => {
const streamingUrl = getWebServerEndpoint() + "/wave/stream-file?path=" + encodeURIComponent(payload.filePath);
const baseName = encodeURIComponent(path.basename(payload.filePath));
const streamingUrl =
getWebServerEndpoint() + "/wave/stream-file/" + baseName + "?path=" + encodeURIComponent(payload.filePath);
event.sender.downloadURL(streamingUrl);
});

View File

@ -241,6 +241,7 @@ const BlockFrame_Header = ({
icon: "link-slash",
title: "wsh is not installed for this connection",
};
const showNoWshButton = manageConnection && wshProblem && !util.isBlank(connName) && !connName.startsWith("aws:");
return (
<div
@ -263,9 +264,7 @@ const BlockFrame_Header = ({
changeConnModalAtom={changeConnModalAtom}
/>
)}
{manageConnection && wshProblem && (
<IconButton decl={wshInstallButton} className="block-frame-header-iconbutton" />
)}
{showNoWshButton && <IconButton decl={wshInstallButton} className="block-frame-header-iconbutton" />}
<div className="block-frame-textelems-wrapper">{headerTextElems}</div>
<div className="block-frame-end-icons">{endIconsElem}</div>
</div>

View File

@ -4,7 +4,7 @@
import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil";
import { getWebServerEndpoint } from "@/util/endpoints";
import { isBlank, makeConnRoute } from "@/util/util";
import { formatRemoteUri } from "@/util/waveutil";
import parseSrcSet from "parse-srcset";
export type MarkdownContentBlockType = {
@ -158,19 +158,13 @@ export const resolveRemoteFile = async (filepath: string, resolveOpts: MarkdownR
if (!filepath || filepath.startsWith("http://") || filepath.startsWith("https://")) {
return filepath;
}
try {
const route = makeConnRoute(resolveOpts.connName);
const fileInfo = await RpcApi.RemoteFileJoinCommand(TabRpcClient, [resolveOpts.baseDir, filepath], {
route: route,
});
const baseDirUri = formatRemoteUri(resolveOpts.baseDir, resolveOpts.connName);
const fileInfo = await RpcApi.FileJoinCommand(TabRpcClient, [baseDirUri, filepath]);
const remoteUri = formatRemoteUri(fileInfo.path, resolveOpts.connName);
console.log("markdown resolve", resolveOpts, filepath, "=>", baseDirUri, remoteUri);
const usp = new URLSearchParams();
usp.set("path", fileInfo.path);
if (!isBlank(resolveOpts.connName)) {
usp.set("connection", resolveOpts.connName);
}
usp.set("path", remoteUri);
return getWebServerEndpoint() + "/wave/stream-file?" + usp.toString();
} catch (err) {
console.warn("Failed to resolve remote file:", filepath, err);

View File

@ -348,6 +348,7 @@ const ChangeConnectionBlockModal = React.memo(
const connStatusMap = new Map<string, ConnStatus>();
const fullConfig = jotai.useAtomValue(atoms.fullConfigAtom);
let filterOutNowsh = util.useAtomValueSafe(viewModel.filterOutNowsh) ?? true;
const showS3 = util.useAtomValueSafe(viewModel.showS3) ?? false;
let maxActiveConnNum = 1;
for (const conn of allConnStatus) {
@ -436,14 +437,17 @@ const ChangeConnectionBlockModal = React.memo(
fullConfig,
filterOutNowsh
);
const s3Suggestions = getS3Suggestions(
s3List,
connection,
connSelected,
connStatusMap,
fullConfig,
filterOutNowsh
);
let s3Suggestions: SuggestionConnectionScope = null;
if (showS3) {
s3Suggestions = getS3Suggestions(
s3List,
connection,
connSelected,
connStatusMap,
fullConfig,
filterOutNowsh
);
}
const connectionsEditItem = getConnectionsEditItem(changeConnModalAtom, connSelected);
const disconnectItem = getDisconnectItem(connection, connStatusMap);
const newConnectionSuggestionItem = getNewConnectionSuggestionItem(

View File

@ -9,9 +9,10 @@ import { ContextMenuModel } from "@/app/store/contextmenu";
import { PLATFORM, atoms, createBlock, getApi, globalStore } from "@/app/store/global";
import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil";
import { formatRemoteUri, type PreviewModel } from "@/app/view/preview/preview";
import { type PreviewModel } from "@/app/view/preview/preview";
import { checkKeyPressed, isCharacterKeyEvent } from "@/util/keyutil";
import { fireAndForget, isBlank, makeNativeLabel } from "@/util/util";
import { formatRemoteUri } from "@/util/waveutil";
import { offset, useDismiss, useFloating, useInteractions } from "@floating-ui/react";
import {
Column,
@ -575,7 +576,8 @@ function TableBody({
{
label: "Download File",
click: () => {
getApi().downloadFile(normPath);
const remoteUri = formatRemoteUri(finfo.path, conn);
getApi().downloadFile(remoteUri);
},
},
{

View File

@ -38,6 +38,7 @@ import {
makeNativeLabel,
stringToBase64,
} from "@/util/util";
import { formatRemoteUri } from "@/util/waveutil";
import { Monaco } from "@monaco-editor/react";
import clsx from "clsx";
import { Atom, atom, Getter, PrimitiveAtom, useAtom, useAtomValue, useSetAtom, WritableAtom } from "jotai";
@ -180,6 +181,8 @@ export class PreviewModel implements ViewModel {
directoryKeyDownHandler: (waveEvent: WaveKeyboardEvent) => boolean;
codeEditKeyDownHandler: (waveEvent: WaveKeyboardEvent) => boolean;
showS3 = atom(true);
constructor(blockId: string, nodeModel: BlockNodeModel) {
this.viewType = "preview";
this.blockId = blockId;
@ -936,13 +939,14 @@ function StreamingPreview({ model }: SpecializedViewProps) {
const conn = useAtomValue(model.connection);
const fileInfo = useAtomValue(model.statFile);
const filePath = fileInfo.path;
const remotePath = formatRemoteUri(filePath, conn);
const usp = new URLSearchParams();
usp.set("path", filePath);
usp.set("path", remotePath);
if (conn != null) {
usp.set("connection", conn);
}
const streamingUrl = getWebServerEndpoint() + "/wave/stream-file?" + usp.toString();
if (fileInfo.mimetype == "application/pdf") {
const streamingUrl = `${getWebServerEndpoint()}/wave/stream-file?${usp.toString()}`;
if (fileInfo.mimetype === "application/pdf") {
return (
<div className="view-preview view-preview-pdf">
<iframe src={streamingUrl} width="100%" height="100%" name="pdfview" />
@ -1304,16 +1308,4 @@ const ErrorOverlay = memo(({ errorMsg, resetOverlay }: { errorMsg: ErrorMsg; res
);
});
function formatRemoteUri(path: string, connection: string): string {
connection = connection ?? "local";
// TODO: We need a better way to handle s3 paths
let retVal: string;
if (connection.startsWith("aws:")) {
retVal = `${connection}:s3://${path ?? ""}`;
} else {
retVal = `wsh://${connection}/${path}`;
}
return retVal;
}
export { formatRemoteUri, PreviewView };
export { PreviewView };

View File

@ -119,7 +119,8 @@ function TermSticker({ sticker, config }: { sticker: StickerType; config: Sticke
if (sticker.imgsrc == null) {
return null;
}
const streamingUrl = getWebServerEndpoint() + "/wave/stream-file?path=" + encodeURIComponent(sticker.imgsrc);
const streamingUrl =
getWebServerEndpoint() + "/wave/stream-local-file?path=" + encodeURIComponent(sticker.imgsrc);
return (
<div className="term-sticker term-sticker-image" style={style} onClick={clickHandler}>
<img src={streamingUrl} />

View File

@ -292,6 +292,9 @@ declare global {
// If true, filters out 'nowsh' connections (when managing connections)
filterOutNowsh?: jotai.Atom<boolean>;
// if true, show s3 connections in picker
showS3?: jotai.Atom<boolean>;
// If true, removes padding inside the block content area.
noPadding?: jotai.Atom<boolean>;

View File

@ -7,7 +7,9 @@ import { generate as generateCSS, parse as parseCSS, walk as walkCSS } from "css
function encodeFileURL(file: string) {
const webEndpoint = getWebServerEndpoint();
return webEndpoint + `/wave/stream-file?path=${encodeURIComponent(file)}&no404=1`;
const fileUri = formatRemoteUri(file, "local");
const rtn = webEndpoint + `/wave/stream-file?path=${encodeURIComponent(fileUri)}&no404=1`;
return rtn;
}
export function processBackgroundUrls(cssText: string): string {
@ -86,3 +88,15 @@ export function computeBgStyleFromMeta(meta: MetaType, defaultOpacity: number =
return null;
}
}
export function formatRemoteUri(path: string, connection: string): string {
connection = connection ?? "local";
// TODO: We need a better way to handle s3 paths
let retVal: string;
if (connection.startsWith("aws:")) {
retVal = `${connection}:s3://${path ?? ""}`;
} else {
retVal = `wsh://${connection}/${path}`;
}
return retVal;
}

View File

@ -774,20 +774,13 @@ func (c S3Client) Delete(ctx context.Context, conn *connparse.Connection, recurs
func (c S3Client) Join(ctx context.Context, conn *connparse.Connection, parts ...string) (*wshrpc.FileInfo, error) {
var joinParts []string
if conn.Host == "" || conn.Host == fspath.Separator {
if conn.Path == "" || conn.Path == fspath.Separator {
joinParts = parts
} else {
joinParts = append([]string{conn.Path}, parts...)
}
} else if conn.Path == "" || conn.Path == "/" {
joinParts = append([]string{conn.Host}, parts...)
if conn.Path == "" || conn.Path == fspath.Separator {
joinParts = parts
} else {
joinParts = append([]string{conn.Host, conn.Path}, parts...)
joinParts = append([]string{conn.Path}, parts...)
}
conn.Path = fspath.Join(joinParts...)
return c.Stat(ctx, conn)
}

View File

@ -25,6 +25,7 @@ import (
"github.com/wavetermdev/waveterm/pkg/docsite"
"github.com/wavetermdev/waveterm/pkg/filestore"
"github.com/wavetermdev/waveterm/pkg/panichandler"
"github.com/wavetermdev/waveterm/pkg/remote/fileshare"
"github.com/wavetermdev/waveterm/pkg/schema"
"github.com/wavetermdev/waveterm/pkg/service"
"github.com/wavetermdev/waveterm/pkg/util/utilfn"
@ -251,6 +252,10 @@ func handleRemoteStreamFile(w http.ResponseWriter, req *http.Request, conn strin
route := wshutil.MakeConnectionRouteId(conn)
rpcOpts := &wshrpc.RpcOpts{Route: route, Timeout: 60 * 1000}
rtnCh := wshclient.RemoteStreamFileCommand(client, streamFileData, rpcOpts)
return handleRemoteStreamFileFromCh(w, req, path, rtnCh, rpcOpts.StreamCancelFn, no404)
}
func handleRemoteStreamFileFromCh(w http.ResponseWriter, req *http.Request, path string, rtnCh <-chan wshrpc.RespOrErrorUnion[wshrpc.FileData], streamCancelFn func(), no404 bool) error {
firstPk := true
var fileInfo *wshrpc.FileInfo
loopDone := false
@ -265,7 +270,9 @@ func handleRemoteStreamFile(w http.ResponseWriter, req *http.Request, conn strin
for {
select {
case <-ctx.Done():
rpcOpts.StreamCancelFn()
if streamCancelFn != nil {
streamCancelFn()
}
return ctx.Err()
case respUnion, ok := <-rtnCh:
if !ok {
@ -311,6 +318,16 @@ func handleRemoteStreamFile(w http.ResponseWriter, req *http.Request, conn strin
}
}
func handleStreamLocalFile(w http.ResponseWriter, r *http.Request) {
path := r.URL.Query().Get("path")
if path == "" {
http.Error(w, "path is required", http.StatusBadRequest)
return
}
no404 := r.URL.Query().Get("no404")
handleLocalStreamFile(w, r, path, no404 != "")
}
func handleStreamFile(w http.ResponseWriter, r *http.Request) {
conn := r.URL.Query().Get("connection")
if conn == "" {
@ -322,14 +339,16 @@ func handleStreamFile(w http.ResponseWriter, r *http.Request) {
return
}
no404 := r.URL.Query().Get("no404")
if conn == wshrpc.LocalConnName {
handleLocalStreamFile(w, r, path, no404 != "")
} else {
err := handleRemoteStreamFile(w, r, conn, path, no404 != "")
if err != nil {
log.Printf("error streaming remote file %q %q: %v\n", conn, path, err)
http.Error(w, fmt.Sprintf("error streaming file: %v", err), http.StatusInternalServerError)
}
data := wshrpc.FileData{
Info: &wshrpc.FileInfo{
Path: path,
},
}
rtnCh := fileshare.ReadStream(r.Context(), data)
err := handleRemoteStreamFileFromCh(w, r, path, rtnCh, nil, no404 != "")
if err != nil {
log.Printf("error streaming file %q %q: %v\n", conn, path, err)
http.Error(w, fmt.Sprintf("error streaming file: %v", err), http.StatusInternalServerError)
}
}
@ -423,7 +442,9 @@ const schemaPrefix = "/schema/"
// blocking
func RunWebServer(listener net.Listener) {
gr := mux.NewRouter()
gr.HandleFunc("/wave/stream-local-file", WebFnWrap(WebFnOpts{AllowCaching: true}, handleStreamLocalFile))
gr.HandleFunc("/wave/stream-file", WebFnWrap(WebFnOpts{AllowCaching: true}, handleStreamFile))
gr.PathPrefix("/wave/stream-file/").HandlerFunc(WebFnWrap(WebFnOpts{AllowCaching: true}, handleStreamFile))
gr.HandleFunc("/wave/file", WebFnWrap(WebFnOpts{AllowCaching: false}, handleWaveFile))
gr.HandleFunc("/wave/service", WebFnWrap(WebFnOpts{JsonErrors: true}, handleService))
gr.HandleFunc("/vdom/{uuid}/{path:.*}", WebFnWrap(WebFnOpts{AllowCaching: true}, handleVDom))