fixed the remote icon handler

canary
WolverinDEV 2020-09-26 20:39:37 +02:00
parent ef07b0c2e5
commit 3fb5ccf8cf
3 changed files with 196 additions and 9 deletions

View File

@ -1,6 +1,6 @@
import {Registry} from "tc-shared/events";
export const kIPCIconChannel = "avatars";
export const kIPCIconChannel = "icons";
export const kGlobalIconHandlerId = "global";
export interface RemoteIconEvents {
@ -22,8 +22,8 @@ export abstract class RemoteIcon {
private state: RemoteIconState;
private imageUrl: string;
private errorMessage: string;
protected imageUrl: string;
protected errorMessage: string;
protected constructor(serverUniqueId: string, iconId: number) {
this.events = new Registry<RemoteIconEvents>();
@ -55,6 +55,10 @@ export abstract class RemoteIcon {
this.events.fire("notify_state_changed", { newState: state, oldState: oldState });
}
hasImageUrl() : boolean {
return !!this.imageUrl;
}
/**
* Will throw an string if the icon isn't in loaded state
*/
@ -64,7 +68,7 @@ export abstract class RemoteIcon {
}
if(!this.imageUrl) {
throw tr("remote icon is missing an image url");
throw tra("remote {} icon is missing an image url", this.iconId);
}
return this.imageUrl;
@ -112,6 +116,10 @@ export abstract class RemoteIcon {
}
export abstract class AbstractIconManager {
protected static iconUniqueKey(iconId: number, serverUniqueId: string) : string {
return "v2-" + serverUniqueId + "-" + iconId;
}
/**
* @param iconId The requested icon
* @param serverUniqueId The server unique id for the icon

View File

@ -1,7 +1,7 @@
import * as loader from "tc-loader";
import {Stage} from "tc-loader";
import {ImageCache, ImageType, imageType2MediaType, responseImageType} from "tc-shared/file/ImageCache";
import {AbstractIconManager, RemoteIcon, RemoteIconState, setIconManager} from "tc-shared/file/Icons";
import {AbstractIconManager, kIPCIconChannel, RemoteIcon, RemoteIconState, setIconManager} from "tc-shared/file/Icons";
import * as log from "tc-shared/log";
import {LogCategory, logDebug, logError, logWarn} from "tc-shared/log";
import {server_connections} from "tc-shared/ConnectionManager";
@ -10,6 +10,9 @@ import {FileTransferState, ResponseTransferTarget, TransferProvider, TransferTar
import {tr} from "tc-shared/i18n/localize";
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
import {ErrorCode} from "tc-shared/connection/ErrorCode";
import {ChannelMessage, IPCChannel} from "tc-shared/ipc/BrowserIPC";
import * as ipc from "tc-shared/ipc/BrowserIPC";
import {kIPCAvatarChannel} from "tc-shared/file/Avatars";
/* TODO: Retry icon download after some time */
/* TODO: Download icon when we're connected to the server were we want the icon from and update the icon */
@ -38,6 +41,13 @@ class LocalRemoteIcon extends RemoteIcon {
super(serverUniqueId, iconId);
}
destroy() {
super.destroy();
if(this.imageUrl && "revokeObjectURL" in URL) {
URL.revokeObjectURL(this.imageUrl);
}
}
public setImageUrl(url: string) {
super.setImageUrl(url);
}
@ -55,14 +65,14 @@ export let localIconCache: ImageCache;
class IconManager extends AbstractIconManager {
private cachedIcons: {[key: string]: LocalRemoteIcon} = {};
private connectionStateChangeListener: {[key: string]: (handlerId: string, event: ConnectionEvents["notify_connection_state_changed"]) => void} = {};
private static iconUniqueKey(iconId: number, serverUniqueId: string) : string {
return "v2-" + serverUniqueId + "-" + iconId;
}
private ipcChannel: IPCChannel;
constructor() {
super();
this.ipcChannel = ipc.getInstance().createChannel(undefined, kIPCIconChannel);
this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this);
server_connections.events().on("notify_handler_created", event => {
this.connectionStateChangeListener[event.handlerId] = this.handleHandlerStateChange.bind(this, event.handlerId);
event.handler.events().on("notify_connection_state_changed", this.connectionStateChangeListener[event.handlerId] as any);
@ -76,6 +86,13 @@ class IconManager extends AbstractIconManager {
});
}
destroy() {
Object.values(this.cachedIcons).forEach(icon => icon.destroy());
this.cachedIcons = {};
/* TODO: Unregister server handler events */
}
private handleHandlerStateChange(handlerId: string, event: ConnectionEvents["notify_connection_state_changed"]) {
const connection = server_connections.findConnection(handlerId);
if(!connection) {
@ -99,6 +116,40 @@ class IconManager extends AbstractIconManager {
});
}
private handleIconStateChanged(icon: RemoteIcon) {
this.sendIconStateChange(icon);
}
private sendIconStateChange(icon: RemoteIcon, remoteId?: string) {
let data = {} as any;
data.iconUniqueId = IconManager.iconUniqueKey(icon.iconId, icon.serverUniqueId);
data.status = icon.getState();
switch (icon.getState()) {
case "loaded":
data.url = icon.hasImageUrl() ? icon.getImageUrl() : undefined;
break;
case "error":
data.errorMessage = icon.getErrorMessage();
break;
}
this.ipcChannel.sendMessage("notify-icon-status", data, remoteId);
}
private handleIpcMessage(remoteId: string, broadcast: boolean, message: ChannelMessage) {
if(broadcast) { return; }
if(message.type === "initialize") {
this.ipcChannel.sendMessage("initialized", {}, remoteId);
return;
} else if(message.type === "icon-resolve") {
this.sendIconStateChange(this.resolveIcon(message.data.iconId, message.data.serverUniqueId, message.data.handlerId), remoteId);
}
}
resolveIcon(iconId: number, serverUniqueId: string, handlerIdHint: string): RemoteIcon {
/* just to ensure */
iconId = iconId >>> 0;
@ -111,6 +162,8 @@ class IconManager extends AbstractIconManager {
let icon = new LocalRemoteIcon(serverUniqueId, iconId);
this.cachedIcons[iconUniqueId] = icon;
icon.events.on("notify_state_changed", () => this.handleIconStateChanged(icon));
if(iconId >= 0 && iconId <= 1000) {
icon.setState("loaded");
} else {

View File

@ -0,0 +1,126 @@
import {AbstractIconManager, kIPCIconChannel, RemoteIcon, RemoteIconState, setIconManager} from "tc-shared/file/Icons";
import * as loader from "tc-loader";
import {Stage} from "tc-loader";
import {ChannelMessage, IPCChannel} from "tc-shared/ipc/BrowserIPC";
import * as ipc from "tc-shared/ipc/BrowserIPC";
import {Settings} from "tc-shared/settings";
import {LogCategory, logWarn} from "tc-shared/log";
class RemoteRemoteIcon extends RemoteIcon {
constructor(serverUniqueId: string, iconId: number) {
super(serverUniqueId, iconId);
}
public setState(state: RemoteIconState) {
super.setState(state);
}
public setErrorMessage(message: string) {
super.setErrorMessage(message);
}
public setImageUrl(url: string) {
super.setImageUrl(url);
}
}
class RemoteIconManager extends AbstractIconManager {
private readonly ipcChannel: IPCChannel;
private callbackInitialized: () => void;
private cachedIcons: {[key: string]: RemoteRemoteIcon} = {};
constructor() {
super();
this.ipcChannel = ipc.getInstance().createChannel(Settings.instance.static(Settings.KEY_IPC_REMOTE_ADDRESS, "invalid"), kIPCIconChannel);
this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this);
}
async initialize() {
this.ipcChannel.sendMessage("initialize", {});
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
this.callbackInitialized = undefined;
reject(tr("initialize timeout"));
}, 5000);
this.callbackInitialized = () => {
clearTimeout(timeout);
resolve();
}
});
}
resolveIcon(iconId: number, serverUniqueId: string, handlerId?: string): RemoteIcon {
iconId = iconId >>> 0;
const uniqueId = RemoteIconManager.iconUniqueKey(iconId, serverUniqueId);
if(this.cachedIcons[uniqueId]) { return this.cachedIcons[uniqueId]; }
const icon = new RemoteRemoteIcon(serverUniqueId, iconId);
this.cachedIcons[uniqueId] = icon;
this.ipcChannel.sendMessage("icon-resolve", {
iconId: iconId,
serverUniqueId: serverUniqueId,
handlerId: handlerId
});
return icon;
}
private handleIpcMessage(_remoteId: string, broadcast: boolean, message: ChannelMessage) {
if(!broadcast) {
if(message.type === "initialized") {
if(this.callbackInitialized)
this.callbackInitialized();
}
}
if(message.type === "notify-icon-status") {
const icon = this.cachedIcons[message.data.iconUniqueId];
if(!icon) { return; }
switch (message.data.status as RemoteIconState) {
case "destroyed":
delete this.cachedIcons[message.data.iconUniqueId];
icon.destroy();
break;
case "empty":
icon.setState("empty");
break;
case "error":
icon.setErrorMessage(message.data.errorMessage);
icon.setState("error");
break;
case "loaded":
icon.setImageUrl(message.data.url);
icon.setState("loaded");
break;
case "loading":
icon.setState("loading");
break;
default:
logWarn(LogCategory.FILE_TRANSFER, tr("Received remote icon state change with an unknown state %s"), message.data.state);
break;
}
}
}
}
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
priority: 10,
name: "IPC icon init",
function: async () => {
let instance = new RemoteIconManager();
await instance.initialize();
setIconManager(instance);
}
});