2020-03-30 13:44:18 +02:00
import * as log from "tc-shared/log" ;
2020-04-01 21:47:33 +02:00
import * as hex from "tc-shared/crypto/hex" ;
2020-03-30 13:44:18 +02:00
import { LogCategory } from "tc-shared/log" ;
import { ChannelEntry } from "tc-shared/ui/channel" ;
import { ConnectionHandler } from "tc-shared/ConnectionHandler" ;
import { ServerCommand } from "tc-shared/connection/ConnectionBase" ;
import { CommandResult } from "tc-shared/connection/ServerConnectionDeclaration" ;
import { ClientEntry } from "tc-shared/ui/client" ;
import { AbstractCommandHandler } from "tc-shared/connection/AbstractCommandHandler" ;
export class FileEntry {
2018-02-27 17:20:49 +01:00
name : string ;
datetime : number ;
type : number ;
size : number ;
}
2020-03-30 13:44:18 +02:00
export class FileListRequest {
2018-02-27 17:20:49 +01:00
path : string ;
entries : FileEntry [ ] ;
callback : ( entries : FileEntry [ ] ) = > void ;
}
2020-03-30 13:44:18 +02:00
export interface TransferKey {
client_transfer_id : number ;
server_transfer_id : number ;
2018-02-27 17:20:49 +01:00
2020-03-30 13:44:18 +02:00
key : string ;
2018-02-27 17:20:49 +01:00
2020-03-30 13:44:18 +02:00
file_path : string ;
file_name : string ;
2019-03-22 22:43:27 +01:00
2020-03-30 13:44:18 +02:00
peer : {
hosts : string [ ] ,
port : number ;
} ;
2019-03-28 17:30:00 +01:00
2020-03-30 13:44:18 +02:00
total_size : number ;
}
2019-03-28 17:30:00 +01:00
2020-03-30 13:44:18 +02:00
export interface UploadOptions {
name : string ;
path : string ;
2019-03-28 17:30:00 +01:00
2020-03-30 13:44:18 +02:00
channel? : ChannelEntry ;
channel_password? : string ;
2019-03-28 17:30:00 +01:00
2020-03-30 13:44:18 +02:00
size : number ;
overwrite : boolean ;
}
2019-03-28 17:30:00 +01:00
2020-03-30 13:44:18 +02:00
export interface DownloadTransfer {
get_key ( ) : DownloadKey ;
2018-02-27 17:20:49 +01:00
2020-03-30 13:44:18 +02:00
request_file ( ) : Promise < Response > ;
}
2019-09-01 21:35:59 +02:00
2020-03-30 13:44:18 +02:00
export interface UploadTransfer {
get_key ( ) : UploadKey ;
2018-02-27 17:20:49 +01:00
2020-03-30 13:44:18 +02:00
put_data ( data : BlobPart | File ) : Promise < void > ;
}
2018-02-27 17:20:49 +01:00
2020-03-30 13:44:18 +02:00
export type DownloadKey = TransferKey ;
export type UploadKey = TransferKey ;
export function spawn_download_transfer ( key : DownloadKey ) : DownloadTransfer {
return new RequestFileDownload ( key ) ;
}
export function spawn_upload_transfer ( key : UploadKey ) : UploadTransfer {
return new RequestFileUpload ( key ) ;
2018-02-27 17:20:49 +01:00
}
2019-04-15 15:33:51 +02:00
2020-03-30 13:44:18 +02:00
export class RequestFileDownload implements DownloadTransfer {
readonly transfer_key : DownloadKey ;
2019-03-22 22:43:27 +01:00
2020-03-30 13:44:18 +02:00
constructor ( key : DownloadKey ) {
2019-03-22 22:43:27 +01:00
this . transfer_key = key ;
}
async request_file ( ) : Promise < Response > {
return await this . try_fetch ( "https://" + this . transfer_key . peer . hosts [ 0 ] + ":" + this . transfer_key . peer . port ) ;
}
private async try_fetch ( url : string ) : Promise < Response > {
const response = await fetch ( url , {
method : 'GET' ,
cache : "no-cache" ,
mode : 'cors' ,
headers : {
'transfer-key' : this . transfer_key . key ,
'download-name' : this . transfer_key . file_name ,
'Access-Control-Allow-Headers' : '*' ,
'Access-Control-Expose-Headers' : '*'
}
} ) ;
2019-06-26 14:06:20 +02:00
if ( ! response . ok ) {
debugger ;
2019-03-22 22:43:27 +01:00
throw ( response . type == 'opaque' || response . type == 'opaqueredirect' ? "invalid cross origin flag! May target isn't a TeaSpeak server?" : response . statusText || "response is not ok" ) ;
2019-06-26 14:06:20 +02:00
}
2019-03-22 22:43:27 +01:00
return response ;
}
2019-04-15 15:33:51 +02:00
2020-03-30 13:44:18 +02:00
get_key ( ) : DownloadKey {
2019-04-15 15:33:51 +02:00
return this . transfer_key ;
}
2019-03-22 22:43:27 +01:00
}
2018-02-27 17:20:49 +01:00
2020-03-30 13:44:18 +02:00
export class RequestFileUpload implements UploadTransfer {
readonly transfer_key : UploadKey ;
constructor ( key : DownloadKey ) {
2019-03-28 17:30:00 +01:00
this . transfer_key = key ;
}
2020-03-30 13:44:18 +02:00
get_key ( ) : UploadKey {
2019-09-01 21:35:59 +02:00
return this . transfer_key ;
}
2019-09-01 17:24:06 +02:00
async put_data ( data : BlobPart | File ) : Promise < void > {
2019-03-28 17:30:00 +01:00
const form_data = new FormData ( ) ;
if ( data instanceof File ) {
if ( data . size != this . transfer_key . total_size )
throw "invalid size" ;
form_data . append ( "file" , data ) ;
2019-08-21 10:00:01 +02:00
} else if ( typeof ( data ) === "string" ) {
if ( data . length != this . transfer_key . total_size )
throw "invalid size" ;
form_data . append ( "file" , new Blob ( [ data ] , { type : "application/octet-stream" } ) ) ;
2019-03-28 17:30:00 +01:00
} else {
const buffer = < BufferSource > data ;
if ( buffer . byteLength != this . transfer_key . total_size )
throw "invalid size" ;
form_data . append ( "file" , new Blob ( [ buffer ] , { type : "application/octet-stream" } ) ) ;
}
await this . try_put ( form_data , "https://" + this . transfer_key . peer . hosts [ 0 ] + ":" + this . transfer_key . peer . port ) ;
}
2019-09-01 17:24:06 +02:00
private async try_put ( data : FormData , url : string ) : Promise < void > {
2019-03-28 17:30:00 +01:00
const response = await fetch ( url , {
method : 'POST' ,
cache : "no-cache" ,
mode : 'cors' ,
body : data ,
headers : {
'transfer-key' : this . transfer_key . key ,
'Access-Control-Allow-Headers' : '*' ,
'Access-Control-Expose-Headers' : '*'
}
} ) ;
if ( ! response . ok )
throw ( response . type == 'opaque' || response . type == 'opaqueredirect' ? "invalid cross origin flag! May target isn't a TeaSpeak server?" : response . statusText || "response is not ok" ) ;
}
}
2020-03-30 13:44:18 +02:00
export class FileManager extends AbstractCommandHandler {
2019-04-04 21:47:52 +02:00
handle : ConnectionHandler ;
2018-02-27 17:20:49 +01:00
icons : IconManager ;
2018-04-16 20:38:35 +02:00
avatars : AvatarManager ;
2018-02-27 17:20:49 +01:00
private listRequests : FileListRequest [ ] = [ ] ;
2020-03-30 13:44:18 +02:00
private pending_download_requests : DownloadKey [ ] = [ ] ;
private pending_upload_requests : UploadKey [ ] = [ ] ;
2019-03-28 17:30:00 +01:00
2019-04-25 20:22:13 +02:00
private transfer_counter : number = 1 ;
2018-02-27 17:20:49 +01:00
2019-04-04 21:47:52 +02:00
constructor ( client : ConnectionHandler ) {
2019-02-23 14:15:22 +01:00
super ( client . serverConnection ) ;
2018-02-27 17:20:49 +01:00
this . handle = client ;
this . icons = new IconManager ( this ) ;
2018-04-16 20:38:35 +02:00
this . avatars = new AvatarManager ( this ) ;
2018-02-27 17:20:49 +01:00
2019-02-23 14:15:22 +01:00
this . connection . command_handler_boss ( ) . register_handler ( this ) ;
}
2019-08-21 10:00:01 +02:00
destroy() {
if ( this . connection ) {
const hboss = this . connection . command_handler_boss ( ) ;
if ( hboss )
hboss . unregister_handler ( this ) ;
}
this . listRequests = undefined ;
this . pending_download_requests = undefined ;
this . pending_upload_requests = undefined ;
this . icons && this . icons . destroy ( ) ;
this . icons = undefined ;
this . avatars && this . avatars . destroy ( ) ;
this . avatars = undefined ;
}
2020-03-30 13:44:18 +02:00
handle_command ( command : ServerCommand ) : boolean {
2019-02-23 14:15:22 +01:00
switch ( command . command ) {
case "notifyfilelist" :
this . notifyFileList ( command . arguments ) ;
return true ;
case "notifyfilelistfinished" :
this . notifyFileListFinished ( command . arguments ) ;
return true ;
case "notifystartdownload" :
this . notifyStartDownload ( command . arguments ) ;
return true ;
2019-03-28 17:30:00 +01:00
case "notifystartupload" :
this . notifyStartUpload ( command . arguments ) ;
return true ;
2019-02-23 14:15:22 +01:00
}
return false ;
2018-02-27 17:20:49 +01:00
}
/******************************** File list ********************************/
//TODO multiple requests (same path)
requestFileList ( path : string , channel? : ChannelEntry , password? : string ) : Promise < FileEntry [ ] > {
const _this = this ;
return new Promise ( ( accept , reject ) = > {
let req = new FileListRequest ( ) ;
req . path = path ;
req . entries = [ ] ;
req . callback = accept ;
_this . listRequests . push ( req ) ;
2019-02-23 14:15:22 +01:00
_this . handle . serverConnection . send_command ( "ftgetfilelist" , { "path" : path , "cid" : ( channel ? channel . channelId : "0" ) , "cpw" : ( password ? password : "" ) } ) . then ( ( ) = > { } ) . catch ( reason = > {
2018-02-27 17:20:49 +01:00
_this . listRequests . remove ( req ) ;
if ( reason instanceof CommandResult ) {
if ( reason . id == 0x0501 ) {
accept ( [ ] ) ; //Empty result
return ;
}
}
reject ( reason ) ;
} ) ;
} ) ;
}
private notifyFileList ( json ) {
let entry : FileListRequest = undefined ;
for ( let e of this . listRequests ) {
if ( e . path == json [ 0 ] [ "path" ] ) {
entry = e ;
break ;
}
}
if ( ! entry ) {
2019-08-30 23:06:39 +02:00
log . error ( LogCategory . CLIENT , tr ( "Invalid file list entry. Path: %s" ) , json [ 0 ] [ "path" ] ) ;
2018-02-27 17:20:49 +01:00
return ;
}
2019-03-25 20:04:04 +01:00
for ( let e of ( json as Array < FileEntry > ) ) {
e . datetime = parseInt ( e . datetime + "" ) ;
e . size = parseInt ( e . size + "" ) ;
e . type = parseInt ( e . type + "" ) ;
2018-02-27 17:20:49 +01:00
entry . entries . push ( e ) ;
2019-03-25 20:04:04 +01:00
}
2018-02-27 17:20:49 +01:00
}
private notifyFileListFinished ( json ) {
let entry : FileListRequest = undefined ;
for ( let e of this . listRequests ) {
if ( e . path == json [ 0 ] [ "path" ] ) {
entry = e ;
this . listRequests . remove ( e ) ;
break ;
}
}
if ( ! entry ) {
2019-08-30 23:06:39 +02:00
log . error ( LogCategory . CLIENT , tr ( "Invalid file list entry finish. Path: " ) , json [ 0 ] [ "path" ] ) ;
2018-02-27 17:20:49 +01:00
return ;
}
entry . callback ( entry . entries ) ;
}
2019-03-28 17:30:00 +01:00
/******************************** File download/upload ********************************/
2020-03-30 13:44:18 +02:00
download_file ( path : string , file : string , channel? : ChannelEntry , password? : string ) : Promise < DownloadKey > {
const transfer_data : DownloadKey = {
2019-03-22 22:43:27 +01:00
file_name : file ,
2019-03-28 17:30:00 +01:00
file_path : path ,
client_transfer_id : this.transfer_counter ++
2019-03-22 22:43:27 +01:00
} as any ;
2019-03-28 17:30:00 +01:00
this . pending_download_requests . push ( transfer_data ) ;
2020-03-30 13:44:18 +02:00
return new Promise < DownloadKey > ( ( resolve , reject ) = > {
2019-03-28 17:30:00 +01:00
transfer_data [ "_callback" ] = resolve ;
this . handle . serverConnection . send_command ( "ftinitdownload" , {
2018-02-27 17:20:49 +01:00
"path" : path ,
"name" : file ,
"cid" : ( channel ? channel . channelId : "0" ) ,
"cpw" : ( password ? password : "" ) ,
2019-04-15 15:33:51 +02:00
"clientftfid" : transfer_data . client_transfer_id ,
2019-04-25 17:19:54 +02:00
"seekpos" : 0 ,
"proto" : 1
2019-08-21 10:00:01 +02:00
} , { process_result : false } ) . catch ( reason = > {
2019-03-28 17:30:00 +01:00
this . pending_download_requests . remove ( transfer_data ) ;
reject ( reason ) ;
} )
} ) ;
}
2020-03-30 13:44:18 +02:00
upload_file ( options : UploadOptions ) : Promise < UploadKey > {
const transfer_data : UploadKey = {
2019-03-28 17:30:00 +01:00
file_path : options.path ,
file_name : options.name ,
client_transfer_id : this.transfer_counter ++ ,
total_size : options.size
} as any ;
this . pending_upload_requests . push ( transfer_data ) ;
2020-03-30 13:44:18 +02:00
return new Promise < UploadKey > ( ( resolve , reject ) = > {
2019-03-28 17:30:00 +01:00
transfer_data [ "_callback" ] = resolve ;
this . handle . serverConnection . send_command ( "ftinitupload" , {
"path" : options . path ,
"name" : options . name ,
"cid" : ( options . channel ? options . channel . channelId : "0" ) ,
"cpw" : options . channel_password || "" ,
"clientftfid" : transfer_data . client_transfer_id ,
"size" : options . size ,
"overwrite" : options . overwrite ,
2019-04-25 17:19:54 +02:00
"resume" : false ,
"proto" : 1
2019-03-28 17:30:00 +01:00
} ) . catch ( reason = > {
this . pending_upload_requests . remove ( transfer_data ) ;
2018-02-27 17:20:49 +01:00
reject ( reason ) ;
} )
} ) ;
}
private notifyStartDownload ( json ) {
json = json [ 0 ] ;
2019-04-15 15:33:51 +02:00
let clientftfid = parseInt ( json [ "clientftfid" ] ) ;
2020-03-30 13:44:18 +02:00
let transfer : DownloadKey ;
2019-03-28 17:30:00 +01:00
for ( let e of this . pending_download_requests )
2019-04-15 15:33:51 +02:00
if ( e . client_transfer_id == clientftfid ) {
2018-02-27 17:20:49 +01:00
transfer = e ;
break ;
}
2019-04-15 15:33:51 +02:00
transfer . server_transfer_id = parseInt ( json [ "serverftfid" ] ) ;
2019-03-22 22:43:27 +01:00
transfer . key = json [ "ftkey" ] ;
transfer . total_size = json [ "size" ] ;
2018-02-27 17:20:49 +01:00
2019-03-22 22:43:27 +01:00
transfer . peer = {
hosts : ( json [ "ip" ] || "" ) . split ( "," ) ,
2019-04-15 15:33:51 +02:00
port : parseInt ( json [ "port" ] )
2019-03-22 22:43:27 +01:00
} ;
if ( transfer . peer . hosts . length == 0 )
transfer . peer . hosts . push ( "0.0.0.0" ) ;
if ( transfer . peer . hosts [ 0 ] . length == 0 || transfer . peer . hosts [ 0 ] == '0.0.0.0' )
2019-04-15 15:33:51 +02:00
transfer . peer . hosts [ 0 ] = this . handle . serverConnection . remote_address ( ) . host ;
2018-02-27 17:20:49 +01:00
2020-03-30 13:44:18 +02:00
( transfer [ "_callback" ] as ( val : DownloadKey ) = > void ) ( transfer ) ;
2019-03-28 17:30:00 +01:00
this . pending_download_requests . remove ( transfer ) ;
}
private notifyStartUpload ( json ) {
json = json [ 0 ] ;
2020-03-30 13:44:18 +02:00
let transfer : UploadKey ;
2019-04-15 15:33:51 +02:00
let clientftfid = parseInt ( json [ "clientftfid" ] ) ;
2019-03-28 17:30:00 +01:00
for ( let e of this . pending_upload_requests )
2019-04-15 15:33:51 +02:00
if ( e . client_transfer_id == clientftfid ) {
2019-03-28 17:30:00 +01:00
transfer = e ;
break ;
}
2019-04-15 15:33:51 +02:00
transfer . server_transfer_id = parseInt ( json [ "serverftfid" ] ) ;
2019-03-28 17:30:00 +01:00
transfer . key = json [ "ftkey" ] ;
transfer . peer = {
hosts : ( json [ "ip" ] || "" ) . split ( "," ) ,
2019-04-15 15:33:51 +02:00
port : parseInt ( json [ "port" ] )
2019-03-28 17:30:00 +01:00
} ;
if ( transfer . peer . hosts . length == 0 )
transfer . peer . hosts . push ( "0.0.0.0" ) ;
if ( transfer . peer . hosts [ 0 ] . length == 0 || transfer . peer . hosts [ 0 ] == '0.0.0.0' )
2019-04-15 15:33:51 +02:00
transfer . peer . hosts [ 0 ] = this . handle . serverConnection . remote_address ( ) . host ;
2019-03-28 17:30:00 +01:00
2020-03-30 13:44:18 +02:00
( transfer [ "_callback" ] as ( val : UploadKey ) = > void ) ( transfer ) ;
2019-03-28 17:30:00 +01:00
this . pending_upload_requests . remove ( transfer ) ;
2018-02-27 17:20:49 +01:00
}
2019-05-24 22:14:02 +02:00
/** File management **/
async delete_file ( props : {
name : string ,
path? : string ;
cid? : number ;
cpw? : string ;
} ) : Promise < void > {
if ( ! props . name )
throw "invalid name!" ;
try {
await this . handle . serverConnection . send_command ( "ftdeletefile" , {
cid : props.cid || 0 ,
cpw : props.cpw ,
path : props.path || "" ,
name : props.name
} )
} catch ( error ) {
throw error ;
}
}
2018-02-27 17:20:49 +01:00
}
2020-03-30 13:44:18 +02:00
export class Icon {
2018-02-27 17:20:49 +01:00
id : number ;
2019-03-22 22:43:27 +01:00
url : string ;
2018-02-27 17:20:49 +01:00
}
2020-03-30 13:44:18 +02:00
export enum ImageType {
2018-11-25 13:45:45 +01:00
UNKNOWN ,
BITMAP ,
PNG ,
GIF ,
SVG ,
JPEG
}
2020-03-30 13:44:18 +02:00
export function media_image_type ( type : ImageType , file? : boolean ) {
2018-11-25 13:45:45 +01:00
switch ( type ) {
case ImageType . BITMAP :
return "bmp" ;
case ImageType . GIF :
return "gif" ;
case ImageType . SVG :
2019-03-25 20:04:04 +01:00
return file ? "svg" : "svg+xml" ;
2018-11-25 13:45:45 +01:00
case ImageType . JPEG :
return "jpeg" ;
case ImageType . UNKNOWN :
case ImageType . PNG :
default :
return "png" ;
}
}
2020-03-30 13:44:18 +02:00
export function image_type ( encoded_data : string | ArrayBuffer , base64_encoded? : boolean ) {
2019-05-24 22:14:02 +02:00
const ab2str10 = ( ) = > {
2019-08-21 10:00:01 +02:00
const buf = new Uint8Array ( encoded_data as ArrayBuffer ) ;
2019-05-24 22:14:02 +02:00
if ( buf . byteLength < 10 )
return "" ;
let result = "" ;
for ( let index = 0 ; index < 10 ; index ++ )
result += String . fromCharCode ( buf [ index ] ) ;
return result ;
} ;
2019-08-21 10:00:01 +02:00
const bin = typeof ( encoded_data ) === "string" ? ( ( typeof ( base64_encoded ) === "undefined" || base64_encoded ) ? atob ( encoded_data ) : encoded_data ) : ab2str10 ( ) ;
2018-11-25 13:45:45 +01:00
if ( bin . length < 10 ) return ImageType . UNKNOWN ;
if ( bin [ 0 ] == String . fromCharCode ( 66 ) && bin [ 1 ] == String . fromCharCode ( 77 ) ) {
return ImageType . BITMAP ;
} else if ( bin . substr ( 0 , 8 ) == "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a" ) {
return ImageType . PNG ;
} else if ( bin . substr ( 0 , 4 ) == "\x47\x49\x46\x38" && ( bin [ 4 ] == '\x37' || bin [ 4 ] == '\x39' ) && bin [ 5 ] == '\x61' ) {
return ImageType . GIF ;
} else if ( bin [ 0 ] == '\x3c' ) {
return ImageType . SVG ;
} else if ( bin [ 0 ] == '\xFF' && bin [ 1 ] == '\xd8' ) {
return ImageType . JPEG ;
}
return ImageType . UNKNOWN ;
}
2020-03-30 13:44:18 +02:00
export class CacheManager {
2019-03-22 22:43:27 +01:00
readonly cache_name : string ;
private _cache_category : Cache ;
constructor ( name : string ) {
this . cache_name = name ;
}
setupped ( ) : boolean { return ! ! this . _cache_category ; }
2019-06-26 14:06:20 +02:00
async reset() {
if ( ! window . caches )
return ;
try {
await caches . delete ( this . cache_name ) ;
} catch ( error ) {
throw "Failed to delete cache: " + error ;
}
try {
await this . setup ( ) ;
} catch ( error ) {
throw "Failed to reinitialize cache!" ;
}
}
2019-03-22 22:43:27 +01:00
async setup() {
if ( ! window . caches )
throw "Missing caches!" ;
this . _cache_category = await caches . open ( this . cache_name ) ;
}
async cleanup ( max_age : number ) {
/* FIXME: TODO */
}
async resolve_cached ( key : string , max_age? : number ) : Promise < Response | undefined > {
max_age = typeof ( max_age ) === "number" ? max_age : - 1 ;
2019-08-21 10:00:01 +02:00
const cached_response = await this . _cache_category . match ( "https://_local_cache/cache_request_" + key ) ;
2019-03-22 22:43:27 +01:00
if ( ! cached_response )
return undefined ;
/* FIXME: Max age */
return cached_response ;
}
async put_cache ( key : string , value : Response , type ? : string , headers ? : { [ key : string ] : string } ) {
const new_headers = new Headers ( ) ;
for ( const key of value . headers . keys ( ) )
new_headers . set ( key , value . headers . get ( key ) ) ;
if ( type )
new_headers . set ( "Content-type" , type ) ;
for ( const key of Object . keys ( headers || { } ) )
new_headers . set ( key , headers [ key ] ) ;
2019-08-21 10:00:01 +02:00
await this . _cache_category . put ( "https://_local_cache/cache_request_" + key , new Response ( value . body , {
2019-03-22 22:43:27 +01:00
headers : new_headers
} ) ) ;
}
2019-08-21 10:00:01 +02:00
async delete ( key : string ) {
const flag = await this . _cache_category . delete ( "https://_local_cache/cache_request_" + key , {
ignoreVary : true ,
ignoreMethod : true ,
ignoreSearch : true
} ) ;
if ( ! flag ) {
console . warn ( tr ( "Failed to delete key %s from cache!" ) , flag ) ;
}
}
2019-03-22 22:43:27 +01:00
}
2020-03-30 13:44:18 +02:00
export class IconManager {
2019-08-21 10:00:01 +02:00
private static cache : CacheManager = new CacheManager ( "icons" ) ;
2019-04-04 21:47:52 +02:00
2018-02-27 17:20:49 +01:00
handle : FileManager ;
2019-03-22 22:43:27 +01:00
private _id_urls : { [ id :number ] : string } = { } ;
private _loading_promises : { [ id :number ] : Promise < Icon > } = { } ;
2018-02-27 17:20:49 +01:00
constructor ( handle : FileManager ) {
this . handle = handle ;
2019-08-21 10:00:01 +02:00
}
2019-04-04 21:47:52 +02:00
2019-08-21 10:00:01 +02:00
destroy() {
if ( URL . revokeObjectURL ) {
for ( const id of Object . keys ( this . _id_urls ) )
URL . revokeObjectURL ( this . _id_urls [ id ] ) ;
}
this . _id_urls = undefined ;
this . _loading_promises = undefined ;
2018-02-27 17:20:49 +01:00
}
2019-06-26 14:06:20 +02:00
async clear_cache() {
await IconManager . cache . reset ( ) ;
if ( URL . revokeObjectURL ) {
for ( const id of Object . keys ( this . _id_urls ) )
URL . revokeObjectURL ( this . _id_urls [ id ] ) ;
}
this . _id_urls = { } ;
this . _loading_promises = { } ;
}
2019-05-24 22:14:02 +02:00
async delete_icon ( id : number ) : Promise < void > {
if ( id <= 1000 )
throw "invalid id!" ;
await this . handle . delete_file ( {
name : '/icon_' + id
} ) ;
}
2018-02-27 17:20:49 +01:00
iconList ( ) : Promise < FileEntry [ ] > {
return this . handle . requestFileList ( "/icons" ) ;
}
2020-03-30 13:44:18 +02:00
create_icon_download ( id : number ) : Promise < DownloadKey > {
2019-03-22 22:43:27 +01:00
return this . handle . download_file ( "" , "/icon_" + id ) ;
2018-02-27 17:20:49 +01:00
}
2019-08-21 10:00:01 +02:00
private static async _response_url ( response : Response ) {
2019-03-22 22:43:27 +01:00
if ( ! response . headers . has ( 'X-media-bytes' ) )
throw "missing media bytes" ;
const type = image_type ( response . headers . get ( 'X-media-bytes' ) ) ;
const media = media_image_type ( type ) ;
const blob = await response . blob ( ) ;
2019-03-25 20:04:04 +01:00
if ( blob . type !== "image/" + media )
return URL . createObjectURL ( blob . slice ( 0 , blob . size , "image/" + media ) ) ;
else
return URL . createObjectURL ( blob )
2018-02-27 17:20:49 +01:00
}
2019-03-22 22:43:27 +01:00
async resolved_cached ? ( id : number ) : Promise < Icon > {
if ( this . _id_urls [ id ] )
return {
id : id ,
url : this._id_urls [ id ]
} ;
2019-04-04 21:47:52 +02:00
if ( ! IconManager . cache . setupped ( ) )
await IconManager . cache . setup ( ) ;
2019-03-22 22:43:27 +01:00
2019-04-04 21:47:52 +02:00
const response = await IconManager . cache . resolve_cached ( 'icon_' + id ) ; //TODO age!
2019-08-21 10:00:01 +02:00
if ( response ) {
const url = await IconManager . _response_url ( response ) ;
if ( this . _id_urls [ id ] )
URL . revokeObjectURL ( this . _id_urls [ id ] ) ;
2019-03-22 22:43:27 +01:00
return {
id : id ,
2019-08-21 10:00:01 +02:00
url : url
2019-03-22 22:43:27 +01:00
} ;
2019-08-21 10:00:01 +02:00
}
2019-03-22 22:43:27 +01:00
return undefined ;
2018-08-12 19:01:47 +02:00
}
2018-02-27 17:20:49 +01:00
2019-08-21 10:00:01 +02:00
private static _static_id_url : { [ icon : number ] : string } = { } ;
private static _static_cached_promise : { [ icon : number ] : Promise < Icon > } = { } ;
static load_cached_icon ( id : number , ignore_age? : boolean ) : Promise < Icon > | Icon {
if ( this . _static_id_url [ id ] ) {
return {
id : id ,
url : this._static_id_url [ id ]
} ;
}
if ( this . _static_cached_promise [ id ] )
return this . _static_cached_promise [ id ] ;
return ( this . _static_cached_promise [ id ] = ( async ( ) = > {
if ( ! this . cache . setupped ( ) )
await this . cache . setup ( ) ;
const response = await this . cache . resolve_cached ( 'icon_' + id ) ; //TODO age!
if ( response ) {
const url = await this . _response_url ( response ) ;
if ( this . _static_id_url [ id ] )
URL . revokeObjectURL ( this . _static_id_url [ id ] ) ;
this . _static_id_url [ id ] = url ;
return {
id : id ,
url : url
} ;
}
} ) ( ) ) ;
}
2019-03-22 22:43:27 +01:00
private async _load_icon ( id : number ) : Promise < Icon > {
try {
2020-03-30 13:44:18 +02:00
let download_key : DownloadKey ;
2019-03-25 20:04:04 +01:00
try {
download_key = await this . create_icon_download ( id ) ;
} catch ( error ) {
2019-08-30 23:06:39 +02:00
log . error ( LogCategory . CLIENT , tr ( "Could not request download for icon %d: %o" ) , id , error ) ;
2019-03-25 20:04:04 +01:00
throw "Failed to request icon" ;
}
2018-08-12 19:01:47 +02:00
2020-03-30 13:44:18 +02:00
const downloader = spawn_download_transfer ( download_key ) ;
2019-03-25 20:04:04 +01:00
let response : Response ;
try {
response = await downloader . request_file ( ) ;
} catch ( error ) {
2019-08-30 23:06:39 +02:00
log . error ( LogCategory . CLIENT , tr ( "Could not download icon %d: %o" ) , id , error ) ;
2019-03-25 20:04:04 +01:00
throw "failed to download icon" ;
}
2019-03-22 22:43:27 +01:00
2019-03-25 20:04:04 +01:00
const type = image_type ( response . headers . get ( 'X-media-bytes' ) ) ;
const media = media_image_type ( type ) ;
2019-03-22 22:43:27 +01:00
2019-04-04 21:47:52 +02:00
await IconManager . cache . put_cache ( 'icon_' + id , response . clone ( ) , "image/" + media ) ;
2019-08-21 10:00:01 +02:00
const url = await IconManager . _response_url ( response . clone ( ) ) ;
if ( this . _id_urls [ id ] )
URL . revokeObjectURL ( this . _id_urls [ id ] ) ;
this . _id_urls [ id ] = url ;
2019-03-22 22:43:27 +01:00
2019-03-25 20:04:04 +01:00
this . _loading_promises [ id ] = undefined ;
return {
id : id ,
url : url
} ;
} catch ( error ) {
setTimeout ( ( ) = > {
this . _loading_promises [ id ] = undefined ;
} , 1000 * 60 ) ; /* try again in 60 seconds */
throw error ;
}
2019-03-22 22:43:27 +01:00
}
2019-05-25 12:20:57 +02:00
download_icon ( id : number ) : Promise < Icon > {
2019-03-22 22:43:27 +01:00
return this . _loading_promises [ id ] || ( this . _loading_promises [ id ] = this . _load_icon ( id ) ) ;
2018-02-27 17:20:49 +01:00
}
2019-05-25 12:20:57 +02:00
async resolve_icon ( id : number ) : Promise < Icon > {
id = id >>> 0 ;
try {
2019-05-25 21:20:48 +02:00
const result = await this . resolved_cached ( id ) ;
if ( result )
return result ;
throw "" ;
2019-05-25 12:20:57 +02:00
} catch ( error ) { }
try {
2019-05-25 21:20:48 +02:00
const result = await this . download_icon ( id ) ;
if ( result )
return result ;
throw "load result is empty" ;
2019-05-25 12:20:57 +02:00
} catch ( error ) {
2019-08-30 23:06:39 +02:00
log . error ( LogCategory . CLIENT , tr ( "Icon download failed of icon %d: %o" ) , id , error ) ;
2019-05-25 12:20:57 +02:00
}
throw "icon not found" ;
}
2019-10-19 17:13:40 +02:00
static generate_tag ( icon : Promise < Icon > | Icon | undefined , options ? : {
2019-03-25 20:04:04 +01:00
animate? : boolean
} ) : JQuery < HTMLDivElement > {
options = options || { } ;
2019-08-21 10:00:01 +02:00
let icon_container = $ . spawn ( "div" ) . addClass ( "icon-container icon_empty" ) ;
let icon_load_image = $ . spawn ( "div" ) . addClass ( "icon_loading" ) ;
2018-02-27 17:20:49 +01:00
2019-03-22 22:43:27 +01:00
const icon_image = $ . spawn ( "img" ) . attr ( "width" , 16 ) . attr ( "height" , 16 ) . attr ( "alt" , "" ) ;
2019-08-21 10:00:01 +02:00
const _apply = ( icon ) = > {
let id = icon ? ( icon . id >>> 0 ) : 0 ;
if ( ! icon || id == 0 ) {
icon_load_image . remove ( ) ;
icon_load_image = undefined ;
return ;
} else if ( id < 1000 ) {
icon_load_image . remove ( ) ;
icon_load_image = undefined ;
icon_container . removeClass ( "icon_empty" ) . addClass ( "icon_em client-group_" + id ) ;
return ;
}
2018-02-27 17:20:49 +01:00
2019-08-21 10:00:01 +02:00
icon_image . attr ( "src" , icon . url ) ;
icon_container . append ( icon_image ) . removeClass ( "icon_empty" ) ;
if ( typeof ( options . animate ) !== "boolean" || options . animate ) {
icon_image . css ( "opacity" , 0 ) ;
icon_load_image . animate ( { opacity : 0 } , 50 , function ( ) {
icon_load_image . remove ( ) ;
icon_image . animate ( { opacity : 1 } , 150 ) ;
} ) ;
} else {
icon_load_image . remove ( ) ;
icon_load_image = undefined ;
}
} ;
if ( icon instanceof Promise ) {
icon . then ( _apply ) . catch ( error = > {
2019-08-30 23:06:39 +02:00
log . error ( LogCategory . CLIENT , tr ( "Could not load icon. Reason: %s" ) , error ) ;
2019-08-21 10:00:01 +02:00
icon_load_image . removeClass ( "icon_loading" ) . addClass ( "icon client-warning" ) . attr ( "tag" , "Could not load icon" ) ;
} ) ;
2018-02-27 17:20:49 +01:00
} else {
2019-08-21 10:00:01 +02:00
_apply ( icon as Icon ) ;
}
if ( icon_load_image )
2019-03-22 22:43:27 +01:00
icon_load_image . appendTo ( icon_container ) ;
2019-08-21 10:00:01 +02:00
return icon_container ;
}
2019-03-22 22:43:27 +01:00
2019-08-21 10:00:01 +02:00
generateTag ( id : number , options ? : {
animate? : boolean
} ) : JQuery < HTMLDivElement > {
options = options || { } ;
2019-03-22 22:43:27 +01:00
2019-08-21 10:00:01 +02:00
id = id >>> 0 ;
if ( id == 0 || ! id )
return IconManager . generate_tag ( { id : id , url : "" } , options ) ;
else if ( id < 1000 )
return IconManager . generate_tag ( { id : id , url : "" } , options ) ;
2019-03-22 22:43:27 +01:00
2019-03-25 20:04:04 +01:00
2019-08-21 10:00:01 +02:00
if ( this . _id_urls [ id ] ) {
return IconManager . generate_tag ( { id : id , url : this._id_urls [ id ] } , options ) ;
} else {
return IconManager . generate_tag ( this . resolve_icon ( id ) , options ) ;
2018-04-16 20:38:35 +02:00
}
}
}
2020-03-30 13:44:18 +02:00
export class Avatar {
2019-03-22 22:43:27 +01:00
client_avatar_id : string ; /* the base64 uid thing from a-m */
avatar_id : string ; /* client_flag_avatar */
url : string ;
2019-03-25 20:04:04 +01:00
type : ImageType ;
2018-04-16 20:38:35 +02:00
}
2020-03-30 13:44:18 +02:00
export class AvatarManager {
2018-04-16 20:38:35 +02:00
handle : FileManager ;
2019-03-22 22:43:27 +01:00
2019-04-04 21:47:52 +02:00
private static cache : CacheManager ;
2019-03-22 22:43:27 +01:00
private _cached_avatars : { [ response_avatar_id :number ] : Avatar } = { } ;
private _loading_promises : { [ response_avatar_id :number ] : Promise < Icon > } = { } ;
2018-04-16 20:38:35 +02:00
constructor ( handle : FileManager ) {
this . handle = handle ;
2019-03-22 22:43:27 +01:00
2019-04-04 21:47:52 +02:00
if ( ! AvatarManager . cache )
AvatarManager . cache = new CacheManager ( "avatars" ) ;
2018-04-16 20:38:35 +02:00
}
2019-08-21 10:00:01 +02:00
destroy() {
this . _cached_avatars = undefined ;
this . _loading_promises = undefined ;
}
2019-03-25 20:04:04 +01:00
private async _response_url ( response : Response , type : ImageType ) : Promise < string > {
2019-03-22 22:43:27 +01:00
if ( ! response . headers . has ( 'X-media-bytes' ) )
throw "missing media bytes" ;
const media = media_image_type ( type ) ;
const blob = await response . blob ( ) ;
2019-03-25 20:04:04 +01:00
if ( blob . type !== "image/" + media )
return URL . createObjectURL ( blob . slice ( 0 , blob . size , "image/" + media ) ) ;
else
return URL . createObjectURL ( blob ) ;
2018-04-16 20:38:35 +02:00
}
2019-08-21 10:00:01 +02:00
async resolved_cached ? ( client_avatar_id : string , avatar_version? : string ) : Promise < Avatar > {
let avatar : Avatar = this . _cached_avatars [ avatar_version ] ;
2018-04-16 20:38:35 +02:00
if ( avatar ) {
2019-08-21 10:00:01 +02:00
if ( typeof ( avatar_version ) !== "string" || avatar . avatar_id == avatar_version )
2019-03-22 22:43:27 +01:00
return avatar ;
2019-08-21 10:00:01 +02:00
avatar = undefined ;
2019-03-22 22:43:27 +01:00
}
2018-08-12 18:58:15 +02:00
2019-04-04 21:47:52 +02:00
if ( ! AvatarManager . cache . setupped ( ) )
await AvatarManager . cache . setup ( ) ;
2018-08-12 18:58:15 +02:00
2019-04-04 21:47:52 +02:00
const response = await AvatarManager . cache . resolve_cached ( 'avatar_' + client_avatar_id ) ; //TODO age!
2019-03-22 22:43:27 +01:00
if ( ! response )
return undefined ;
2019-08-21 10:00:01 +02:00
let response_avatar_version = response . headers . has ( "X-avatar-version" ) ? response . headers . get ( "X-avatar-version" ) : undefined ;
if ( typeof ( avatar_version ) === "string" && response_avatar_version != avatar_version )
2019-03-22 22:43:27 +01:00
return undefined ;
2019-03-25 20:04:04 +01:00
const type = image_type ( response . headers . get ( 'X-media-bytes' ) ) ;
2019-03-22 22:43:27 +01:00
return this . _cached_avatars [ client_avatar_id ] = {
client_avatar_id : client_avatar_id ,
2019-08-21 10:00:01 +02:00
avatar_id : avatar_version || response_avatar_version ,
2019-03-25 20:04:04 +01:00
url : await this . _response_url ( response , type ) ,
type : type
2019-03-22 22:43:27 +01:00
} ;
2018-04-16 20:38:35 +02:00
}
2020-03-30 13:44:18 +02:00
create_avatar_download ( client_avatar_id : string ) : Promise < DownloadKey > {
2019-08-30 23:06:39 +02:00
log . debug ( LogCategory . GENERAL , "Requesting download for avatar %s" , client_avatar_id ) ;
2019-03-22 22:43:27 +01:00
return this . handle . download_file ( "" , "/avatar_" + client_avatar_id ) ;
2018-08-12 18:58:15 +02:00
}
2019-08-21 10:00:01 +02:00
private async _load_avatar ( client_avatar_id : string , avatar_version : string ) {
2019-03-22 22:43:27 +01:00
try {
2020-03-30 13:44:18 +02:00
let download_key : DownloadKey ;
2019-08-21 10:00:01 +02:00
try {
download_key = await this . create_avatar_download ( client_avatar_id ) ;
} catch ( error ) {
2019-08-30 23:06:39 +02:00
log . error ( LogCategory . GENERAL , tr ( "Could not request download for avatar %s: %o" ) , client_avatar_id , error ) ;
2019-08-21 10:00:01 +02:00
throw "failed to request avatar download" ;
}
2018-04-16 20:38:35 +02:00
2020-03-30 13:44:18 +02:00
const downloader = spawn_download_transfer ( download_key ) ;
2019-08-21 10:00:01 +02:00
let response : Response ;
try {
response = await downloader . request_file ( ) ;
} catch ( error ) {
2019-08-30 23:06:39 +02:00
log . error ( LogCategory . GENERAL , tr ( "Could not download avatar %s: %o" ) , client_avatar_id , error ) ;
2019-08-21 10:00:01 +02:00
throw "failed to download avatar" ;
}
2018-04-16 20:38:35 +02:00
2019-08-21 10:00:01 +02:00
const type = image_type ( response . headers . get ( 'X-media-bytes' ) ) ;
const media = media_image_type ( type ) ;
2018-04-16 20:38:35 +02:00
2019-08-21 10:00:01 +02:00
await AvatarManager . cache . put_cache ( 'avatar_' + client_avatar_id , response . clone ( ) , "image/" + media , {
"X-avatar-version" : avatar_version
} ) ;
const url = await this . _response_url ( response . clone ( ) , type ) ;
2018-08-12 18:58:15 +02:00
2019-08-21 10:00:01 +02:00
return this . _cached_avatars [ client_avatar_id ] = {
client_avatar_id : client_avatar_id ,
avatar_id : avatar_version ,
url : url ,
type : type
} ;
} finally {
this . _loading_promises [ client_avatar_id ] = undefined ;
}
2018-04-16 20:38:35 +02:00
}
2019-08-21 10:00:01 +02:00
/* loads an avatar by the avatar id and optional with the avatar version */
load_avatar ( client_avatar_id : string , avatar_version : string ) : Promise < Avatar > {
return this . _loading_promises [ client_avatar_id ] || ( this . _loading_promises [ client_avatar_id ] = this . _load_avatar ( client_avatar_id , avatar_version ) ) ;
2019-03-22 22:43:27 +01:00
}
2018-04-16 20:38:35 +02:00
2019-03-25 20:04:04 +01:00
generate_client_tag ( client : ClientEntry ) : JQuery {
return this . generate_tag ( client . avatarId ( ) , client . properties . client_flag_avatar ) ;
}
2019-08-21 10:00:01 +02:00
update_cache ( client_avatar_id : string , avatar_id : string ) {
const _cached : Avatar = this . _cached_avatars [ client_avatar_id ] ;
if ( _cached ) {
if ( _cached . avatar_id === avatar_id )
return ; /* cache is up2date */
2019-08-30 23:06:39 +02:00
log . info ( LogCategory . GENERAL , tr ( "Deleting cached avatar for client %s. Cached version: %s; New version: %s" ) , client_avatar_id , _cached . avatar_id , avatar_id ) ;
2019-08-21 10:00:01 +02:00
delete this . _cached_avatars [ client_avatar_id ] ;
AvatarManager . cache . delete ( "avatar_" + client_avatar_id ) . catch ( error = > {
log . error ( LogCategory . GENERAL , tr ( "Failed to delete cached avatar for client %o: %o" ) , client_avatar_id , error ) ;
} ) ;
} else {
this . resolved_cached ( client_avatar_id ) . then ( avatar = > {
if ( avatar && avatar . avatar_id !== avatar_id ) {
/* this time we ensured that its cached */
this . update_cache ( client_avatar_id , avatar_id ) ;
}
} ) . catch ( error = > {
log . error ( LogCategory . GENERAL , tr ( "Failed to delete cached avatar for client %o (cache lookup failed): %o" ) , client_avatar_id , error ) ;
} ) ;
}
}
2019-03-25 20:04:04 +01:00
generate_tag ( client_avatar_id : string , avatar_id? : string , options ? : {
callback_image ? : ( tag : JQuery < HTMLImageElement > ) = > any ,
callback_avatar ? : ( avatar : Avatar ) = > any
} ) : JQuery {
options = options || { } ;
2018-04-16 20:38:35 +02:00
2019-03-22 22:43:27 +01:00
let avatar_container = $ . spawn ( "div" ) ;
let avatar_image = $ . spawn ( "img" ) . attr ( "alt" , tr ( "Client avatar" ) ) ;
let cached_avatar : Avatar = this . _cached_avatars [ client_avatar_id ] ;
2019-08-21 10:00:01 +02:00
if ( avatar_id === "" ) {
avatar_container . append ( this . generate_default_image ( ) ) ;
} else if ( cached_avatar && cached_avatar . avatar_id == avatar_id ) {
2019-03-22 22:43:27 +01:00
avatar_image . attr ( "src" , cached_avatar . url ) ;
avatar_container . append ( avatar_image ) ;
2019-03-25 20:04:04 +01:00
if ( options . callback_image )
options . callback_image ( avatar_image ) ;
if ( options . callback_avatar )
options . callback_avatar ( cached_avatar ) ;
2018-04-16 20:38:35 +02:00
} else {
2019-03-22 22:43:27 +01:00
let loader_image = $ . spawn ( "img" ) ;
loader_image . attr ( "src" , "img/loading_image.svg" ) . css ( "width" , "75%" ) ;
avatar_container . append ( loader_image ) ;
( async ( ) = > {
let avatar : Avatar ;
try {
avatar = await this . resolved_cached ( client_avatar_id , avatar_id ) ;
} catch ( error ) {
2019-08-30 23:06:39 +02:00
log . error ( LogCategory . CLIENT , error ) ;
2018-11-25 13:45:45 +01:00
}
2018-04-16 20:38:35 +02:00
2019-03-22 22:43:27 +01:00
if ( ! avatar )
2019-08-21 10:00:01 +02:00
avatar = await this . load_avatar ( client_avatar_id , avatar_id ) ;
2019-03-22 22:43:27 +01:00
2019-03-25 20:04:04 +01:00
if ( ! avatar )
throw "failed to load avatar" ;
if ( options . callback_avatar )
options . callback_avatar ( avatar ) ;
2019-03-22 22:43:27 +01:00
avatar_image . attr ( "src" , avatar . url ) ;
avatar_image . css ( "opacity" , 0 ) ;
avatar_container . append ( avatar_image ) ;
2019-03-25 20:04:04 +01:00
loader_image . animate ( { opacity : 0 } , 50 , ( ) = > {
2019-08-21 10:00:01 +02:00
loader_image . remove ( ) ;
2019-03-25 20:04:04 +01:00
avatar_image . animate ( { opacity : 1 } , 150 , ( ) = > {
if ( options . callback_image )
options . callback_image ( avatar_image ) ;
} ) ;
2018-04-16 20:38:35 +02:00
} ) ;
2019-03-22 22:43:27 +01:00
} ) ( ) . catch ( reason = > {
2019-08-30 23:06:39 +02:00
log . error ( LogCategory . CLIENT , tr ( "Could not load avatar for id %s. Reason: %s" ) , client_avatar_id , reason ) ;
2018-04-16 20:38:35 +02:00
//TODO Broken image
2019-03-25 20:04:04 +01:00
loader_image . addClass ( "icon client-warning" ) . attr ( "tag" , tr ( "Could not load avatar " ) + client_avatar_id ) ;
2019-03-22 22:43:27 +01:00
} )
2018-02-27 17:20:49 +01:00
}
2019-03-22 22:43:27 +01:00
return avatar_container ;
2018-02-27 17:20:49 +01:00
}
2019-08-21 10:00:01 +02:00
unique_id_2_avatar_id ( unique_id : string ) {
function str2ab ( str ) {
let buf = new ArrayBuffer ( str . length ) ; // 2 bytes for each char
let bufView = new Uint8Array ( buf ) ;
for ( let i = 0 , strLen = str . length ; i < strLen ; i + + ) {
bufView [ i ] = str . charCodeAt ( i ) ;
}
return buf ;
}
try {
let raw = atob ( unique_id ) ;
let input = hex . encode ( str2ab ( raw ) ) ;
let result : string = "" ;
for ( let index = 0 ; index < input . length ; index ++ ) {
let c = input . charAt ( index ) ;
let offset : number = 0 ;
if ( c >= '0' && c <= '9' )
offset = c . charCodeAt ( 0 ) - '0' . charCodeAt ( 0 ) ;
else if ( c >= 'A' && c <= 'F' )
offset = c . charCodeAt ( 0 ) - 'A' . charCodeAt ( 0 ) + 0x0A ;
else if ( c >= 'a' && c <= 'f' )
offset = c . charCodeAt ( 0 ) - 'a' . charCodeAt ( 0 ) + 0x0A ;
result += String . fromCharCode ( 'a' . charCodeAt ( 0 ) + offset ) ;
}
return result ;
} catch ( e ) { //invalid base 64 (like music bot etc)
return undefined ;
}
}
private generate_default_image ( ) : JQuery {
return $ . spawn ( "img" ) . attr ( "src" , "img/style/avatar.png" ) . css ( { width : '100%' , height : '100%' } ) ;
}
generate_chat_tag ( client : { id? : number ; database_id? : number ; } , client_unique_id : string , callback_loaded ? : ( successfully : boolean , error? : any ) = > any ) : JQuery {
let client_handle ;
if ( typeof ( client . id ) == "number" )
client_handle = this . handle . handle . channelTree . findClient ( client . id ) ;
if ( ! client_handle && typeof ( client . id ) == "number" ) {
client_handle = this . handle . handle . channelTree . find_client_by_dbid ( client . database_id ) ;
}
if ( client_handle && client_handle . clientUid ( ) !== client_unique_id )
client_handle = undefined ;
const container = $ . spawn ( "div" ) . addClass ( "avatar" ) ;
if ( client_handle && ! client_handle . properties . client_flag_avatar )
return container . append ( this . generate_default_image ( ) ) ;
const avatar_id = client_handle ? client_handle . avatarId ( ) : this . unique_id_2_avatar_id ( client_unique_id ) ;
if ( avatar_id ) {
if ( this . _cached_avatars [ avatar_id ] ) { /* Test if we're may able to load the client avatar sync without a loading screen */
const cache : Avatar = this . _cached_avatars [ avatar_id ] ;
2019-08-30 23:06:39 +02:00
log . debug ( LogCategory . GENERAL , tr ( "Using cached avatar. ID: %o | Version: %o (Cached: %o)" ) , avatar_id , client_handle ? client_handle.properties.client_flag_avatar : undefined , cache . avatar_id ) ;
2019-08-21 10:00:01 +02:00
if ( ! client_handle || client_handle . properties . client_flag_avatar == cache . avatar_id ) {
const image = $ . spawn ( "img" ) . attr ( "src" , cache . url ) . css ( { width : '100%' , height : '100%' } ) ;
return container . append ( image ) ;
}
}
const image_loading = $ . spawn ( "img" ) . attr ( "src" , "img/loading_image.svg" ) . css ( { width : '100%' , height : '100%' } ) ;
/* lets actually load the avatar */
( async ( ) = > {
let avatar : Avatar ;
let loaded_image = this . generate_default_image ( ) ;
2019-08-30 23:06:39 +02:00
log . debug ( LogCategory . GENERAL , tr ( "Resolving avatar. ID: %o | Version: %o" ) , avatar_id , client_handle ? client_handle.properties.client_flag_avatar : undefined ) ;
2019-08-21 10:00:01 +02:00
try {
//TODO: Cache if avatar load failed and try again in some minutes/may just even consider using the default avatar 'till restart
try {
avatar = await this . resolved_cached ( avatar_id , client_handle ? client_handle.properties.client_flag_avatar : undefined ) ;
} catch ( error ) {
2019-08-30 23:06:39 +02:00
log . error ( LogCategory . GENERAL , tr ( "Failed to use cached avatar: %o" ) , error ) ;
2019-08-21 10:00:01 +02:00
}
if ( ! avatar )
avatar = await this . load_avatar ( avatar_id , client_handle ? client_handle.properties.client_flag_avatar : undefined ) ;
if ( ! avatar )
throw "no avatar present!" ;
loaded_image = $ . spawn ( "img" ) . attr ( "src" , avatar . url ) . css ( { width : '100%' , height : '100%' } ) ;
} catch ( error ) {
throw error ;
} finally {
container . children ( ) . remove ( ) ;
container . append ( loaded_image ) ;
}
} ) ( ) . then ( ( ) = > callback_loaded && callback_loaded ( true ) ) . catch ( error = > {
log . warn ( LogCategory . CLIENT , tr ( "Failed to load chat avatar for client %s. Error: %o" ) , client_unique_id , error ) ;
callback_loaded && callback_loaded ( false , error ) ;
} ) ;
image_loading . appendTo ( container ) ;
} else {
this . generate_default_image ( ) . appendTo ( container ) ;
}
return container ;
}
2018-02-27 17:20:49 +01:00
}