dejsem.1.5/python/dejsem.pycharm/server.py
author hh
Wed, 27 Nov 2019 09:50:16 +0100
changeset 0 676905a3b03c
permissions -rw-r--r--
--
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
0
hh
parents:
diff changeset
     1
#!/usr/bin/python3
hh
parents:
diff changeset
     2
# coding=utf8
hh
parents:
diff changeset
     3
hh
parents:
diff changeset
     4
import os, sys, subprocess, random, string, traceback, urllib.parse
hh
parents:
diff changeset
     5
from os.path import join
hh
parents:
diff changeset
     6
from d import D
hh
parents:
diff changeset
     7
from parms import Parms
hh
parents:
diff changeset
     8
from node import Node
hh
parents:
diff changeset
     9
hh
parents:
diff changeset
    10
hh
parents:
diff changeset
    11
# operace v nekonečném cyklu serveru:
hh
parents:
diff changeset
    12
#	- pro každý kanál v homedir je jedna instance Server
hh
parents:
diff changeset
    13
#	- každá instance má k dispozici 10 portů pro LISTEN
hh
parents:
diff changeset
    14
#	- číslování portů:
hh
parents:
diff changeset
    15
#		- 17CCP
hh
parents:
diff changeset
    16
#		- CC = číslo kanálu
hh
parents:
diff changeset
    17
#		- P  = číslo portu v kanálu
hh
parents:
diff changeset
    18
#		- port 17xx0 je vyhrazen pro rychlé synchronní operace serveru - commands
hh
parents:
diff changeset
    19
#		- porty 17xx1-9 jsou pro long running tasks
hh
parents:
diff changeset
    20
hh
parents:
diff changeset
    21
"""
hh
parents:
diff changeset
    22
	server pro určitý uživatelský kanál
hh
parents:
diff changeset
    23
	instance vytváří a spouští main.py
hh
parents:
diff changeset
    24
"""
hh
parents:
diff changeset
    25
class Server():
hh
parents:
diff changeset
    26
	
hh
parents:
diff changeset
    27
	def __init__(self, d, chan):
hh
parents:
diff changeset
    28
		self._baseid = "{}.server[{:02d}]".format(d.debid, chan)
hh
parents:
diff changeset
    29
		self.d = D("{}".format(self._baseid))
hh
parents:
diff changeset
    30
hh
parents:
diff changeset
    31
		Parms.clientMode = False
hh
parents:
diff changeset
    32
		Parms.sslCert = join(Parms.sslPath, "srv.pem")
hh
parents:
diff changeset
    33
		self.d.log("ssl path: {}".format(Parms.sslPath), sev=3)
hh
parents:
diff changeset
    34
hh
parents:
diff changeset
    35
		self._chan = chan
hh
parents:
diff changeset
    36
		self._homedir = join(Parms.srv_homedir, "{:02d}".format(self._chan))
hh
parents:
diff changeset
    37
		os.chdir(self._homedir)
hh
parents:
diff changeset
    38
		self.d.log("working dir={}, ls: {}".format(os.getcwd(), os.listdir(), sev=4))
hh
parents:
diff changeset
    39
		os.umask(0o077)
hh
parents:
diff changeset
    40
		
hh
parents:
diff changeset
    41
		self._filedir = join(self._homedir, Parms.filedir)
hh
parents:
diff changeset
    42
hh
parents:
diff changeset
    43
		try:
hh
parents:
diff changeset
    44
			self._node = Node(self.d, host='', chan=self._chan, conn=False, tryPort=False)
hh
parents:
diff changeset
    45
		except Exception as e:
hh
parents:
diff changeset
    46
			self.d.abend("creating network node", e)
hh
parents:
diff changeset
    47
			sys.exit(1)
hh
parents:
diff changeset
    48
		self.server()
hh
parents:
diff changeset
    49
hh
parents:
diff changeset
    50
	def server(self):
hh
parents:
diff changeset
    51
		# v této metodě server poslouchá na základním portu v kanálu (port 0);
hh
parents:
diff changeset
    52
		# po připojení klienta alokuje socket a synchronně cyklicky volá metodu service() k provedení příkazu klienta;
hh
parents:
diff changeset
    53
		# cyklus volání service() trvá dokud service() nevrátí False;
hh
parents:
diff changeset
    54
		# smyslem cyklu před novým acceptem je dát možnost klientu provést rychlou dávku příkazů proveditelných okamžitě;
hh
parents:
diff changeset
    55
		if not os.path.exists(Parms.clipfile):
hh
parents:
diff changeset
    56
			os.system("touch " + Parms.clipfile)
hh
parents:
diff changeset
    57
		if not os.path.exists(Parms.filedir):
hh
parents:
diff changeset
    58
			os.mkdir(Parms.filedir)
hh
parents:
diff changeset
    59
		while True:
hh
parents:
diff changeset
    60
			try:
hh
parents:
diff changeset
    61
				if self._node.acc(acc_TO=None):
hh
parents:
diff changeset
    62
					while self.service(): pass
hh
parents:
diff changeset
    63
			except KeyboardInterrupt:
hh
parents:
diff changeset
    64
				self.d.log("accept: KeyboardInterrupt")
hh
parents:
diff changeset
    65
				break
hh
parents:
diff changeset
    66
		if self.d.ll(4): self.d.log("closing ssc...")
hh
parents:
diff changeset
    67
		self._node.close_ssc()
hh
parents:
diff changeset
    68
hh
parents:
diff changeset
    69
	def service(self):
hh
parents:
diff changeset
    70
		# metoda synchronně ("blocking") dostává ze socketu příkaz klienta, provede ho
hh
parents:
diff changeset
    71
		#   a vrací true, když chce nechat socket otevřený a false, když se může socket zavřít;
hh
parents:
diff changeset
    72
		# socket se nechává otevřený, když má smysl provádět danou operaci v dávce;
hh
parents:
diff changeset
    73
		#   je na klientu, aby dávka byla krátká a aby zavřel neprodleně
hh
parents:
diff changeset
    74
		cmd = ""
hh
parents:
diff changeset
    75
		try:
hh
parents:
diff changeset
    76
			if self.d.ll(2): self.d.log("get command...")
hh
parents:
diff changeset
    77
			random.getrandbits(16)
hh
parents:
diff changeset
    78
			cmd = self._node.getcmd()	 # čtení osmi-znakového příkazu
hh
parents:
diff changeset
    79
			if self.d.ll(5): self.d.log("cmd len={}".format(len(cmd)))
hh
parents:
diff changeset
    80
			if not cmd: return False
hh
parents:
diff changeset
    81
			if self.d.ll(3): self.d.log("cmd={}".format(cmd))
hh
parents:
diff changeset
    82
			# názvy operací pull/push odpovídají pohledu klienta;
hh
parents:
diff changeset
    83
			# operace jsou synchronní, blokují další příkazy v daném kanále a musí být v "lidském" měřítku krátké;
hh
parents:
diff changeset
    84
			# dlouhé operace se zahajují příkazem LONGTASK, po němž se alokuje paralelní nit k provedení dlouhé operace (typicky přenosu dat);
hh
parents:
diff changeset
    85
			if   cmd == "PULLCLIP":	# přenos posledního uloženého clipboardu na klienta
hh
parents:
diff changeset
    86
				return self.cmd_pullclip()
hh
parents:
diff changeset
    87
			elif cmd == "PULLHIST":	# přenos přehledu uložených clipboard entries na klienta
hh
parents:
diff changeset
    88
				return self.cmd_pullhist()
hh
parents:
diff changeset
    89
			elif cmd == "PULLLIST":	# přenos seznamu souborů v adresáři na klienta
hh
parents:
diff changeset
    90
				return self.cmd_pulllist()
hh
parents:
diff changeset
    91
			elif cmd == "PUSHCLIP":	# příjem nového clipboardu k uložení na serveru
hh
parents:
diff changeset
    92
				return self.cmd_pushclip()
hh
parents:
diff changeset
    93
			elif cmd == "GETPEER":	# předání uložené peer-adresy klientu
hh
parents:
diff changeset
    94
				return self.cmd_getpeer()
hh
parents:
diff changeset
    95
			elif cmd == "SETPEER":	# příjem peer-adresy k uložení na serveru
hh
parents:
diff changeset
    96
				return self.cmd_setpeer()
hh
parents:
diff changeset
    97
			elif cmd == "MOVE":		# přesun uloženého souboru/adresáře v rámci serveru
hh
parents:
diff changeset
    98
				return self.cmd_move()
hh
parents:
diff changeset
    99
			elif cmd == "EXPOSE":	# vystavení souboru/adresáře na http-serveru - vrací se URI path
hh
parents:
diff changeset
   100
				return self.cmd_expose()
hh
parents:
diff changeset
   101
			elif cmd == "HIDE":		# zakrytí souboru/adresáře pro http-server
hh
parents:
diff changeset
   102
				return self.cmd_hide()
hh
parents:
diff changeset
   103
			elif cmd == "DELETE":	# rekurzivní výmaz souboru/adresáře na serveru
hh
parents:
diff changeset
   104
				return self.cmd_delete()
hh
parents:
diff changeset
   105
			elif cmd == "CREATDIR":	# založení adresáře na serveru
hh
parents:
diff changeset
   106
				return self.cmd_createdir()
hh
parents:
diff changeset
   107
			elif cmd == "FREE":		# předání informace o volném prostoru na serveru
hh
parents:
diff changeset
   108
				return self.cmd_freespace()
hh
parents:
diff changeset
   109
			elif cmd == "RECKON":	# předání informace o velikost souboru/adresáře
hh
parents:
diff changeset
   110
				return self.cmd_reckon()
hh
parents:
diff changeset
   111
			elif cmd == "LONGTASK":	# zahájení operace v jiném procesu na jiném portu
hh
parents:
diff changeset
   112
				return self.cmd_longtask()
hh
parents:
diff changeset
   113
			elif cmd == "HACK":
hh
parents:
diff changeset
   114
				self.d.log("hack")
hh
parents:
diff changeset
   115
				return False
hh
parents:
diff changeset
   116
			else:
hh
parents:
diff changeset
   117
				if self.d.ll(4): self.d.log("unknown command")
hh
parents:
diff changeset
   118
				self._node.close_sc()
hh
parents:
diff changeset
   119
				return False
hh
parents:
diff changeset
   120
		except KeyboardInterrupt:
hh
parents:
diff changeset
   121
			raise
hh
parents:
diff changeset
   122
		except Exception as e:
hh
parents:
diff changeset
   123
			# if self.d.ll(4): self.d.log("service {} aborted: {}: {}".format(cmd, e.__class__.__name__, str(e)))
hh
parents:
diff changeset
   124
			if self.d.ll(4): self.d.log("service {} aborted: {}".format(cmd, e))
hh
parents:
diff changeset
   125
			traceback.print_tb(sys.exc_info()[2])
hh
parents:
diff changeset
   126
			self._node.close_sc()
hh
parents:
diff changeset
   127
			return False
hh
parents:
diff changeset
   128
hh
parents:
diff changeset
   129
	def cmd_longtask(self):
hh
parents:
diff changeset
   130
		"""
hh
parents:
diff changeset
   131
		najde volný port 1-9, pošle ho klientovi, forkne se do paralelního procesu v němž
hh
parents:
diff changeset
   132
			čeká na připojení klienta, čte příkaz k operaci a provádí operaci
hh
parents:
diff changeset
   133
		- operace se nedávkují, na konci procesu se socket a serversocket zavírají a port uvolňuje
hh
parents:
diff changeset
   134
		- ssc, port = self._node.bindtrynext(Parms.srvhost)
hh
parents:
diff changeset
   135
		"""
hh
parents:
diff changeset
   136
		try:
hh
parents:
diff changeset
   137
			longtaskNode = Node(self.d, host='', chan=self._chan, tryPort=True, conn=False)
hh
parents:
diff changeset
   138
		except Node.AllPortsBusy:
hh
parents:
diff changeset
   139
			self._node.sendport(0)
hh
parents:
diff changeset
   140
			return False
hh
parents:
diff changeset
   141
		self._node.sendport(longtaskNode.port)
hh
parents:
diff changeset
   142
		self._node.close_sc()
hh
parents:
diff changeset
   143
		if os.fork():
hh
parents:
diff changeset
   144
			return False
hh
parents:
diff changeset
   145
		# subprocess --------------
hh
parents:
diff changeset
   146
		self._node = longtaskNode
hh
parents:
diff changeset
   147
		self.d.debid = "{} long task[{:d}]".format(self._baseid, self._node.port)
hh
parents:
diff changeset
   148
		random.seed()   # je potřeba se odstřihnout od random-sekvence hlavního procesu
hh
parents:
diff changeset
   149
		try:
hh
parents:
diff changeset
   150
			if self._node.acc(acc_TO=Parms.long_run_accept_timeout):
hh
parents:
diff changeset
   151
				while self.longserv(): pass	 # cykluj, dokud je longserv positivní
hh
parents:
diff changeset
   152
		except Exception as e:
hh
parents:
diff changeset
   153
			self.d.abendMsg("long task accept", e=e)
hh
parents:
diff changeset
   154
		self._node.close_sc()
hh
parents:
diff changeset
   155
		self._node.close_ssc()
hh
parents:
diff changeset
   156
		os._exit(0)
hh
parents:
diff changeset
   157
hh
parents:
diff changeset
   158
	def longserv(self):
hh
parents:
diff changeset
   159
		cmd = self._node.getcmd()
hh
parents:
diff changeset
   160
		if not cmd: return False
hh
parents:
diff changeset
   161
		try:
hh
parents:
diff changeset
   162
			if self.d.ll(4): self.d.log("longtask cmd: " + cmd)
hh
parents:
diff changeset
   163
			# názvy operací pull/push odpovídají pohledu klienta
hh
parents:
diff changeset
   164
			if   cmd == "PUSHFILE":	 # rekurzivní příjem souboru/adresáře k uložení na serveru
hh
parents:
diff changeset
   165
				return self.cmd_pushfile()
hh
parents:
diff changeset
   166
			elif cmd == "PUSHFIEX":	 # rekurzivní příjem souboru/adresáře k vystavení na http serveru
hh
parents:
diff changeset
   167
				return self.cmd_pushFileExpose()
hh
parents:
diff changeset
   168
			elif cmd == "PUSHSTEX":	 # příjem streamu k vystavení na http serveru
hh
parents:
diff changeset
   169
				return self.cmd_pushStreamExpose()
hh
parents:
diff changeset
   170
			elif cmd == "PULLFILE":	 # rekurzivní odeslání obsahu souboru/adresáře
hh
parents:
diff changeset
   171
				return self.cmd_pullfile()
hh
parents:
diff changeset
   172
			elif cmd == "PULLCLIP":	 # využije se pro dávkovou synchronizaci historie clipboardu
hh
parents:
diff changeset
   173
				return self.cmd_pullclip()
hh
parents:
diff changeset
   174
			elif cmd == "PULLHIST":	 # využije se pro dávkovou synchronizaci historie clipboardu
hh
parents:
diff changeset
   175
				return self.cmd_pullhist()
hh
parents:
diff changeset
   176
			elif cmd == "ECHO":
hh
parents:
diff changeset
   177
				return self.ee(cmd)
hh
parents:
diff changeset
   178
			elif cmd == "ECHOECHO":
hh
parents:
diff changeset
   179
				return self.ee(cmd)
hh
parents:
diff changeset
   180
			elif cmd == "HACK":
hh
parents:
diff changeset
   181
				return self.rand(6)
hh
parents:
diff changeset
   182
			else:
hh
parents:
diff changeset
   183
				return False
hh
parents:
diff changeset
   184
		except Exception as e:
hh
parents:
diff changeset
   185
			self.d.abendMsg("long task action {}".format(cmd), e=e)
hh
parents:
diff changeset
   186
			return False
hh
parents:
diff changeset
   187
hh
parents:
diff changeset
   188
	def rand(self, n):
hh
parents:
diff changeset
   189
		for i in range(n):
hh
parents:
diff changeset
   190
			self.d.log("{:04x}".format(random.getrandbits(16)))
hh
parents:
diff changeset
   191
		return False
hh
parents:
diff changeset
   192
hh
parents:
diff changeset
   193
	def ee(self, cmd):
hh
parents:
diff changeset
   194
		self.d.log("ECHO.004: cmd={}".format(cmd))
hh
parents:
diff changeset
   195
		while cmd == "ECHO____" or cmd == "ECHOECHO":
hh
parents:
diff changeset
   196
			self.d.log("echoing...")
hh
parents:
diff changeset
   197
			self._node.putstr("ECHO")
hh
parents:
diff changeset
   198
			if cmd == "ECHOECHO": self.d.log("echo, got={}".format(self._node.getfn()))
hh
parents:
diff changeset
   199
			self.d.log("waiting for cmd")
hh
parents:
diff changeset
   200
			cmd = self._node.getcmd()
hh
parents:
diff changeset
   201
			self.d.log("cmd got={}".format(cmd))
hh
parents:
diff changeset
   202
		self.d.log("waiting for close")
hh
parents:
diff changeset
   203
		self._node.get(1)  # wait for other side close
hh
parents:
diff changeset
   204
hh
parents:
diff changeset
   205
	def cmd_pullfile(self):
hh
parents:
diff changeset
   206
		# operace download, v níž může klient dávkovat příkazy PULLFILE ke stažení objektů
hh
parents:
diff changeset
   207
		cmd = "PULLFILE"
hh
parents:
diff changeset
   208
		while cmd == "PULLFILE":
hh
parents:
diff changeset
   209
			req = self.formpath(self._node.getfn())
hh
parents:
diff changeset
   210
			if req:
hh
parents:
diff changeset
   211
				fp = os.path.normpath(join(Parms.filedir, req))
hh
parents:
diff changeset
   212
				if os.path.exists(fp):
hh
parents:
diff changeset
   213
					prefix = os.path.dirname(fp)
hh
parents:
diff changeset
   214
					if self.d.ll(3): self.d.log("pullfile request for '{}': starting...".format(fp))
hh
parents:
diff changeset
   215
					self.pullfilerecurse(fp, prefix)
hh
parents:
diff changeset
   216
				else:
hh
parents:
diff changeset
   217
					if self.d.ll(3): self.d.log("pullfile request for '{}': not found".format(fp))
hh
parents:
diff changeset
   218
			self._node.sendEOD()  # end of recursive subtree of files
hh
parents:
diff changeset
   219
			cmd = self._node.getcmd()	# another PULLFILE is expected here
hh
parents:
diff changeset
   220
		self._node.getcmd()  # wait for other side close
hh
parents:
diff changeset
   221
		return False	# konec dávky
hh
parents:
diff changeset
   222
hh
parents:
diff changeset
   223
	def pullfilerecurse(self, fp, prefix):
hh
parents:
diff changeset
   224
		if os.path.isdir(fp):
hh
parents:
diff changeset
   225
			for cwd, void, files in os.walk(fp, topdown=False):
hh
parents:
diff changeset
   226
				for entry in files:
hh
parents:
diff changeset
   227
					self.pullfilerecurse(join(cwd, entry), prefix)
hh
parents:
diff changeset
   228
				self._node.putfileinfo(cwd, os.path.relpath(cwd, start=prefix))
hh
parents:
diff changeset
   229
		else:
hh
parents:
diff changeset
   230
			self._node.putfileinfo(fp, os.path.relpath(fp, start=prefix))
hh
parents:
diff changeset
   231
			with open(fp, mode='rb') as f:
hh
parents:
diff changeset
   232
				g = self._node.genput()
hh
parents:
diff changeset
   233
				g.send(None)
hh
parents:
diff changeset
   234
				data = f.read(Parms.bufSize)
hh
parents:
diff changeset
   235
				while data:
hh
parents:
diff changeset
   236
					try:
hh
parents:
diff changeset
   237
						g.send(data)
hh
parents:
diff changeset
   238
						data = f.read(Parms.bufSize)
hh
parents:
diff changeset
   239
					except Exception:
hh
parents:
diff changeset
   240
						break
hh
parents:
diff changeset
   241
				g.close()
hh
parents:
diff changeset
   242
		if self.d.ll(3): self.d.log("PULLFILE: entry {} sent".format(fp))
hh
parents:
diff changeset
   243
hh
parents:
diff changeset
   244
	def cmd_pushfile(self):
hh
parents:
diff changeset
   245
		# předpokládá se, že subadresáře, do nichž se přijímá, jsou vždycky writable, vznikly uploadem
hh
parents:
diff changeset
   246
		if self.d.ll(4): self.d.log("push file start")
hh
parents:
diff changeset
   247
		cmd = "PUSHFILE"
hh
parents:
diff changeset
   248
		while cmd == "PUSHFILE":
hh
parents:
diff changeset
   249
			req = self._node.getfn()	# relativní cesta
hh
parents:
diff changeset
   250
			while len(req) > 0:			# rekurzivní načtení stromu - končí prázdným req
hh
parents:
diff changeset
   251
				req = self.formpath(req)
hh
parents:
diff changeset
   252
				uploadPath = join(Parms.filedir, req)
hh
parents:
diff changeset
   253
				size = self._node.getnum()
hh
parents:
diff changeset
   254
				timestamp = self._node.getnum()
hh
parents:
diff changeset
   255
				if self.d.ll(4): self.d.log("push: req={} to=[{}, dir={}]...".format(req, uploadPath, size == -1))
hh
parents:
diff changeset
   256
				if size < 0:
hh
parents:
diff changeset
   257
					self._node.receive_dir(uploadPath, size, timestamp)
hh
parents:
diff changeset
   258
				else:
hh
parents:
diff changeset
   259
					self._node.receive_file(uploadPath, size, timestamp)
hh
parents:
diff changeset
   260
				req = self._node.getfn()
hh
parents:
diff changeset
   261
			cmd = self._node.getcmd()		# PUSHFILE or BATCHEND is expected here
hh
parents:
diff changeset
   262
			if self.d.ll(4): self.d.log("cmd_pushfile(), iterace v dávce, cmd={}".format(cmd))
hh
parents:
diff changeset
   263
		self._node.putnum(0)  				# client awaits end of transfer confirmation
hh
parents:
diff changeset
   264
		if self.d.ll(4): self.d.log("push file finished")
hh
parents:
diff changeset
   265
		return False	# konec dávky
hh
parents:
diff changeset
   266
hh
parents:
diff changeset
   267
	def cmd_pushFileExpose(self):
hh
parents:
diff changeset
   268
		"""
hh
parents:
diff changeset
   269
		Upload and Expose to HTTP
hh
parents:
diff changeset
   270
		● klient posílá na pozadí objekt, který se vystaví na http-serveru
hh
parents:
diff changeset
   271
		● objekt se ukládá do zvláštního adresáře self._exposed pod randomizovaným jménem
hh
parents:
diff changeset
   272
		● klientovi se posílá segment "path" z výsledného URL, URL si zkomletuje klient
hh
parents:
diff changeset
   273
		● metoda vrací False, protože se expose nedávkuje
hh
parents:
diff changeset
   274
		:return: False
hh
parents:
diff changeset
   275
		"""
hh
parents:
diff changeset
   276
		if self.d.ll(4): self.d.log("push and expose file start")
hh
parents:
diff changeset
   277
		self.link_exposed()
hh
parents:
diff changeset
   278
		req = self._node.getfn()  		# relativní cesta
hh
parents:
diff changeset
   279
		if len(req) > 0:
hh
parents:
diff changeset
   280
			stem, void, rest = self.formpath(req).partition('/')
hh
parents:
diff changeset
   281
			exposedPath	= self.randomize_path(stem, self._exposed)
hh
parents:
diff changeset
   282
			while len(req) > 0:			# rekurzivní načtení stromu - končí prázdným req
hh
parents:
diff changeset
   283
				uploadPath = join(self._exposed, exposedPath)
hh
parents:
diff changeset
   284
				void, void, rest = self.formpath(req).partition('/')
hh
parents:
diff changeset
   285
				if rest: uploadPath = join(uploadPath, rest)
hh
parents:
diff changeset
   286
				size = self._node.getnum()
hh
parents:
diff changeset
   287
				timestamp = self._node.getnum()
hh
parents:
diff changeset
   288
				if self.d.ll(4): self.d.log("push to '{}, dir={}'...".format(uploadPath, size == -1))
hh
parents:
diff changeset
   289
				if size < 0:
hh
parents:
diff changeset
   290
					self._node.receive_dir(uploadPath, size, timestamp)
hh
parents:
diff changeset
   291
				else:
hh
parents:
diff changeset
   292
					self._node.receive_file(uploadPath, size, timestamp)
hh
parents:
diff changeset
   293
				req = self._node.getfn()  # relativní cesta
hh
parents:
diff changeset
   294
			self.d.log("{} uploaded".format(exposedPath))
hh
parents:
diff changeset
   295
			self.expose_link(exposedPath)
hh
parents:
diff changeset
   296
			if self.d.ll(4): self.d.log("push and expose file end")
hh
parents:
diff changeset
   297
		else:
hh
parents:
diff changeset
   298
			self.node.putnum(0)
hh
parents:
diff changeset
   299
		return False	# konec, expose se nedávkuje
hh
parents:
diff changeset
   300
hh
parents:
diff changeset
   301
	def cmd_pushStreamExpose(self):
hh
parents:
diff changeset
   302
		"""
hh
parents:
diff changeset
   303
		Upload and Expose to HTTP
hh
parents:
diff changeset
   304
		● klient posílá na pozadí stream, který se vystaví na http-serveru
hh
parents:
diff changeset
   305
		● stream se ukládá do zvláštního adresáře self._exposed pod randomizovaným jménem
hh
parents:
diff changeset
   306
		● klientovi se posílá segment "path" z výsledného URL, URL si zkomletuje klient
hh
parents:
diff changeset
   307
		● metoda vrací False, protože se expose nedávkuje
hh
parents:
diff changeset
   308
		:return: False
hh
parents:
diff changeset
   309
		"""
hh
parents:
diff changeset
   310
		if self.d.ll(4): self.d.log("push and expose stream start")
hh
parents:
diff changeset
   311
		expName, origName = None, Node
hh
parents:
diff changeset
   312
		mimeType = self._node.getstr()
hh
parents:
diff changeset
   313
		size = self._node.getnum()
hh
parents:
diff changeset
   314
		if size > 0:
hh
parents:
diff changeset
   315
			self.link_exposed()
hh
parents:
diff changeset
   316
			(type, suffix) = mimeType.split('/')
hh
parents:
diff changeset
   317
			if not type or type == '*': type = "content"
hh
parents:
diff changeset
   318
			expName = self.randomize_path(type, self._exposed)
hh
parents:
diff changeset
   319
			if suffix and suffix != '*': expName += '.' + suffix
hh
parents:
diff changeset
   320
			expPath = join(self._exposed, expName)
hh
parents:
diff changeset
   321
			if self.d.ll(4): self.d.log("push stream to '{}'...".format(expPath))
hh
parents:
diff changeset
   322
			self._node.receive_stream(expPath, size)
hh
parents:
diff changeset
   323
			# subprocess.run(('touch', expPath))
hh
parents:
diff changeset
   324
			self.expose_link(expName)
hh
parents:
diff changeset
   325
			if self.d.ll(4): self.d.log("push stream to '{}' finished".format(expPath))
hh
parents:
diff changeset
   326
		else:
hh
parents:
diff changeset
   327
			"""self.node.putnum(0)"""
hh
parents:
diff changeset
   328
		return False	# konec dávky
hh
parents:
diff changeset
   329
hh
parents:
diff changeset
   330
	def cmd_pulllist(self):
hh
parents:
diff changeset
   331
		req = self.formpath(self._node.getfn())
hh
parents:
diff changeset
   332
		if req:
hh
parents:
diff changeset
   333
			fp = os.path.normpath(join(Parms.filedir, req))
hh
parents:
diff changeset
   334
			if self.d.ll(4): self.d.log("dir=" + fp)
hh
parents:
diff changeset
   335
			if os.path.exists(fp) and os.access(fp, os.R_OK | os.X_OK):
hh
parents:
diff changeset
   336
				if os.path.isdir(fp):
hh
parents:
diff changeset
   337
					for entry in os.listdir(path=fp):
hh
parents:
diff changeset
   338
						self._node.putfileinfo(join(fp, entry), entry)
hh
parents:
diff changeset
   339
				else:
hh
parents:
diff changeset
   340
					self._node.putfileinfo(fp, req)
hh
parents:
diff changeset
   341
		self._node.putnum(0)
hh
parents:
diff changeset
   342
		self._node.close_sc()
hh
parents:
diff changeset
   343
		return False	# konec dávky
hh
parents:
diff changeset
   344
hh
parents:
diff changeset
   345
	def cmd_pullclip(self):
hh
parents:
diff changeset
   346
		fn = self._node.getfn()
hh
parents:
diff changeset
   347
		if fn == "":
hh
parents:
diff changeset
   348
			fp = Parms.clipfile
hh
parents:
diff changeset
   349
		else:
hh
parents:
diff changeset
   350
			fp = Parms.histdir + "/" + fn
hh
parents:
diff changeset
   351
		if self.d.ll(4):
hh
parents:
diff changeset
   352
			self.d.log("clip fp={}".format(fp))
hh
parents:
diff changeset
   353
		if os.path.exists(fp):
hh
parents:
diff changeset
   354
			self._node.putnum(os.path.getsize(fp))
hh
parents:
diff changeset
   355
			with open(fp, mode="rb") as f:
hh
parents:
diff changeset
   356
				g = self._node.genput()
hh
parents:
diff changeset
   357
				g.send(None)
hh
parents:
diff changeset
   358
				data = f.read(Parms.bufSize)
hh
parents:
diff changeset
   359
				while len(data) > 0:
hh
parents:
diff changeset
   360
					try:
hh
parents:
diff changeset
   361
						g.send(data)
hh
parents:
diff changeset
   362
						data = f.read(Parms.bufSize)
hh
parents:
diff changeset
   363
					except Exception:
hh
parents:
diff changeset
   364
						break
hh
parents:
diff changeset
   365
				g.close()
hh
parents:
diff changeset
   366
		return True		# případné dávkování
hh
parents:
diff changeset
   367
hh
parents:
diff changeset
   368
	def cmd_pushclip(self):
hh
parents:
diff changeset
   369
		with open(Parms.clipfile, mode='wb') as f:
hh
parents:
diff changeset
   370
			for data in self._node.genget(size = -1):
hh
parents:
diff changeset
   371
				f.write(data)
hh
parents:
diff changeset
   372
		self._node.close_sc()
hh
parents:
diff changeset
   373
		subprocess.call(("cp", "-a", Parms.clipfile, join(Parms.histdir, self.hist_fn())))
hh
parents:
diff changeset
   374
		if self.d.ll(4): self.d.log("pushclip: {} bytes stored".format(os.path.getsize(Parms.clipfile)))
hh
parents:
diff changeset
   375
		return False	# konec dávky
hh
parents:
diff changeset
   376
hh
parents:
diff changeset
   377
	def hist_fn(self):	# random string file name
hh
parents:
diff changeset
   378
		if not os.path.exists(Parms.histdir):
hh
parents:
diff changeset
   379
			os.mkdir(Parms.histdir)
hh
parents:
diff changeset
   380
		a = (string.digits + string.ascii_letters)
hh
parents:
diff changeset
   381
		fn = ""
hh
parents:
diff changeset
   382
		for i in list(range(5)):
hh
parents:
diff changeset
   383
			fn += a[random.randint(0, 61)]
hh
parents:
diff changeset
   384
		while os.path.exists(join(Parms.histdir, fn)):
hh
parents:
diff changeset
   385
			fn = ""
hh
parents:
diff changeset
   386
			for i in list(range(5)):
hh
parents:
diff changeset
   387
				fn += a[random.randint(0, 61)]
hh
parents:
diff changeset
   388
		return fn
hh
parents:
diff changeset
   389
hh
parents:
diff changeset
   390
	def cmd_pullhist(self):
hh
parents:
diff changeset
   391
		if os.path.exists(Parms.histdir) and os.path.isdir(Parms.histdir):
hh
parents:
diff changeset
   392
			for entry in os.listdir(Parms.histdir):
hh
parents:
diff changeset
   393
				p = join(Parms.histdir, entry)
hh
parents:
diff changeset
   394
				self._node.putstr(entry)
hh
parents:
diff changeset
   395
				sample = open(p, mode="rb").read(80)	# pošli vzorek max.80 z každého entry
hh
parents:
diff changeset
   396
				self._node.putnum(len(sample))
hh
parents:
diff changeset
   397
				self._node.put(sample)
hh
parents:
diff changeset
   398
				self._node.putnum(os.path.getsize(p))
hh
parents:
diff changeset
   399
				self._node.putnum(int(os.path.getmtime(p)))
hh
parents:
diff changeset
   400
		self._node.putnum(0)	# konec streamu
hh
parents:
diff changeset
   401
		return True
hh
parents:
diff changeset
   402
hh
parents:
diff changeset
   403
	def cmd_expose(self):
hh
parents:
diff changeset
   404
		"""
hh
parents:
diff changeset
   405
		Expose to HTTP
hh
parents:
diff changeset
   406
		● klient posílá jméno objektu na serveru, který se má vystavit na http-serveru
hh
parents:
diff changeset
   407
		● jméno je cesta relativní k self._filedir
hh
parents:
diff changeset
   408
		● objekt se symlinkuje ve zvláštním adresáři self._exposed randomizovaným jménem odvozeným ze jména objektu
hh
parents:
diff changeset
   409
		● klientovi se posílá segment "path" z výsledného URL, URL si zkompletuje klient
hh
parents:
diff changeset
   410
		● metoda vrací False, protože se expose nedávkuje
hh
parents:
diff changeset
   411
		:return: False
hh
parents:
diff changeset
   412
		"""
hh
parents:
diff changeset
   413
		fpath = join(self._filedir, self.formpath(self._node.getfn()))	# node.getfn() je cesta relativně k self._filedir
hh
parents:
diff changeset
   414
		self.link_exposed()
hh
parents:
diff changeset
   415
		expName = self.randomize_path(os.path.basename(fpath), self._exposed)
hh
parents:
diff changeset
   416
		expPath = join(self._exposed, expName)
hh
parents:
diff changeset
   417
		subprocess.run(('ln', '-sfnr', fpath, expPath), check=True)
hh
parents:
diff changeset
   418
		if self.d.ll(3): self.d.log("fn={}".format(expPath))
hh
parents:
diff changeset
   419
		self.expose_link(expName)
hh
parents:
diff changeset
   420
		return False		# expose nemůže být v dávce, protože se klientovi posílá zpátky URI path
hh
parents:
diff changeset
   421
hh
parents:
diff changeset
   422
	def cmd_hide(self):
hh
parents:
diff changeset
   423
		fpath = self.formpath(self._node.getfn())
hh
parents:
diff changeset
   424
		if self.d.ll(3): self.d.log("fn={}".format(fpath))
hh
parents:
diff changeset
   425
		channel = "{:02d}".format(self._chan)
hh
parents:
diff changeset
   426
		fpath = join(Parms.srv_homedir, channel, Parms.filedir, fpath)
hh
parents:
diff changeset
   427
		subprocess.run(('chmod', '-R', 'o-rwx', fpath))
hh
parents:
diff changeset
   428
		subprocess.run(('find', fpath, '-name', '.htaccess', '-exec', 'rm', '-f', '{}', '+'))
hh
parents:
diff changeset
   429
		return True		# případné dávkování
hh
parents:
diff changeset
   430
hh
parents:
diff changeset
   431
	def cmd_getpeer(self):	# atavismus z doby před použitím UDP broadcast
hh
parents:
diff changeset
   432
		host = ""
hh
parents:
diff changeset
   433
		port = 0
hh
parents:
diff changeset
   434
		if len(self.peer) == 2:
hh
parents:
diff changeset
   435
			host = "10.0.2.2" if self.peer[0] == "10.0.2.15" else str(self.peer[0])
hh
parents:
diff changeset
   436
			port = self.peer[1]
hh
parents:
diff changeset
   437
			self.peer = []
hh
parents:
diff changeset
   438
		self._node.putstr(host)
hh
parents:
diff changeset
   439
		self._node.putnum(port)
hh
parents:
diff changeset
   440
		self._node.close_sc()
hh
parents:
diff changeset
   441
		return False
hh
parents:
diff changeset
   442
hh
parents:
diff changeset
   443
	def cmd_setpeer(self):	# atavismus z doby před použitím UDP broadcast
hh
parents:
diff changeset
   444
		self.peer = (str(self._node.getfn()), int(self._node.getnum()))
hh
parents:
diff changeset
   445
		self._node.close_sc()
hh
parents:
diff changeset
   446
		return False
hh
parents:
diff changeset
   447
hh
parents:
diff changeset
   448
	def cmd_move(self):
hh
parents:
diff changeset
   449
		orig = self.formpath(self._node.getfn())
hh
parents:
diff changeset
   450
		target = self.formpath(self._node.getfn())
hh
parents:
diff changeset
   451
		if orig and target:
hh
parents:
diff changeset
   452
			if self.d.ll(4): self.d.log("performing mv -n {} {}".format(orig, target))
hh
parents:
diff changeset
   453
			subprocess.call(["mv", "-n", join(Parms.filedir, orig), join(Parms.filedir, target)])
hh
parents:
diff changeset
   454
		return True	 	# případné dávkování
hh
parents:
diff changeset
   455
hh
parents:
diff changeset
   456
	def cmd_delete(self):
hh
parents:
diff changeset
   457
		req = self.formpath(self._node.getfn())
hh
parents:
diff changeset
   458
		if req:
hh
parents:
diff changeset
   459
			if self.d.ll(4): self.d.log("performing rm -rf {}".format(req))
hh
parents:
diff changeset
   460
			subprocess.call(["rm", "-rf", join(Parms.filedir, req)])
hh
parents:
diff changeset
   461
		return True		# případné dávkování
hh
parents:
diff changeset
   462
hh
parents:
diff changeset
   463
	def cmd_createdir(self):
hh
parents:
diff changeset
   464
		req = self.formpath(self._node.getfn())
hh
parents:
diff changeset
   465
		if req:
hh
parents:
diff changeset
   466
			fp = join(Parms.filedir, req)
hh
parents:
diff changeset
   467
			if self.d.ll(4): self.d.log("performing mkdir -p {}".format(fp))
hh
parents:
diff changeset
   468
			subprocess.call(["mkdir", "-p", fp])
hh
parents:
diff changeset
   469
		return True	 		# případné dávkování
hh
parents:
diff changeset
   470
hh
parents:
diff changeset
   471
	def cmd_freespace(self):
hh
parents:
diff changeset
   472
		# df v Debianu 7.6 nemá parametr --output   :-(
hh
parents:
diff changeset
   473
		p = subprocess.Popen(["df", "--block-size=1", "."], stdout=subprocess.PIPE)
hh
parents:
diff changeset
   474
		p.wait()
hh
parents:
diff changeset
   475
		if p.returncode == 0:
hh
parents:
diff changeset
   476
			free = int(p.stdout.readlines()[1].decode().split()[3])
hh
parents:
diff changeset
   477
		else:
hh
parents:
diff changeset
   478
			free = -1
hh
parents:
diff changeset
   479
		self._node.putnum(free)
hh
parents:
diff changeset
   480
		self._node.close_sc()
hh
parents:
diff changeset
   481
		return True	 		# případné dávkování
hh
parents:
diff changeset
   482
hh
parents:
diff changeset
   483
	def cmd_reckon(self):
hh
parents:
diff changeset
   484
		req = self.formpath(self._node.getfn())
hh
parents:
diff changeset
   485
		self.sendobjectsize(join(Parms.filedir, req) if req else None)
hh
parents:
diff changeset
   486
		return True	 		# případné dávkování
hh
parents:
diff changeset
   487
hh
parents:
diff changeset
   488
	def sendobjectsize(self, fp):
hh
parents:
diff changeset
   489
		if not fp or not os.path.exists(fp):
hh
parents:
diff changeset
   490
			size, fnum, dnum = (0, 0, 0)
hh
parents:
diff changeset
   491
		elif os.path.isfile(fp):
hh
parents:
diff changeset
   492
			size, fnum, dnum = (os.path.getsize(fp), 1, 0)
hh
parents:
diff changeset
   493
		else:
hh
parents:
diff changeset
   494
			size, fnum, dnum = self.dirsize(fp)
hh
parents:
diff changeset
   495
		self._node.putnum(int(size))
hh
parents:
diff changeset
   496
		self._node.putnum(int(fnum))
hh
parents:
diff changeset
   497
		self._node.putnum(int(dnum))
hh
parents:
diff changeset
   498
hh
parents:
diff changeset
   499
	def link_exposed(self):
hh
parents:
diff changeset
   500
		"""
hh
parents:
diff changeset
   501
		● založení adresáře pro vystavené objekty self._exposed podle Parms.exposed
hh
parents:
diff changeset
   502
		● adresář je symlinkován z http-serveru číslem kanálu
hh
parents:
diff changeset
   503
		● symlink z http-serveru musí vytvořit instalace nebo super-user
hh
parents:
diff changeset
   504
		● na http-serveru se při instalaci aplikace zakládá adresář pro tuto aplikaci se symlinky na exposed dirs jednotlivých kanálů
hh
parents:
diff changeset
   505
			● <http_server>/<appl_name>/<kanál> --> <appl_home>/<kanál>/<files>/<exposed>
hh
parents:
diff changeset
   506
			● <http_server>/<appl_name> musí mít povoleny symlinky a povolen( instalovat .htaccess s Options Indexes
hh
parents:
diff changeset
   507
			● http-server musí mít x-access po cestě <http_server>/<appl_name>/<kanál> --> <exposed>
hh
parents:
diff changeset
   508
			● výšeuvedené atributy se nemůže zařídit aplikace, musí být nastaveny při instalaci nebo administrátorem
hh
parents:
diff changeset
   509
		"""
hh
parents:
diff changeset
   510
		self._exposed = join(self._filedir, Parms.exposed)
hh
parents:
diff changeset
   511
		if not os.path.exists(self._exposed):
hh
parents:
diff changeset
   512
			subprocess.run(('mkdir', '-pm771', self._exposed))
hh
parents:
diff changeset
   513
		# os.mkdir(self._exposed, mode=0o771)
hh
parents:
diff changeset
   514
		channel = "{:02d}".format(self._chan)
hh
parents:
diff changeset
   515
		self._wwwhome = join(Parms.srv_wwwhomedir, channel)
hh
parents:
diff changeset
   516
		if not os.path.exists(self._wwwhome):
hh
parents:
diff changeset
   517
			subprocess.run(('ln', '-sfnr', self._exposed, self._wwwhome), check=True)
hh
parents:
diff changeset
   518
hh
parents:
diff changeset
   519
	def expose_link(self, path):
hh
parents:
diff changeset
   520
		"""
hh
parents:
diff changeset
   521
		● fpath je cesta relativní k self._exposed
hh
parents:
diff changeset
   522
		● úkolem je zařídit read-access k filům, x-access k dirs, případně .htacess v kořenu stromu
hh
parents:
diff changeset
   523
		"""
hh
parents:
diff changeset
   524
		orig = join(self._exposed, path)
hh
parents:
diff changeset
   525
		self.d.log("orig={}".format(orig), sev=4)
hh
parents:
diff changeset
   526
		try:
hh
parents:
diff changeset
   527
			subprocess.run(('chmod', 'o+r', orig), check=True)
hh
parents:
diff changeset
   528
			if os.path.isdir(orig):
hh
parents:
diff changeset
   529
				self.expose_dir_tree(orig)
hh
parents:
diff changeset
   530
			elif path.find('/') > 0:
hh
parents:
diff changeset
   531
				self.expose_dir_path(path)
hh
parents:
diff changeset
   532
			channel = "{:02d}".format(self._chan)
hh
parents:
diff changeset
   533
			# uriPath = urllib.parse.quote(join(Parms.applName, channel, path))
hh
parents:
diff changeset
   534
			uriPath = join(Parms.applName, channel, path)
hh
parents:
diff changeset
   535
			self._node.putstr(uriPath)  # pošli URL-path klientovi
hh
parents:
diff changeset
   536
			self.d.log("uri path {} sent".format(uriPath), sev=3)
hh
parents:
diff changeset
   537
		except Exception as e:
hh
parents:
diff changeset
   538
			self.d.abend("exposing file to web server", e)
hh
parents:
diff changeset
   539
hh
parents:
diff changeset
   540
	def expose_dir_path(self, rel_file_path):
hh
parents:
diff changeset
   541
		"""
hh
parents:
diff changeset
   542
		po cestě k vystavenému souboru je potřeba nastavit x-access pro http-server
hh
parents:
diff changeset
   543
		:param rel_file_path: cesta relativní k self._exposed
hh
parents:
diff changeset
   544
		"""
hh
parents:
diff changeset
   545
		base = self._exposed
hh
parents:
diff changeset
   546
		while rel_file_path:
hh
parents:
diff changeset
   547
			subprocess.run(('chmod', 'o=x', base))
hh
parents:
diff changeset
   548
			(subdir, sep, rel_file_path) = rel_file_path.partition('/')
hh
parents:
diff changeset
   549
			base = join(base, subdir)
hh
parents:
diff changeset
   550
hh
parents:
diff changeset
   551
	def expose_dir_tree(self, rel_dir_path):
hh
parents:
diff changeset
   552
		"""
hh
parents:
diff changeset
   553
		do vystaveného stromu je třeba umístit .htaccess, po cestě nastavit x-access, ve stromu nastavit rx-access
hh
parents:
diff changeset
   554
		:param rel_dir_path: cesta relativní k self._exposed
hh
parents:
diff changeset
   555
		"""
hh
parents:
diff changeset
   556
		htaccess = join(rel_dir_path, ".htaccess")
hh
parents:
diff changeset
   557
		with open(htaccess, mode="w") as f:
hh
parents:
diff changeset
   558
			f.write("Options Indexes")
hh
parents:
diff changeset
   559
		subprocess.run(('chmod', 'o+r', htaccess))
hh
parents:
diff changeset
   560
		subprocess.run(('chmod', 'o+x', rel_dir_path))
hh
parents:
diff changeset
   561
		subprocess.run(('chmod', '-R', 'o+r', rel_dir_path))
hh
parents:
diff changeset
   562
		for (rel_dir_path, dirs, files) in os.walk(rel_dir_path):
hh
parents:
diff changeset
   563
			for dir in dirs:
hh
parents:
diff changeset
   564
				dpath = join(rel_dir_path, dir)
hh
parents:
diff changeset
   565
				subprocess.run(('chmod', 'o+x', dpath))
hh
parents:
diff changeset
   566
hh
parents:
diff changeset
   567
	def randomize_path(self, fn, dirname):
hh
parents:
diff changeset
   568
		name, void, suff = os.path.basename(fn).rpartition(".")
hh
parents:
diff changeset
   569
		expName = ""
hh
parents:
diff changeset
   570
		while os.path.exists(join(dirname, expName)) or expName == "":
hh
parents:
diff changeset
   571
			uniq = "{:04x}".format(random.getrandbits(16))
hh
parents:
diff changeset
   572
			if name:
hh
parents:
diff changeset
   573
				expName = name + "." + uniq + "." + suff
hh
parents:
diff changeset
   574
			else:
hh
parents:
diff changeset
   575
				expName = suff + "." + uniq
hh
parents:
diff changeset
   576
		return expName
hh
parents:
diff changeset
   577
hh
parents:
diff changeset
   578
	def dirsize(self, fp):
hh
parents:
diff changeset
   579
		if self.d.ll(4): self.d.log("performing du -sb '{}'".format(fp))
hh
parents:
diff changeset
   580
		try:
hh
parents:
diff changeset
   581
			size = subprocess.check_output(["du", "-sb", fp])[:-1].decode().split("\t")[0]
hh
parents:
diff changeset
   582
			fnum = len(subprocess.check_output(["find", fp, "-type", "f"]).split(b'\n'))-1
hh
parents:
diff changeset
   583
			dnum = len(subprocess.check_output(["find", fp, "-type", "d"]).split(b'\n'))-1
hh
parents:
diff changeset
   584
		except subprocess.CalledProcessError:
hh
parents:
diff changeset
   585
			(size, fnum, dnum) = (-1, 0, 0)
hh
parents:
diff changeset
   586
		if self.d.ll(5): self.d.log("size={}, type={}, fnum={}. type={}, dnum={}, type={}"
hh
parents:
diff changeset
   587
								.format(size, type(size), fnum, type(fnum), dnum, type(dnum)))
hh
parents:
diff changeset
   588
		return (size, fnum, dnum)
hh
parents:
diff changeset
   589
hh
parents:
diff changeset
   590
	def close_sc(self):
hh
parents:
diff changeset
   591
		self._node.close_sc()
hh
parents:
diff changeset
   592
hh
parents:
diff changeset
   593
	def formpath(self, path):
hh
parents:
diff changeset
   594
		"""
hh
parents:
diff changeset
   595
		- v žádném případě absolutní cesta
hh
parents:
diff changeset
   596
		- vrací None, když path jde up from current
hh
parents:
diff changeset
   597
		"""
hh
parents:
diff changeset
   598
		if path.startswith('/'): path = path[1:]
hh
parents:
diff changeset
   599
		p = os.path.normpath(path)
hh
parents:
diff changeset
   600
		return None if p == '..' or p.startswith('../') else p