Skip to content

Commit

Permalink
#2289 use brotli for clipboard data
Browse files Browse the repository at this point in the history
git-svn-id: https://xpra.org/svn/Xpra/trunk@22626 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed May 6, 2019
1 parent 2c9d63a commit 4de6ea3
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 17 deletions.
2 changes: 1 addition & 1 deletion src/etc/xpra/conf.d/10_network.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ lock = auto
#compressors = all
#compressors = none
#compressors = zlib
compressors = lz4, lzo, zlib
compressors = lz4, lzo, zlib, brotli

# Default compression (0 to 9):
compression_level = 1
Expand Down
6 changes: 5 additions & 1 deletion src/xpra/client/client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,9 +412,13 @@ def compressed_wrapper(self, datatype, data, level=5):
zlib = "zlib" in self.server_compressors and compression.use_zlib
lz4 = "lz4" in self.server_compressors and compression.use_lz4
lzo = "lzo" in self.server_compressors and compression.use_lzo
brotli = False
#never use brotli as a generic compressor
#brotli = "brotli" in self.server_compressors and compression.use_brotli
if level>0 and len(data)>=256 and (zlib or lz4 or lzo):
cw = compression.compressed_wrapper(datatype, data, level=level,
zlib=zlib, lz4=lz4, lzo=lzo, can_inline=False)
zlib=zlib, lz4=lz4, lzo=lzo, brotli=brotli,
can_inline=False)
if len(cw)<len(data):
#the compressed version is smaller, use it:
return cw
Expand Down
7 changes: 6 additions & 1 deletion src/xpra/client/gtk_base/gtk_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1279,7 +1279,12 @@ def clipboard_send(*parts):
def register_clipboard_compress_cb(compressible):
#register the compressor which will fire in protocol.encode:
def compress_clipboard():
clipboardlog("compress_clipboard() compressing %s", compressible)
clipboardlog("compress_clipboard() compressing %s, server compressors=%s",
compressible, self.server_compressors)
from xpra.net import compression
if "brotli" in self.server_compressors and compression.use_brotli:
return compression.compressed_wrapper(compressible.datatype, compressible.data,
level=9, brotli=True, can_inline=False)
return self.compressed_wrapper(compressible.datatype, compressible.data)
compressible.compress = compress_clipboard
def clipboard_progress(local_requests, remote_requests):
Expand Down
74 changes: 60 additions & 14 deletions src/xpra/net/compression.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import struct

from xpra.os_util import PYTHON3
from xpra.net.header import LZ4_FLAG, ZLIB_FLAG, LZO_FLAG
from xpra.net.header import LZ4_FLAG, ZLIB_FLAG, LZO_FLAG, BROTLI_FLAG


def debug(msg, *args, **kwargs):
Expand Down Expand Up @@ -66,6 +66,30 @@ def lzo_compress(packet, level):
raise Exception("lzo is not supported!")


brotli_compress = None
brotli_decompress = None
brotli_version = None
try:
from brotli import (
compress as bcompress,
decompress as bdecompress,
__version__ as brotli_version,
)
has_brotli = True
def _brotli_compress(packet, level):
if len(packet)>1024*1024:
level = min(9, level)
else:
level = min(11, level)
if not isinstance(packet, bytes):
packet = bytes(packet, 'UTF-8')
return level | BROTLI_FLAG, bcompress(packet, quality=level)
brotli_compress = _brotli_compress
brotli_decompress = bdecompress
except ImportError:
has_brotli = False


try:
from zlib import compress as zlib_compress, decompress as zlib_decompress
from zlib import __version__ as zlib_version
Expand Down Expand Up @@ -105,18 +129,20 @@ def nocompress(packet, _level):
use_zlib = has_zlib
use_lzo = has_lzo
use_lz4 = has_lz4
use_brotli = has_brotli

#all the compressors we know about, in best compatibility order:
ALL_COMPRESSORS = ("zlib", "lz4", "lzo")
ALL_COMPRESSORS = ("zlib", "lz4", "lzo", "brotli")

#order for performance:
PERFORMANCE_ORDER = ("lz4", "lzo", "zlib")
PERFORMANCE_ORDER = ("lz4", "lzo", "zlib", "brotli")


_COMPRESSORS = {
"zlib" : zcompress,
"lz4" : lz4_compress,
"lzo" : lzo_compress,
"brotli": brotli_compress,
"none" : nocompress,
}

Expand All @@ -143,21 +169,28 @@ def get_compression_caps():
}
if has_zlib:
_zlib["version"] = zlib_version
_brotli = {
"" : use_brotli
}
if has_brotli:
_brotli["version"] = brotli_version
caps.update({
"lz4" : _lz4,
"lzo" : _lzo,
"zlib" : _zlib,
})
"lz4" : _lz4,
"lzo" : _lzo,
"zlib" : _zlib,
"brotli" : _brotli,
})
return caps

def get_enabled_compressors(order=ALL_COMPRESSORS):
enabled = [x for x,b in {
"lz4" : use_lz4,
"lzo" : use_lzo,
"zlib" : use_zlib,
}.items() if b]
enabled = tuple(x for x,b in {
"lz4" : use_lz4,
"lzo" : use_lzo,
"zlib" : use_zlib,
"brotli" : use_brotli,
}.items() if b)
#order them:
return [x for x in order if x in enabled]
return tuple(x for x in order if x in enabled)

def get_compressor(c):
assert c=="none" or c in ALL_COMPRESSORS
Expand Down Expand Up @@ -223,7 +256,7 @@ def compress(self):
raise Exception("compress() not defined on %s" % self)


def compressed_wrapper(datatype, data, level=5, zlib=False, lz4=False, lzo=False, can_inline=True):
def compressed_wrapper(datatype, data, level=5, zlib=False, lz4=False, lzo=False, brotli=False, can_inline=True):
size = len(data)
if size>MAX_SIZE:
sizemb = size//1024//1024
Expand All @@ -237,6 +270,10 @@ def compressed_wrapper(datatype, data, level=5, zlib=False, lz4=False, lzo=False
assert use_lzo, "cannot use lzo"
algo = "lzo"
cl, cdata = lzo_compress(data, level)
elif brotli:
assert use_brotli, "cannot use brotli"
algo = "brotli"
cl, cdata = brotli_compress(data, level)
else:
assert zlib and use_zlib, "cannot use zlib"
algo = "zlib"
Expand All @@ -253,6 +290,8 @@ def get_compression_type(level):
return "lz4"
if level & LZO_FLAG:
return "lzo"
if level & BROTLI_FLAG:
return "brotli"
return "zlib"


Expand All @@ -278,6 +317,12 @@ def decompress(data, level):
if not use_lzo:
raise InvalidCompressionException("lzo is not enabled")
return LZO_decompress(data)
if level & BROTLI_FLAG:
if not has_brotli:
raise InvalidCompressionException("brotli is not available")
if not use_brotli:
raise InvalidCompressionException("brotli is not enabled")
return brotli_decompress(data)
if not use_zlib:
raise InvalidCompressionException("zlib is not enabled")
if isinstance(data, memoryview):
Expand All @@ -288,6 +333,7 @@ def decompress(data, level):
"lz4" : LZ4_FLAG,
"zlib" : 0,
"lzo" : LZO_FLAG,
"brotli": BROTLI_FLAG,
}

def decompress_by_name(data, algo):
Expand Down
6 changes: 6 additions & 0 deletions src/xpra/net/header.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@

import struct

#Note: since encoding flags and compression flags are all mutually exclusive,
# (ie: only one encoder and at most one compressor can be used at a time)
# we could theoretically add many more values here,
# not necessarily limitting ourselves to the ones that land on a bit.

#packet encoding flags:
FLAGS_BENCODE = 0x0 #assume bencode if not other flag is set
FLAGS_RENCODE = 0x1
Expand All @@ -16,6 +21,7 @@
ZLIB_FLAG = 0x0 #assume zlib if no other compression flag is set
LZ4_FLAG = 0x10
LZO_FLAG = 0x20
BROTLI_FLAG = 0x40
FLAGS_NOHEADER = 0x10000 #never encoded, so we can use a value bigger than a byte


Expand Down

0 comments on commit 4de6ea3

Please sign in to comment.