-- draft default tip
authorhh
Wed, 27 Nov 2019 09:50:16 +0100
changeset 0 676905a3b03c
--
dejsem.1.5/android/dejsem.studio/app/build.gradle
dejsem.1.5/android/dejsem.studio/app/src/main/AndroidManifest.xml
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/Act.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/BroadcastReceiver.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/FlatButton.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/GetReady.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/K.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/Main.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/PassW.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/Prefs.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/SdUri.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/SendUriNG.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/TitleBar.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/clipboard/Clipboard.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/clipboard/HistFrag.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/clipboard/HistList.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/BarNotification.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/DialogFragmentDelete.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/DialogFragmentEntry.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/DialogFragmentMoveDest.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/EntitySize.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FM.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FMLocal.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FMServer.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FSLocal.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FSServer.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FileAttrs.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FileList.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FileListAdapter.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FragFM.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FragFMPeer.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FragFMServer.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/NetTaskRef.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelDirView.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelLocalDir.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelLocalMoveDest.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelPeerDir.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelServerDir.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelServerMoveDest.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/ProgressMonitor.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/TaskProgress.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/TransferAlert.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/TransferOverview.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/UploadAct.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/UploadFrag.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackA.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackAlertTest.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackContextMenu.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackDF.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackDfTst.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackDfUse.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackDgrams.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackDialogFragment.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackFS.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackFile.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackNotifDialogAct.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackNotifListAct.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackPanelMenu.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackUDP.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackViewDialog.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/lastmile/LastMileMeterFrag.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/lastmile/LastMileMeterRun.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/ExposeFile.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/ExposeStream.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/FileIO.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/GetPeerHostThread.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/GetPut.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/InChIO.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/LongAsyncTask.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetChIO.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetChOps.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetConn.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetConnPeer.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetConnSrv.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetCrypt.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetData.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetIO.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetInfoFrag.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetNode.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetScIO.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetSslChIO.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetSslChbIO.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetSslChnIO.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetSslScIO.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetThread.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetUDP.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/PullFromPeer.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/PullFromServer.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/PushToPeer.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/PushToServer.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/SendPeerHostThread.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/ServerCopy.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/SrvCmd.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/StreamIO.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/USBBus.java
dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/trash/TitleStat.java
dejsem.1.5/android/dejsem.studio/app/src/main/res/drawable/pending.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/basic_layout.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/dir_view_layout.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/file_entry.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/flat_button.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/h_border_thin.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/hack.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/hist_entry.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/hist_list.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/last_mile.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/main_panel.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/net_info.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/peer_file_manager.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/progress_entry.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/progress_list.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/server_file_manager.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/sort_header.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/title_bar.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/upload_panel.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/v_border_thin.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/word_dialog.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_common.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_dir_list.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_info.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_local_operations.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_peer_operations.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_prioritized.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_selection.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_server_operations.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/hack.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/main.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/move_dest.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/order.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/values-w820dp/dimens.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/values/attrs.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/values/channel.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/values/colors.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/values/dimens.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/values/ids.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/values/strings.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/values/styles.xml
dejsem.1.5/android/dejsem.studio/app/src/main/res/xml/prefs.xml
dejsem.1.5/android/dejsem.studio/build.gradle
dejsem.1.5/android/dejsem.studio/hhlibv10-debug/build.gradle
dejsem.1.5/android/dejsem.studio/local.properties
dejsem.1.5/android/dejsem.studio/settings.gradle
dejsem.1.5/python/dejsem.pycharm/client.py
dejsem.1.5/python/dejsem.pycharm/counter.py
dejsem.1.5/python/dejsem.pycharm/d.py
dejsem.1.5/python/dejsem.pycharm/main.py
dejsem.1.5/python/dejsem.pycharm/meter.py
dejsem.1.5/python/dejsem.pycharm/node.py
dejsem.1.5/python/dejsem.pycharm/parms.py
dejsem.1.5/python/dejsem.pycharm/server.py
dejsem.1.5/ssl/CA.ext
dejsem.1.5/ssl/ssl.sh
dejsem.1.5/unix/bin/d.l
dejsem.1.5/unix/bin/d.list
dejsem.1.5/unix/bin/d.plc
dejsem.1.5/unix/bin/d.plf
dejsem.1.5/unix/bin/d.plp
dejsem.1.5/unix/bin/d.puc
dejsem.1.5/unix/bin/d.puf
dejsem.1.5/unix/bin/d.pull
dejsem.1.5/unix/bin/d.pullfile
dejsem.1.5/unix/bin/d.pulllist
dejsem.1.5/unix/bin/d.pullpeer
dejsem.1.5/unix/bin/d.pup
dejsem.1.5/unix/bin/d.push
dejsem.1.5/unix/bin/d.pushfile
dejsem.1.5/unix/bin/d.pushpeer
dejsem.1.5/unix/bin/dejsem
dejsem.1.5/unix/bin/dejsemd
dejsem.1.5/unix/debian/client.DEBIAN/config
dejsem.1.5/unix/debian/client.DEBIAN/control
dejsem.1.5/unix/debian/client.DEBIAN/postinst
dejsem.1.5/unix/debian/client.DEBIAN/postrm
dejsem.1.5/unix/debian/client.DEBIAN/prerm
dejsem.1.5/unix/debian/client.DEBIAN/templates
dejsem.1.5/unix/debian/common.DEBIAN/control
dejsem.1.5/unix/debian/common.DEBIAN/postinst
dejsem.1.5/unix/debian/dummy.DEBIAN/control
dejsem.1.5/unix/debian/etc/default/dejsem
dejsem.1.5/unix/debian/etc/init.d/dejsemd
dejsem.1.5/unix/debian/makefile
dejsem.1.5/unix/debian/pack
dejsem.1.5/unix/debian/server.DEBIAN/control
dejsem.1.5/unix/debian/server.DEBIAN/postinst
dejsem.1.5/unix/debian/server.DEBIAN/postrm
dejsem.1.5/unix/debian/server.DEBIAN/prerm
dejsem.1.5/unix/test.sh
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/build.gradle	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,35 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 28
+
+    defaultConfig {
+        applicationId "hh.dejsem"
+        minSdkVersion 22
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        release {
+            postprocessing {
+                removeUnusedCode false
+                removeUnusedResources false
+                obfuscate false
+                optimizeCode false
+                proguardFile 'proguard-rules.pro'
+            }
+        }
+    }
+}
+
+dependencies {
+    implementation project(':hhlibv10-debug')
+    implementation fileTree(include: ['*.jar'], dir: 'libs')
+    implementation 'com.android.support:support-v4:28.0.0-beta01'
+    implementation 'com.android.support:appcompat-v7:28.0.0-beta01'
+    implementation 'com.android.support:preference-v7:28.0.0-beta01'
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/AndroidManifest.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest
+	package="hh.dejsem"
+	xmlns:android="http://schemas.android.com/apk/res/android"
+	>
+
+	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+	<uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+	<uses-permission android:name="android.permission.SEND_SMS"/>
+	<uses-permission android:name="android.permission.VIBRATE"/>
+	<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+	<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+
+	<!--<provider
+		android:name="dejsem.fileprovider"
+		android:authorities="hh.fileprovider"
+		android:grantUriPermissions="true"/>-->
+
+	<application
+		android:allowBackup="true"
+		android:icon="@mipmap/ic_launcher"
+		android:label="@string/app_name"
+		android:theme="@style/ThemeDej"
+		>
+
+		<activity android:name=".Main">
+			<intent-filter>
+				<action android:name="android.intent.action.MAIN"/>
+				<category android:name="android.intent.category.LAUNCHER"/>
+			</intent-filter>
+		</activity>
+
+		<activity android:name=".fm.UploadAct">
+
+            <intent-filter android:label="upload to server">
+                <action android:name="android.intent.action.SEND" />
+                <action android:name="android.intent.action.SENDTO" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="*/*" />
+            </intent-filter>
+
+	        <!--<intent-filter>
+		        <action android:name="android.intent.action.VIEW" />
+		        <category android:name="android.intent.category.DEFAULT" />
+		        <data android:mimeType="*/*" />
+	        </intent-filter>-->
+
+			<!--
+                <data android:scheme="file"/>
+				<data android:scheme="content"/>
+            -->
+
+			<!--
+                <data android:mimeType="text/*"/>
+				<data android:mimeType="image/*"/>
+				<data android:mimeType="audio/*"/>
+				<data android:mimeType="video/*"/>
+            -->
+
+			<!--
+            <intent-filter>
+				<action android:name="android.intent.action.SEND"/>
+				<category android:name="android.intent.category.DEFAULT"/>
+				<data android:mimeType="text/*"/>
+				<data android:mimeType="image/*"/>
+				<data android:mimeType="audio/*"/>
+				<data android:mimeType="video/*"/>
+			</intent-filter>
+            -->
+
+			<!--
+            <intent-filter>
+				<action android:name="android.intent.action.SEND"/>
+				<category android:name="android.intent.category.DEFAULT"/>
+				<data android:scheme="file"/>
+				<data android:scheme="content"/>
+			</intent-filter>
+            -->
+
+			<!--
+            <intent-filter>
+				<action android:name="android.intent.action.SEND"/>
+				<category android:name="android.intent.category.DEFAULT"/>
+			</intent-filter>
+            -->
+
+			<!--
+            <intent-filter>
+				<action android:name="android.intent.action.VIEW"/>
+				<category android:name="android.intent.category.DEFAULT"/>
+				<data android:mimeType="text/*"/>
+				<data android:mimeType="image/*"/>
+				<data android:mimeType="audio/*"/>
+				<data android:mimeType="video/*"/>
+				<data android:scheme="file"/>
+				<data android:scheme="content"/>
+			</intent-filter>
+            -->
+
+			<!--
+                <data android:mimeType="text/*"/>
+				<data android:mimeType="image/*"/>
+				<data android:mimeType="audio/*"/>
+				<data android:mimeType="video/*"/>
+            -->
+
+			<!--
+            <intent-filter>
+				<action android:name="android.intent.action.VIEW"/>
+				<category android:name="android.intent.category.DEFAULT"/>
+				<data android:scheme="file"/>
+				<data android:scheme="content"/>
+			</intent-filter>
+            -->
+
+			<!--
+            <intent-filter>
+				<action android:name="android.intent.action.VIEW"/>
+				<category android:name="android.intent.category.DEFAULT"/>
+			</intent-filter>
+            -->
+
+		</activity>
+
+        <!--<activity
+			android:name=".TransferAlert"
+			android:excludeFromRecents="true"
+			android:noHistory="true"
+			android:taskAffinity=""
+			android:theme="@style/ThemeDej.Transparent"
+			/>-->
+
+		<activity
+			android:name=".fm.TransferAlert"
+        	android:theme="@style/ThemeDej.Transparent"
+			/>
+			<!--android:theme="@style/ThemeDej.Transparent"-->
+
+        <activity
+			android:name=".fm.ProgressMonitor"
+			android:excludeFromRecents="true"
+			android:noHistory="true"
+			android:taskAffinity=""
+			android:theme="@style/ThemeDej.Transparent"
+			/>
+
+		<receiver android:name=".BroadcastReceiver">
+			<intent-filter android:label="receiver B">
+				<action android:name="android.intent.action.SEND" />
+				<action android:name="android.intent.action.SENDTO" />
+				<category android:name="android.intent.category.DEFAULT" />
+				<data android:mimeType="*/*" />
+			</intent-filter>
+		</receiver>
+
+		<activity android:name=".SdUri"/>
+
+		<activity android:name=".hack.HackUDP"/>
+
+		<activity android:name=".hack.HackNotifListAct"/>
+
+		<activity android:name=".hack.HackContextMenu"/>
+
+		<activity android:name=".hack.HackNotifDialogAct"/>
+
+		<activity android:name=".hack.HackA">
+
+			<!--
+            <intent-filter
+	            android:label="dej-expose"
+	            >
+                <action android:name="android.intent.action.SEND" />
+                <action android:name="android.intent.action.SENDTO" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="*/*" />
+            </intent-filter>
+
+	        <intent-filter
+	            android:label="dej-inspect"
+	            >
+		        <action android:name="android.intent.action.VIEW" />
+		        <category android:name="android.intent.category.DEFAULT" />
+		        <data android:mimeType="*/*" />
+	        </intent-filter>
+            -->
+
+		</activity>
+
+	</application>
+
+</manifest>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/Act.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,96 @@
+package hh.dejsem;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+
+import hh.lib.DA;
+
+public class Act extends DA implements Handler.Callback {
+
+	@Override
+	public void onCreate(Bundle b) {
+		super.onCreate(b);
+		new Prefs().refreshPrefs(this);
+	}
+
+	@Override
+	public boolean onCreateOptionsMenu(Menu menu) {     // volá se jen jednou
+		d.l(4, "onCreateOptionsMenu");
+		getMenuInflater().inflate(R.menu.main, menu);
+		return true;
+	}
+
+	@Override
+	public boolean onOptionsItemSelected(MenuItem item) {
+		if(d.ll(4)) d.l(String.format("options menu item selected=%s", item.getTitle()));
+		if(onItemSelected(item)) return true;
+		else return super.onOptionsItemSelected(item);
+	}
+
+	@Override
+	public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+		d.l(2, "onCreateContextMenu");
+		getMenuInflater().inflate(R.menu.main, menu);
+	}
+
+	@Override
+	public boolean onContextItemSelected(MenuItem item) {
+		if(d.ll(4)) d.l(String.format("context menu item selected=%s", item.getTitle()));
+		if(onItemSelected(item)) return true;
+		else return super.onContextItemSelected(item);
+	}
+
+	@Override
+	public boolean handleMessage(Message msg) {
+		if(d.ll(4)) d.l("handleMessage: what=" + msg.what);
+		return false;
+	}
+
+	public boolean onItemSelected(MenuItem item) {
+		switch(item.getItemId()) {
+			case R.id.menu_settings:
+				getSupportFragmentManager().beginTransaction().replace(android.R.id.content, new Prefs()).addToBackStack("Prefs").commit();
+				return true;
+			case R.id.menu_uninstall:
+				Uri packageURI = Uri.parse("package:" + getComponentName().getPackageName().toString());
+				Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI);
+				startActivity(uninstallIntent);
+				return true;
+			case R.id.menu_activity_hack:
+				hack();
+				return true;
+			case R.id.menu_del_passwd:
+				PassW.clearPW();
+				return true;
+			case R.id.menu_del_sd_card_perms:
+				SdUri.clearPermissions();
+				return true;
+			case R.id.menu_clean_caches:
+				cleanCache();
+				return true;
+			case R.id.menu_reset_prefs:
+				new Prefs().resetPrefs(this);
+				return true;
+		}
+		return false;
+	}
+
+	public void cleanCache() {
+		Prefs.sp.edit().remove(Prefs.HOST_CACHE_KEY).apply();
+		Prefs.sp.edit().remove(Prefs.HOME_CACHE_KEY).apply();
+	}
+
+	@Override
+	public void hack() {
+		if(d.ll(4)) d.l("hack");
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/BroadcastReceiver.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,17 @@
+package hh.dejsem;
+
+import android.content.Context;
+import android.content.Intent;
+
+import hh.lib.D;
+
+public class BroadcastReceiver extends android.content.BroadcastReceiver {
+
+	D d;
+
+	@Override
+	public void onReceive(Context context, Intent intent) {
+		d = new D(context, getClass().getSimpleName());
+		d.setContext(context);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/FlatButton.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,265 @@
+package hh.dejsem;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.support.v7.widget.AppCompatTextView;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import hh.lib.D;
+
+public class FlatButton extends RelativeLayout {
+	/**-------------------------------------------------------------------------
+	 * Základem je RelativeLayout jako kontejner
+	 * 	- do levého horního rohu se umístí TextView s textem, s paddigem,
+	 * 		s defaultním nebo explicitním marginem
+	 * 	- na okraje textu (včetně jeho paddingu) se chytne rámeček s kulatými rohy
+	 * 		a průhledným pozadím (FrameLayout s vepsaným obdélníkem).
+	 *
+	 * Poloměr rohů rámečku a padding textu se odvozuje z velikosti fontu.
+	 * Barva se přebírá z barvy textu.
+	 * Padding vytváří odstup rámečku od textu, margin vytváří odstup vně rámečku
+	 * od sousedních views (hlavně od sousedních tlačítek).
+	 *
+	 * Kontejner, text i rámeček dostávají všechny XML-atributy z nichž se natvrdo přebíjejí
+	 *      android:background      průhledným pozadím
+	 *      android:gravity         centrováním textu v jeho view
+	 *      android:padding...		padding textu se odvozuje z velikosti fontu
+	 *
+	 * Při vynechání android:text se doplňuje "button"
+	 *
+	 * Všechny android:layout_margins se vztahují k nadřídené viewGroup.
+	 * Margins pro umístění textu v kontejneru se berou z custom name space
+	 *      ns:layout_margin            přebíjí default margin stejný na všech stranách
+	 *      ns:layout_marginStart       přebíjí margin na jedné straně
+	 *      ns:layout_marginTop         přebíjí margin na jedné straně
+	 *      ns:layout_marginEnd         přebíjí margin na jedné straně
+	 *      ns:layout_marginBottom      přebíjí margin na jedné straně
+	 *
+	 * Programově lze nastavit
+	 *      text            setText(String)
+	 *      velikost písma  setTargetSize(float)
+	 *      barvu           setColor(int)
+	 *      marginStart v kontejneru
+	 *                      setMarginStart(int)
+	 *      marginEnd v kontejneru
+	 *                      setMarginEnd(int)
+	 * V <resources> se předpokládá definice styleable-grupy FlatButton
+	 *      <declare-styleable name="FlatButton">
+	 *          <attr name="android:text"/>
+	 *          <attr name="android:background"/>           <-- TODO
+	 *          <attr name="layout_margin" format="dimension"/>
+	 *          <attr name="layout_marginStart" format="dimension"/>
+	 *          <attr name="layout_marginTop" format="dimension"/>
+	 *          <attr name="layout_marginEnd" format="dimension"/>
+	 *          <attr name="layout_marginBottom" format="dimension"/>
+	 *      </declare-styleable>
+	 */
+	D d;
+	static final int WRAP_CONTENT = RelativeLayout.LayoutParams.WRAP_CONTENT;
+	TextView t = null;
+	Encircle e = null;
+	View v = null;                  /* overlay for click */
+	float rad;                      /* poloměr rohů */
+	final int defaultMargin = 8;	/* margin textu v kontejneru */
+	final int textId = android.R.id.text1;
+	String text = "button";
+	int textPadding = -1, textPaddingStart = -1, textPaddingTop = -1, textPaddingEnd = -1, textPaddingBottom = -1;
+
+	class Encircle extends View {
+		String tag;
+		final Paint stroke;
+		int width = 0, height = 0;
+
+		Encircle(Context c, AttributeSet as) {
+			super(c, as);
+//			setBackgroundResource(Color.TRANSPARENT);
+			setBackgroundResource(android.R.color.transparent);
+			stroke = new Paint();
+			stroke.setAntiAlias(true);
+			stroke.setStyle(Paint.Style.STROKE);
+			stroke.setColor(t.getCurrentTextColor());
+			stroke.setStrokeWidth(3f);
+			if(d.ll(5)) d.l(String.format("text created"));
+		}
+
+		public void onDraw(Canvas canvas) {
+			canvas.drawRoundRect(new RectF(0, 0, width, height), rad, rad, stroke);
+			if(d.ll(5)) d.l(String.format("onDraw: width=%d, height=%d, rad=%activeFragment", width, height, rad));
+		}
+
+		protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+			super.onLayout(changed, left, top, right, bottom);
+			if(d.ll(5)) d.l(String.format("onLayout: changed=%b, w=%d, h=%d", changed, getWidth(), getHeight()));
+			if(changed) {
+				width = getWidth();
+				height = getHeight();
+//				rad = height / 4;
+			}
+		}
+	}
+
+	class Text extends AppCompatTextView {
+		String tag;
+
+		Text(Context c, AttributeSet as) {
+			super(c, as);
+			setId(textId);
+			setText(text);
+//			Typeface tf = Typeface.create(defaultText, Typeface.NORMAL);
+//			setTypeface(tf);
+			setGravity(Gravity.CENTER);
+			FlatButton.this.setPadding(this);
+			setBackgroundResource(android.R.color.transparent);
+			if(d.ll(5)) d.l(String.format("text created"));
+		}
+
+		public void onDraw(Canvas canvas) {
+			super.onDraw(canvas);
+			if(d.ll(5)) d.l(String.format("onDraw: w=%d, h=%d", getWidth(), getHeight()));
+		}
+
+		protected void onMeasure(int w, int h) {
+			super.onMeasure(w, h);
+			if(d.ll(5)) d.l(String.format("onMeasure: w=%d, h=%d", w, h));
+		}
+	}
+
+	public FlatButton(Context c) {
+		this(c, null);
+	}
+
+	public FlatButton(Context c, AttributeSet as) {
+		super(c, as);
+		d = new D(c, getClass().getSimpleName() + (getId() == View.NO_ID ? ".xx" : ".id" + getId()));
+
+		int margin = defaultMargin;
+		int marginStart = defaultMargin;
+		int marginTop = defaultMargin;
+		int marginEnd = defaultMargin;
+		int marginBottom = defaultMargin;
+
+		if(as != null) {
+			TypedArray ta = c.getResources().obtainAttributes(as, R.styleable.FlatButton);
+			try {
+				if(d.ll(5)) d.l(String.format("android:text has value=%b, hh:text has value=%b",
+						ta.hasValue(R.styleable.FlatButton_android_text), ta.hasValue(R.styleable.FlatButton_text)));
+				if(ta.hasValue(R.styleable.FlatButton_android_text))
+					text = ta.getString(R.styleable.FlatButton_android_text);
+				if(ta.hasValue(R.styleable.FlatButton_layout_margin))
+					margin = ta.getDimensionPixelOffset(R.styleable.FlatButton_layout_margin, defaultMargin);
+				if(ta.hasValue(R.styleable.FlatButton_layout_marginStart))
+					marginStart = ta.getDimensionPixelOffset(R.styleable.FlatButton_layout_marginStart, margin);
+				else marginStart = margin;
+				if(ta.hasValue(R.styleable.FlatButton_layout_marginTop))
+					marginTop = ta.getDimensionPixelOffset(R.styleable.FlatButton_layout_marginTop, margin);
+				else marginTop = margin;
+				if(ta.hasValue(R.styleable.FlatButton_layout_marginEnd))
+					marginEnd = ta.getDimensionPixelOffset(R.styleable.FlatButton_layout_marginEnd, margin);
+				else marginEnd = margin;
+				if(ta.hasValue(R.styleable.FlatButton_layout_marginBottom))
+					marginBottom = ta.getDimensionPixelOffset(R.styleable.FlatButton_layout_marginBottom, margin);
+				else marginBottom = margin;
+				if(d.ll(5)) d.l(String.format("text_padding has value=%b", ta.hasValue(R.styleable.FlatButton_text_padding)));
+				textPadding = ta.getDimensionPixelOffset(R.styleable.FlatButton_text_padding, -1);
+				if(ta.hasValue(R.styleable.FlatButton_text_paddingStart))
+					textPaddingStart = ta.getDimensionPixelOffset(R.styleable.FlatButton_text_paddingStart, -1);
+				if(ta.hasValue(R.styleable.FlatButton_text_paddingTop))
+					textPaddingTop = ta.getDimensionPixelOffset(R.styleable.FlatButton_text_paddingTop, -1);
+				if(ta.hasValue(R.styleable.FlatButton_text_paddingEnd))
+					textPaddingEnd = ta.getDimensionPixelOffset(R.styleable.FlatButton_text_paddingEnd, -1);
+				if(ta.hasValue(R.styleable.FlatButton_text_paddingBottom))
+					textPaddingBottom = ta.getDimensionPixelOffset(R.styleable.FlatButton_text_paddingBottom, -1);
+			} finally { ta.recycle(); }
+		}
+
+		t = new Text(c, as);
+		LayoutParams textLayout = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+		textLayout.addRule(ALIGN_PARENT_TOP);
+		textLayout.addRule(ALIGN_PARENT_LEFT);
+		textLayout.setMargins(marginStart, marginTop, marginEnd, marginBottom);
+
+		e = new Encircle(c, as);
+		LayoutParams encircleLayout = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+		encircleLayout.addRule(ALIGN_RIGHT, textId);
+		encircleLayout.addRule(ALIGN_LEFT, textId);
+		encircleLayout.addRule(ALIGN_TOP, textId);
+		encircleLayout.addRule(ALIGN_BOTTOM, textId);
+
+		v = new View(c, as);
+
+		addView(e, encircleLayout);
+		addView(t, textLayout);
+		addView(v, encircleLayout);
+
+		if(d.ll(5)) d.l(String.format("flat button created"));
+	}
+
+	void setPadding(TextView t) {
+		rad = t.getTextSize() / 3;
+		final int defaultPad = textPadding == -1 ? Math.round(rad * 0.7f) : textPadding;
+		final int padStart = textPaddingStart == -1 ? defaultPad : textPaddingStart;
+		final int padTop = textPaddingTop == -1 ? defaultPad : textPaddingTop;
+		final int padEnd = textPaddingEnd == -1 ? defaultPad : textPaddingEnd;
+		final int padBottom = textPaddingBottom == -1 ? defaultPad : textPaddingBottom;
+		t.setPadding(padStart, padTop, padEnd, padBottom);
+		invalidate();
+		if(d.ll(5)) d.l(String.format("textPadding=%d, defaultPad=%d, padTop=%d", textPadding, defaultPad, padTop));
+	}
+
+	public FlatButton setText(String text) {
+		t.setText(text);
+		invalidate();
+		return this;
+	}
+
+	CharSequence getText() { return t.getText(); }
+
+	public FlatButton setSize(float textSize) {
+		t.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+		setPadding(t);
+		invalidate();
+		return this;
+	}
+
+	float getSize() { return t.getTextSize(); }
+
+	public FlatButton setColor(int color) {
+		if(d.ll(5)) d.l(String.format("setColor=%x", color));
+		t.setTextColor(color);
+		e.stroke.setColor(color);
+		invalidate();
+		return this;
+	}
+
+	int getColor() { return e.stroke.getColor(); }
+
+	public FlatButton setMarginStart(int margin) {
+		LayoutParams lp = (LayoutParams)t.getLayoutParams();
+		lp.setMarginStart(margin);
+		t.setLayoutParams(lp);
+		invalidate();
+		return this;
+	}
+
+	FlatButton setMarginEnd(int margin) {
+		LayoutParams lp = (LayoutParams)t.getLayoutParams();
+		lp.setMarginEnd(margin);
+		t.setLayoutParams(lp);
+		invalidate();
+		return this;
+	}
+
+	public void setOnClickListener(View.OnClickListener lstnr) {
+		v.setOnClickListener(lstnr);
+	}
+
+	public void setTag(String tag) { v.setTag(tag); }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/GetReady.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,79 @@
+package hh.dejsem;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Messenger;
+import android.support.v7.app.AppCompatActivity;
+
+import java.lang.ref.WeakReference;
+import java.util.HashSet;
+import java.util.Set;
+
+import hh.lib.AbendDialogFragment;
+import hh.lib.D;
+
+public class GetReady implements PassW.PasswListener, SdUri.SDUriListener, AbendDialogFragment.IgnoreAbend {
+
+	public interface ReadyListener { void onReady(boolean ok); }
+
+	static GetReady instance = null;
+
+	public static boolean isReady(D d) {
+		final boolean ready = PassW.isReady() && SdUri.isReady();
+		d.l(4, "isReady=" + ready);
+		return ready;
+	}
+
+	D d;
+	Context c;
+
+	Set subscribers = new HashSet();
+	private GetReady() {}
+
+	public static void getReady(D d, Object subscriber) {
+		if(instance == null) {
+			instance = new GetReady();
+			instance.d = d.klon(instance);
+			instance.d.l(4, "getReady");
+			if(isReady(instance.d)) ((ReadyListener)subscriber).onReady(true);
+			else {
+				instance.c = (Context)subscriber;
+				instance.subscribers.add(subscriber);
+				instance.go(subscriber);
+			}
+		}
+		else {
+			if(isReady(instance.d)) ((ReadyListener)subscriber).onReady(true);
+			else instance.subscribers.add(subscriber);
+		}
+	}
+
+	@Override
+	public void onPasswReady(int result) {
+		if(result < PassW.PW_BAD) {
+			SdUri.subscriber = this;
+			c.startActivity(new Intent(c, SdUri.class));
+		}
+		else result(false);
+		instance = null;
+	}
+
+	@Override
+	public void onSDPermissionReady() { result(true); }
+
+	@Override
+	public void ignoreAbend(boolean ignore) { if(!ignore) d.getAct().finish(); }
+
+	void go(Object subscriber) {
+		d.l(4, "go");
+		K.app = c.getApplicationContext();
+		D.applMsgr = new WeakReference<Messenger>(d.actMsgr);
+		new PassW(d, (AppCompatActivity)subscriber, this);
+	}
+
+	void result(boolean success) {
+		d.l(4, "notifying subscribers: success=" + success);
+		for(Object subscriber: subscribers) ((ReadyListener)subscriber).onReady(success);
+		subscribers.clear();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/K.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,276 @@
+package hh.dejsem;
+
+import android.content.Context;
+import android.os.Environment;
+import android.support.v4.provider.DocumentFile;
+
+import java.io.File;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.util.HashMap;
+
+import hh.dejsem.fm.TransferOverview;
+import hh.dejsem.net.NetConnSrv;
+import hh.dejsem.net.NetNode;
+import hh.dejsem.net.NetUDP;
+import hh.dejsem.net.SrvCmd;
+import hh.lib.D;
+
+/* TODO
+- problémy k 30.10.2018
+	- když se ztratí Notification, nedá se obnovit i když všechny procesy běží normálně dál
+	- po server upload se nerefreshuje file list
+- kontejner roznášející libovolné objekty
+- abend msg s bezpodmínečným abendem
+- StrictMode.ThreadPolicy
+- konsolidace K
+- optimalizace cachingu
+*/
+
+public class K {
+	/*----------------------------------------------------------
+	 * konstanty
+	 *---------------------------------------------------------*/
+	// velký timeout má smysl u pomalého výstupu, po němž následuje vstup - data se odlejí do bufferu a přejde se na čtení,
+	//      ale vstup z protější strany přijde, až když odejseme celý buffer pomalým výstupem na protější stranu}
+	public static final int acceptTO = 60 * 1000;  // peer server accept timeout in msecs
+	public static final int socketTO = 33 * 1000;  // socket IO timeout in msecs
+
+	public static final int BASE_PORT_NUM = 42000;
+	public static final int buffSize = 8192;       // data transfer buffer size
+
+	public static final String ACTION_KEY = "ACTION";
+	public static final String TXT_KEY = "TXT";
+//	public static final String SRC_KEY = "SRC";
+	public static final String PORT_KEY = "SERVER_PORT";
+	public static final String FILES_FROM_KEY = "FILES_FROM";
+	public static final String FILE_TO_KEY = "FILE_TO";
+	public static final String FN_KEY = "FILE_NAME";
+	public static final String STREAM_KEY = "INTENT_STREAM";
+	public static final String MIME_TYPE_KEY = "MIME_TYPE";
+	public static final String LFN_KEY = "LOCAL_FILE_NAME";
+	public static final String RFN_KEY = "REMOTE_FILE_NAME";
+
+	public static final String SD_CARD_MOUNT_POINT = "/storage/extSdCard/";
+	public static DocumentFile SD_CARD_ROOT_DOC_FILE = null;   // sd card root odsouhlasený na začátku a uložený v SharedPrefs
+
+	public static final String CACHEFNR_KEY = "remoteFileListCache";
+	public static final String CACHEFNL_KEY = "localFileListCache";
+	public static final String CACHED_LOC_CWD_KEY = "CACHED_LOC_CWD";
+	public static final String CACHED_REM_CWD_KEY = "CACHED_REM_CWD";
+
+	public static final String HISTORY_TAG = "HISTORY";
+	public static final String NET_INFO_TAG = "NET_INFO";
+	public static final String LAST_MILE_TAG = "LAST_MILE";
+	public static final String UPLOAD_TAG = "UPLOAD";
+
+	public static final String FM_SRV_TAG = "fmsrv";
+	public static final String FM_PEER_TAG = "fmpeer";
+	public static final String FM_DIALOG_SAVED_STATUS_KEY = "DIALOG_SAVED_STATUS";
+	public static final String FM_LOC_SAVED_POSITION_KEY = "FM_LOC_SAVED_POSITION";
+	public static final String FM_LOC_SAVED_SELECTION_KEY = "FM_LOC_SAVED_SELECTION";
+	public static final String FM_SRV_SAVED_POSITION_KEY = "FM_SRV_SAVED_POSITION";
+	public static final String FM_SRV_SAVED_SELECTION_KEY = "FM_SRV_SAVED_SELECTION";
+	public static final String FM_SAVED_CWD_KEY = "FM_SAVED_CWD";
+	public static final String FN_RESERVED_CHARS_REGEX = "[\"*|:<>\\?]";
+	public static final String FS_KIND_KEY = "FS_KIND";
+
+	/**
+	 * request codes
+	 */
+	public static final int MAIN_REFRESH = 16;
+	public static final int HISTORY = 17;
+	public static final int CHOOSE_SD_CARD_TREE = 18;
+	public static final int GETPW = 19;
+
+	public static final int LOC_FS = 72;
+	public static final int SRV_FS = 82;
+
+	public static final int MSG_COPY_TO_SRV = 224;
+	public static final int MSG_COPY_FROM_SRV = 225;
+	public static final int MSG_COPY_TO_PEER = 226;
+	public static final int MSG_COPY_FROM_PEER = 227;
+	public static final int MSG_EXPOSE_TO_SRV = 228;
+
+	public static final int MSG_TEST = 230;
+	public static final int MSG_REFRESH_LOC = 231;     // refresh local  FS dir list
+	public static final int MSG_REFRESH_SRV = 232;     // refresh remote FS dir list
+	public static final int MSG_WATCH_PROGRESS = 233;
+
+	public static final int MSG_END = 240;
+	public static final int MSG_PING = 241;
+	public static final int MSG_EXPOSE = 242;
+	public static final int MSG_SVC_ABEND = 243;
+	public static final int MSG_SVC_WARNING = 244;
+	public static final int MSG_SVC_INFO = 245;
+
+	public static final int TRANSFER_NOTIFICATION_ID = 300;
+	public static final int TRANSFER_PIN_NOTIFICATION = 301;
+	public static final int TRANSFER_DISPLAY_LIST = 302;
+	public static final int TRANSFER_WARNING = 303;
+	public static final int TRANSFER_ABEND = 304;
+	public static final int TRANSFER_DISPLAY_TEST = 305;
+	public static final int TRANSFER_SEND_URI = 306;
+
+	public static final int FM_ACTION_DELETE = 400;
+	public static final int FM_ACTION_RENAME = 401;
+	public static final int FM_ACTION_CREATE = 402;
+	public static final int FM_MOVE_DEST = 403;
+	public static final int FM_MOVE_CANCEL = 404;
+	public static final int FM_MENU_SERVER = 405;
+	public static final int FM_MENU_LOCAL = 406;
+	public static final int FM_MENU_PEER = 406;
+
+	public static final String UDP_ADVERT_KEY = "PEER_IP";
+	public static final String CRYPT_ALG = "DESede/ECB/NoPadding";
+
+	public static final int CRYPT_KEYLEN = 24;
+	public static int UDP_PORT = 4242;    // dejsem 4224, dejsem 4242
+
+	public static final String testText = "<---- 150chars/180bytes of test text ---------------- " +
+			"příliš žluťoučký kůň úpěl ďábelské kódy ------------ " +
+			"PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ KÓDY -->";
+	public static final String passId = "word";
+	private static final String ext_stg = Environment.getExternalStorageDirectory().getAbsolutePath();
+	public static final String home = ext_stg + "/_HH";
+	public static final String dnld = ext_stg + "/Download";
+
+	public static void cmdConnClose(D d) {
+		if (cmdConn != null && cmdConn.netIO.isConnected())
+			try { SrvCmd.CLOSE.exec(d, null); }
+			catch(Exception e) { d.abendMsg("closing cmd connection", e); }
+	}
+
+	public static boolean blocked() { return netInUse; }
+
+	public static String printShared() {
+		return String.format("D.netNode=%s, D.cmdConn=%s, D.transfProgress=%s", netNode, cmdConn, transfProgress);
+	}
+
+	public static class NotCreated extends Exception {
+		public NotCreated() { super("not created"); }
+		public NotCreated(String msg) { super(msg); }
+	}
+
+	public static class NotRenamed extends Exception { NotRenamed() { super("not renamed"); } }
+
+	public static class NotDeleted extends Exception { NotDeleted() { super("not deleted"); } }
+
+	public static class NothingSelected extends Exception { public NothingSelected() { super("nothing selected"); } }
+
+	public static class AllPortsBusy extends Exception { public AllPortsBusy() { super("all server ports busy"); } }
+
+	public static class NotCompleted extends Exception { public NotCompleted() { super("not completed"); } }
+
+	public static class AlreadyExists extends Exception { AlreadyExists(String msg) { super(msg); } }
+
+	public static class EntryNotFound extends Exception {}
+
+	public static class NoPeerListening extends Exception { public NoPeerListening() { super("no peer is listening"); } }
+
+	public static class IntentionalABEND extends Exception { public IntentionalABEND(String msg) { super(msg); } }
+
+	/**
+	 * handle Abnormal End information
+	 */
+	public static class Abend extends Exception {
+		/*----------------------------------------------------------
+		 * handle Abnormal End information
+		 *----------------------------------------------------------*/
+		Abend() { this(""); 	}
+
+		Abend(String m) {
+			this(m, null);
+		}
+
+		Abend(String m, Throwable t) {
+			super(m, t);
+			if (t != null) t.printStackTrace();
+		}
+	}
+
+	/**
+	 * handle Quiet Abnormal End information
+	 */
+	public static class End extends Exception {
+		public End() {
+			this(null, null);
+		}
+
+		public End(String m) {
+			this(m, null);
+		}
+
+		public End(Throwable t) { this(null, t); }
+
+		public End(String m, Throwable t) {
+			super(m, t);
+			if(t != null) t.printStackTrace();
+		}
+	}
+
+	public enum HistOrder {TEXT, SIZE, DATE}
+
+	public enum DirListOrder {
+		NAME_ASC(0), NAME_DESC(1),
+		SIZE_ASC(2), SIZE_DESC(4),
+		DATE_ASC(6), DATE_DESC(7);
+
+		DirListOrder(int value) {
+			this.value = value;
+		}
+
+		private final int value;
+
+		public int value() {
+			return value;
+		}
+	}
+
+	public enum LongTaskAction {
+		BATCHEND("BATCHEND"), PUSHFIEX("PUSHFIEX"), PUSHSTEX("PUSHSTEX"),
+		PULLFILE("PULLFILE"), PUSHFILE("PUSHFILE");
+
+		LongTaskAction(String action) {
+			this.action = action;
+		}
+
+		public final String action;
+
+		public String act() {
+			return action;
+		}
+	}
+
+	/**----------------------------------------------------------
+	 * shared values
+	 * ----------------------------------------------------------*/
+	public static final Object synchron = new Object();
+	public static Context app = null;
+	public static TitleBar title;
+	public static HashMap<String, Object> base = new HashMap<String, Object>();
+	// cache file se využívá pro caching remote filelist (fn + attributes)
+	// io-file proto, že se pro network i file využívá tentýž interface
+	public static File cacheFileR = null;
+	public static String word = "";
+	// public static DialogInterface.OnClickListener dialogLstnr = null;
+	public static TransferOverview transfProgress = null;
+//	public static HistList histlist = null;     // zkouším nepoužívat
+	public static DatagramPacket UDPpacket = null;
+	public static DatagramSocket UDPsocket = null;
+	public static NetUDP netUDp = null;
+	public static NetConnSrv cmdConn = null;
+	public static boolean netInUse = false;	// network operation in progress	<== not used currently
+	public static NetNode netNode = null;
+	public static SdUri sdUri = null;
+
+	private static K k = null;
+	private K() {}
+
+	public static K getInstance() {
+		if(k == null) k = new K();
+		return k;
+	}
+
+	public static void prepTransferOverview(D d) { if (K.transfProgress == null) K.transfProgress = new TransferOverview(d); }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/Main.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,249 @@
+package hh.dejsem;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.Fragment;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import java.io.File;
+
+import hh.dejsem.clipboard.Clipboard;
+import hh.dejsem.clipboard.HistFrag;
+import hh.dejsem.fm.FragFMPeer;
+import hh.dejsem.fm.FragFMServer;
+import hh.dejsem.hack.HackA;
+import hh.dejsem.net.NetInfoFrag;
+import hh.lib.D;
+
+/**
+ * ● main Activity, GUI rozcestník všech funkcí:
+ * <ol>
+ *     <li>sdílení clipboardu skrze server včetně udržování historie</li>
+ *     <li>sdílení datových souborů se serverem</li>
+ *     <li>peer-to-peer přenos datových souborů v LANu</li>
+ *     <li>vystavení datových souborů na serveru</li>
+ *     <li>informace o síťovém připojení včetně průchodnosti internetové last mile</li>
+ * </ol>
+ * ● síťové přenosy jsou šifrovány skrze SSL s asymetrickými klíči
+ * ● vyhrazeno je 10 oddělených přenosových kanálů s různými klíči, přičemž kanál 0 se nešifruje
+ * ● pro každý kanál je vyhrazeno 10 TCP-portů
+ * ● TCP-port 0 je vyhrazen pro zadávání příkazů serveru
+ * ● TCP-porty 1-9 slouží pro paralelní asynchronní datové přenosy
+ * ● porty se alokují relativně k portu 17000 na portech 17000-17099
+ * ● struktura čísla portu je 170XY, X = číslo šifrovaného kanálu, Y = číslo paralelního přenosu v kanálu
+ */
+
+public class Main
+		extends Act
+		implements
+			Handler.Callback,
+			GetReady.ReadyListener,
+			HistFrag.Listener {
+
+	static final String TITLETEXT = "main";
+
+	/**
+	 * helper pro log eye-catcher pro debug-verzi aplikace; volá se z top-level-aktivit.
+	 *
+	 * @param d
+	 * @param act caller activity
+	 */
+	static public void eyeCatcher(D d, Context act) {
+		if(act.getClass().getPackage().getName().startsWith("hh.dej" + "D")) {   // +++
+			K.UDP_PORT = 4224;
+			d.l(String.format("========= %s, %s, logLevel=%d, hashCode=%x =========",
+					act.getResources().getString(R.string.app_name),
+					act.getClass().getSimpleName(),
+					d.getLogLevel(),
+					act.hashCode()
+			));
+		}
+	}
+
+	TextView clipboardContent;
+	ScrollView clipboardScroll;
+	View.OnLongClickListener clickPush, clickPull;
+	View.OnClickListener clickHist, clickMenu, clickSrv, clickPeer, clickNet;
+	View bPush, bPull, bHist, bMenu, bFiles, bPeer, bNet;
+	Clipboard clipboard;
+
+	@Override
+	public void onCreate(Bundle b) {
+		super.onCreate(b);
+		eyeCatcher(d, this);
+
+		setContentView(R.layout.basic_layout);
+		TitleBar.assign(this, R.id.title_bar, TITLETEXT);
+		if(GetReady.isReady(d)) setup();
+		else {
+			GetReady.getReady(d, this);
+			TextView tv = new TextView(this);
+			tv.setText("waiting for password & SD card permissions");
+			((ViewGroup) findViewById(R.id.panel)).addView(tv);
+		}
+	}
+
+	@Override
+	public void onStart() {
+		super.onStart();
+		if(K.title != null) K.title.reassign(this, R.id.title_bar);
+	}
+
+	@Override
+	public void onResume() {
+		super.onResume();
+		refreshTv();
+	}
+
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+//		K.histlist = null;
+		K.cmdConnClose(d);
+		final Fragment df = d.getFmgr().findFragmentByTag("word");
+		if(df != null) ((DialogFragment)df).dismiss();
+	}
+
+	@Override
+	public void onBackPressed() {
+		if(d.ll(4)) d.l(String.format("onBackPressed, BackStackSize=%d", getFragmentManager().getBackStackEntryCount()));
+		refreshTv();
+		super.onBackPressed();
+	}
+
+	@Override
+	public void onReady(boolean success) {
+		if(success) setup();
+		else finish();
+	}
+
+	@Override
+	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+		if(d.ll(4)) d.l(String.format("onActivityResult: requestCode=%d, resultCode=%d", requestCode, resultCode));
+
+		if(requestCode == K.GETPW) {
+			if(K.word == null || K.word.equals("") || K.word.equals("BAD") || resultCode != RESULT_OK) finish();
+			else if(resultCode == RESULT_OK) K.sdUri.getSdUri();
+		}
+		else if(resultCode == RESULT_OK) {
+			if(requestCode == K.HISTORY) clipboard.pull(data.getStringExtra("ENTRYNAME"));
+			else if(requestCode == K.CHOOSE_SD_CARD_TREE) K.sdUri.saveUriPermissions(data);
+		}
+	}
+
+	@Override
+	public void getHistEntry(String entry) {
+		clipboard.pull(entry);
+		refreshTv();
+	}
+
+	void setup() {
+		if(K.cacheFileR == null) K.cacheFileR = new File(getCacheDir(), K.CACHEFNR_KEY);
+
+		((ViewGroup)findViewById(R.id.panel)).addView(getLayoutInflater().inflate(R.layout.main_panel, null));
+
+		bPush = findViewById(R.id.bPush);
+		bPull = findViewById(R.id.bPull);
+		bHist = findViewById(R.id.bHist);
+		bMenu = findViewById(R.id.bMenu);
+		bFiles = findViewById(R.id.bFiles);
+		bPeer = findViewById(R.id.bPeer);
+		bNet = findViewById(R.id.bNet);
+
+		clickPush = new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { return push(); } };
+		clickPull = new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { return pull(); } };
+		clickHist = new View.OnClickListener() { @Override public void onClick(View v) { history(); } };
+		clickMenu = new View.OnClickListener() { @Override public void onClick(View v) { handleContextMenu(); } };
+		clickSrv = new View.OnClickListener() { @Override public void onClick(View v) { server(); } };
+		clickPeer = new View.OnClickListener() { @Override public void onClick(View v) { peer(); } };
+		clickNet = new View.OnClickListener() { @Override public void onClick(View v) { netinfo(); } };
+
+		clipboardContent = (TextView)findViewById(R.id.cbText);
+		clipboardScroll = (ScrollView)findViewById(R.id.cbScroll);
+		registerForContextMenu(bMenu);
+		bPush.setOnLongClickListener(clickPush);
+		bPull.setOnLongClickListener(clickPull);
+		bHist.setOnClickListener(clickHist);
+		bMenu.setOnClickListener(clickMenu);
+		bFiles.setOnClickListener(clickSrv);
+		bPeer.setOnClickListener(clickPeer);
+		bNet.setOnClickListener(clickNet);
+		clipboard = new Clipboard(this, this, d);
+
+		refreshTv();
+	}
+
+	void handleContextMenu() {
+		d.l(4, "openContextMenu");
+		openContextMenu(bMenu);
+	}
+
+	void refreshTv() {
+		if(clipboard != null) {
+			clipboardContent.setText(clipboard.getCB());
+			clipboardContent.invalidate();
+		}
+		if(K.title != null) K.title.setText(TITLETEXT).update();
+	}
+
+	boolean push() {    // push clipboard to server
+		if(d.ll(4)) d.l("push to shared clipboard CLICKED");
+		return clipboard.push();
+	}
+
+	boolean pull() {    // pull last clipboard entry from server
+		if(d.ll(4)) d.l("pull from shared clipboard CLICKED");
+		if(clipboard.pull()) {
+			refreshTv();
+			return true;
+		}
+		else return false;
+	}
+
+	void history() {    // clipboard history
+		if(d.ll(4)) d.l("history CLICKED");
+		HistFrag history = (HistFrag)getSupportFragmentManager().findFragmentByTag(K.HISTORY_TAG);
+		if(history == null) history = HistFrag.instantiate(d/*, this*/);
+//		HistFrag history = HistFrag.instantiate(d/*, this*/);
+		attachFragment(history, R.id.panel, K.HISTORY_TAG);
+	}
+
+	void netinfo() {
+		if(d.ll(4)) d.l("netinfo CLICKED");
+		NetInfoFrag netInfo = (NetInfoFrag)getSupportFragmentManager().findFragmentByTag(K.NET_INFO_TAG);
+		if (netInfo == null) netInfo = NetInfoFrag.instantiate(d);
+		attachFragment(netInfo, R.id.panel, K.NET_INFO_TAG);
+	}
+
+	void server() {
+		FragFMServer fmSrv = (FragFMServer)getSupportFragmentManager().findFragmentByTag(K.FM_SRV_TAG);
+		if (fmSrv == null) fmSrv = FragFMServer.instantiate(d);
+		attachFragment(fmSrv, R.id.panel, K.FM_SRV_TAG);
+	}
+
+	void peer() {
+		FragFMPeer fmPeer = (FragFMPeer)getSupportFragmentManager().findFragmentByTag(K.FM_PEER_TAG);
+		if (fmPeer == null) fmPeer = FragFMPeer.instantiate(d);
+		attachFragment(fmPeer, R.id.panel, K.FM_PEER_TAG);
+	}
+
+	void attachFragment(Fragment fragment, int container, String tag) {
+		d.l("+++ attachFragment, fragment=" + fragment);
+		getSupportFragmentManager()
+				.beginTransaction()
+				.replace(container, fragment, tag)
+				.addToBackStack(null)
+				.commitAllowingStateLoss();
+	}
+
+	@Override
+	public void hack() {
+		startActivity(new Intent(this, HackA.class));
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/PassW.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,185 @@
+package hh.dejsem;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v7.app.AppCompatActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import java.io.InputStream;
+import java.security.KeyStore;
+
+import hh.lib.AbendDialogFragment;
+import hh.lib.D;
+
+/**
+ * Aktivita zodpovědná za dialog zadání úvodního hesla, jímž je kryt load
+ * certifikátů z key store SSL-knihovny Bouncy Castle
+ * <p>
+ *     ● heslo se ukládá do shared prefs, takž se zadává jen po jejich výmazu
+ */
+public class PassW implements AbendDialogFragment.IgnoreAbend {
+
+	public interface PasswListener { void onPasswReady(int result); }
+
+	final static String PW_KEY = "PW_KEY";
+
+	final static int NUM_OF_TRIES = 3;
+
+	final static int PW_OK = 0;
+	final static int PW_IGN = 1;    // passwd ignored by user
+	final static int PW_BAD = 2;
+
+	static PassW instance = null;
+
+	public static boolean isReady() { return K.word.length() > 0; }
+
+	public static void clearPW() {
+		Prefs.sp.edit().remove(PW_KEY).apply();
+		K.word = "";
+		K.netNode = null;
+	}
+
+	D d;
+	PasswListener subscriber;
+	Context c;
+	FragmentManager fm;
+	GetPwDialog dialog = null;
+	int totry = NUM_OF_TRIES;
+
+	@Override
+	public void ignoreAbend(boolean ignore) {
+		if(ignore) subscriber.onPasswReady(PW_IGN); }
+
+	PassW(D d, AppCompatActivity a, PasswListener subscriber) {
+		this.d = d.klon(this);
+		d.l(4, "readyPW");
+		fm = a.getSupportFragmentManager();
+		if(fm.findFragmentByTag(GetPwDialog.TAG) == null) {
+			this.subscriber = subscriber;
+			instance = this;
+			c = a;
+			checkPW();
+		}
+	}
+
+	void savePW(String word) {
+		if(word.equals("IGNORE")) {
+			totry = 0;      // dál nezkoušet
+			Prefs.sp.edit().putString(PW_KEY, "").apply();
+			subscriber.onPasswReady(PW_IGN);
+		}
+		else {
+			Prefs.sp.edit().putString(PW_KEY, word).apply();
+			checkPW();
+		}
+	}
+
+	void checkPW() {
+		d.l(4, String.format("passwd check..."));
+		try {
+			final String word = Prefs.sp.getString(PW_KEY, "BAD");
+			final Resources r = c.getResources();
+			String ksName = String.format("bks%02d", Integer.valueOf(r.getString(R.string.channel)));
+			int ksId = r.getIdentifier(ksName, "raw", c.getPackageName());
+			try {
+				final KeyStore ks = KeyStore.getInstance("bks");
+				InputStream kfs = r.openRawResource(ksId);
+				ks.load(kfs, word.toCharArray());
+			}
+			catch(Resources.NotFoundException e) {
+				d.abendMsg(String.format("cipher keys for channel %02d not found in key store res/raw",
+						Integer.valueOf(r.getString(R.string.channel))), e);
+				subscriber.onPasswReady(PW_BAD);
+				return;
+			}
+			if(word.length() == 0) throw new Exception("KeyStore integrity check failed.");
+			K.word = word;
+			d.l(4, String.format("passwd OK"));
+			subscriber.onPasswReady(PW_OK);
+		}
+		catch(Exception e) {
+			if(e.getMessage().equals("KeyStore integrity check failed.") && totry-- > 0) {
+				new GetPwDialog().dialog(this, fm);
+			}
+			else d.abendMsg("passwd check", e, this);
+		}
+	}
+
+	void onPW(String word) { savePW(word);}
+
+	public static class GetPwDialog extends DialogFragment
+			implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+
+		static final String TAG = "password dialog";
+
+		D d;
+		PassW caller;
+		EditText editText;
+		AlertDialog ad = null;
+
+		@Override
+		public Dialog onCreateDialog(Bundle savedInstanceState) {
+			super.onCreateDialog(savedInstanceState);
+			if(ad != null) return ad;
+			setRetainInstance(true);
+			final LinearLayout llv = (LinearLayout) LayoutInflater.from(getActivity()).inflate(R.layout.word_dialog, null);
+			editText = (EditText)llv.findViewById(R.id.enter_text);
+			final CheckBox vis = (CheckBox)llv.findViewById(R.id.visible);
+			editText.setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
+			View.OnClickListener visCheck = new View.OnClickListener() { @Override public void onClick(View v) {
+				if(vis.isChecked()) editText.setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_NORMAL);
+				else editText.setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); } };
+			vis.setOnClickListener(visCheck);
+			vis.setChecked(false);
+			ad = new AlertDialog.Builder(d.getContext())
+					.setView(llv)
+					.setTitle("enter SSL keystore password")
+					.setPositiveButton("OK", this)
+					.setNegativeButton("Cancel", this)
+					.create();
+			return ad;
+		}
+
+		@Override
+		public void onClick(DialogInterface dialog, int which) {
+			if(which == DialogInterface.BUTTON_POSITIVE)
+				caller.onPW(editText.getText().toString());
+//				PassW.instance.savePW(editText.getText().toString());
+			else
+				caller.onPW("IGNORE");
+//				PassW.instance.savePW("IGNORE");
+			caller = null;  // aby se volal jen jednou
+			dismiss();
+		}
+
+		@Override
+		public void onDismiss(DialogInterface dialog) {
+			if(caller != null) caller.onPW("IGNORE");
+		}
+
+		@Override
+		public void onDestroyView() {
+			Dialog dialog = getDialog();
+			// Work around bug: http://code.google.com/p/android/issues/detail?id=17423
+			if ((dialog != null) && getRetainInstance()) dialog.setDismissMessage(null);
+			super.onDestroyView();
+		}
+
+		void dialog(PassW caller, FragmentManager fm) {
+			this.caller = caller;
+			d = caller.d.klon(this);
+			show(fm, TAG);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/Prefs.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,121 @@
+package hh.dejsem;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.support.v7.preference.Preference;
+
+import hh.lib.PrefsFragment;
+
+public class Prefs extends PrefsFragment {
+
+	static String SSL_DEBUG_KEY;
+	static String SSL_DEBUG_DEFAULT;
+	static String SSL_DEBUG_TITLE;
+	static String SSL_DEBUG_DIALOG_TITLE;
+	static String HOST_KEY;
+	static String HOST_DEFAULT;
+	static String HOST_CACHE_KEY;
+	static String HOST_TITLE;
+	static String HOST_DIALOG_TITLE;
+	static String CHAN_KEY;
+	static String CHAN_DEFAULT;
+	static String HOME_KEY;
+	static String HOME_DEFAULT;
+	static String HOME_CACHE_KEY;
+	static String HOME_TITLE;
+	static String HOME_DIALOG_TITLE;
+	static String SSL_KEY, SSL_DEFAULT;
+
+	public static boolean sslDebug;
+	public static String homeDir;
+	public static String host;
+	public static boolean ssl;
+
+	public void getR(Context c) {
+		super.getR(c);
+		HOST_KEY = rs.getString(R.string.host_key);
+		HOST_DEFAULT = rs.getString(R.string.host_default);
+		HOST_CACHE_KEY = rs.getString(R.string.host_cache_key);
+		HOST_TITLE = rs.getString(R.string.host_title);
+		HOST_DIALOG_TITLE = rs.getString(R.string.host_dialog_title);
+		CHAN_KEY = rs.getString(R.string.chan_key);
+		CHAN_DEFAULT = rs.getString(R.string.chan_default);
+		HOME_KEY = rs.getString(R.string.home_key);
+		HOME_DEFAULT = c.getFilesDir().getAbsolutePath();
+		HOME_CACHE_KEY = rs.getString(R.string.home_cache_key);
+		HOME_TITLE = rs.getString(R.string.home_title);
+		HOME_DIALOG_TITLE = rs.getString(R.string.home_dialog_title);
+		SSL_KEY = rs.getString(R.string.ssl_key);
+		SSL_DEFAULT = rs.getString(R.string.ssl_default);
+		SSL_DEBUG_KEY = rs.getString(R.string.ssl_debug_key);
+		SSL_DEBUG_DEFAULT = rs.getString(R.string.ssl_debug_default);
+		SSL_DEBUG_TITLE = rs.getString(R.string.ssl_debug_title);
+		SSL_DEBUG_DIALOG_TITLE = rs.getString(R.string.ssl_debug_dialog_title);
+	}
+
+	@Override
+	public void refreshPrefs(Context c) {
+		getR(c);
+		super.refreshPrefs(c);
+		sslDebug = sp.getBoolean(SSL_DEBUG_KEY, Boolean.valueOf(SSL_DEBUG_DEFAULT));
+		host = sp.getString(HOST_KEY, HOST_DEFAULT);
+		homeDir = sp.getString(HOME_KEY, HOME_DEFAULT);
+		ssl = sp.getBoolean(SSL_KEY, Boolean.valueOf(SSL_DEFAULT));
+	}
+
+	public void setHome(Context c, String dir) {
+		sp.edit().putString(HOME_KEY, dir).apply();
+		refreshPrefs(c);
+	}
+
+	@Override
+	public void onCreatePreferences(Bundle bundle, String s) {
+		super.onCreatePreferences(bundle, s);
+		addPreferencesFromResource(R.xml.prefs);
+		if(rs == null) getR(getActivity());
+		refreshPrefs(getActivity());
+		setSumms();
+	}
+
+	@Override
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+		super.onSharedPreferenceChanged(sharedPreferences, key);
+		if(key.equals(HOST_KEY)) {
+			K.title.setChannelNum();
+			K.cmdConn = null;
+//			K.histlist = null;
+		}
+	}
+
+	void setSumms() {
+		findPreference(SSL_DEBUG_KEY).setSummary(String.valueOf(sslDebug));
+		findPreference(HOST_KEY).setSummary(host);
+		findPreference(HOME_KEY).setSummary(homeDir);
+		findPreference(SSL_KEY).setSummary(String.valueOf(ssl));
+	}
+
+	@Override
+	public boolean onPreferenceTreeClick(Preference preference)	{
+		String key = preference.getKey();
+		if(key.equals(HOME_KEY)) {
+			new AutoTextComplDialogFragment().enterText(d, preference, HOME_DIALOG_TITLE, HOME_CACHE_KEY);
+			return true;
+		}
+		else if(key.equals(HOST_KEY)) {
+			new AutoTextComplDialogFragment().enterText(d, preference, HOST_DIALOG_TITLE, HOST_CACHE_KEY);
+			return true;
+		}
+		else return super.onPreferenceTreeClick(preference);
+	}
+
+	void resetPrefs(Context c) {
+		getR(c);
+		super.refreshPrefs(c);
+		sp.edit().putBoolean(SSL_DEBUG_KEY, Boolean.valueOf(SSL_DEBUG_DEFAULT)).apply();
+		sp.edit().putString(HOST_KEY, HOST_DEFAULT);
+		sp.edit().putString(HOME_KEY, HOME_DEFAULT);
+		sp.edit().putBoolean(SSL_KEY, Boolean.valueOf(SSL_DEFAULT));
+		refreshPrefs(c);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/SdUri.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,115 @@
+package hh.dejsem;
+
+import android.content.Intent;
+import android.content.UriPermission;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.provider.DocumentFile;
+
+import java.util.List;
+
+import hh.lib.AbendDialogFragment;
+
+/**
+ * hledá UriPermission s cestou k SD Card a příslušné Uri ukládá
+ * - je to Activity ...
+ */
+public class SdUri extends Act implements AbendDialogFragment.IgnoreAbend {
+
+	public static final String SD_CARD_URI_PATH_KEY = "SD_CARD_URI_PATH_KEY";
+	public static Uri SD_CARD_URI = null;
+
+	public interface SDUriListener { void onSDPermissionReady(); }
+
+	public static boolean isReady() { return SdUri.SD_CARD_URI != null; }
+
+	public static void clearPermissions() {
+		Prefs.sp.edit().remove(SD_CARD_URI_PATH_KEY).apply();
+		SD_CARD_URI = null;
+	}
+
+	static SDUriListener subscriber;
+
+	@Override
+	public void onCreate(Bundle b) {
+		super.onCreate(b);
+	}
+
+	@Override
+	public void onStart() {
+		super.onStart();
+		getSdUri();
+	}
+
+	@Override
+	public void onActivityResult(int requestCode, int resultCode, Intent data) {
+		if(d.ll(4)) d.l(String.format("onActivityResult: requestCode=%d, resultCode=%d", requestCode, resultCode));
+		if(resultCode == RESULT_OK && requestCode == K.CHOOSE_SD_CARD_TREE) saveUriPermissions(data);
+	}
+
+	@Override
+	public void ignoreAbend(boolean ignore) {
+		if(ignore) {
+			ready();
+		}
+		else ((AbendDialogFragment.IgnoreAbend)subscriber).ignoreAbend(false);
+
+	}
+
+	void ready() {
+		subscriber.onSDPermissionReady();
+		finish();
+	}
+
+	void getSdUri() {
+		List<UriPermission> pups = persistedUriPermissions();
+		String sdUriPath = Prefs.sp.getString(SD_CARD_URI_PATH_KEY, "");
+		if(!pups.isEmpty() && !sdUriPath.equals(""))
+			for(UriPermission up: pups) {
+				Uri u = up.getUri();
+				String p = u.getPath();
+				if(p.equals(sdUriPath)) {
+					SD_CARD_URI = u;
+					K.SD_CARD_ROOT_DOC_FILE = DocumentFile.fromTreeUri(this, u);
+				}
+			}
+		if(SD_CARD_URI == null) getSdTree();
+		else {
+			ready();}
+	}
+
+	void saveUriPermissions(Intent data) {
+		Uri sdUri = data.getData();
+		final int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+		getContentResolver().takePersistableUriPermission(sdUri, flags);
+		String uriPath = sdUri.getPath();
+		d.l(4, String.format("SD card uri path=%s", uriPath));
+		Prefs.sp.edit().putString(SD_CARD_URI_PATH_KEY, uriPath).apply();
+		SD_CARD_URI = sdUri;
+		K.SD_CARD_ROOT_DOC_FILE = DocumentFile.fromTreeUri(this, sdUri);
+		persistedUriPermissions();
+		ready();
+	}
+
+	/**
+	 * vytahuje a vrací UriPermission a případně opisuje do logu
+	 * @return
+	 */
+	List<UriPermission> persistedUriPermissions() {
+		List<UriPermission> pups = getContentResolver().getPersistedUriPermissions();
+		if(d.ll(4)) {
+			if(pups.isEmpty()) d.l("persisted uri permissions empty");
+			else {
+				d.l("persisted uri permissions:");
+				for(UriPermission up : pups) d.l(up.toString());
+				d.l(String.format("SD card uri path=%s", Prefs.sp.getString(SD_CARD_URI_PATH_KEY, "")));
+			}
+		}
+		return pups;
+	}
+
+	/**
+	 * volba SD card tree
+	 */
+	void getSdTree() { startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), K.CHOOSE_SD_CARD_TREE); }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/SendUriNG.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,75 @@
+package hh.dejsem;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.support.v7.app.AlertDialog;
+import android.telephony.SmsManager;
+
+import java.util.List;
+
+import hh.dejsem.fm.TransferAlert;
+import hh.lib.D;
+
+/**
+ * share http URL
+ * ● complete URL from URI path
+ * ● copy URL to clipboard
+ * ● optionally share URL
+ */
+public class SendUriNG implements DialogInterface.OnClickListener {
+
+	public static void decideSendUri(String uriPath) {
+		if(uriPath != null && uriPath.length() > 0)
+			K.app.startActivity(new Intent(K.app, TransferAlert.class)
+					.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)     /*Intent.FLAG_ACTIVITY_SINGLE_TOP)*/
+					.putExtra(K.ACTION_KEY, K.TRANSFER_SEND_URI)
+					.putExtra(K.TXT_KEY, uriPath)
+			);
+	}
+
+	D d;
+	Context c;
+	AlertDialog alert;
+	public String uriStr;
+
+	public SendUriNG(D d, Context c, String uriPath) {
+		this.d = d.klon(this);
+		this.c = c;
+		uriStr = buildUri(uriPath).toString();
+		if(this.d.ll(4)) this.d.l("exposed as " + uriStr);
+		((ClipboardManager)(c.getSystemService(Context.CLIPBOARD_SERVICE)))     // pin URL on clipboard
+				.setPrimaryClip(ClipData.newPlainText("exposed URL", uriStr));
+	}
+
+	@Override
+	public void onClick(DialogInterface dialog, int which) {
+		d.l(4,"onClick");
+		if(which == DialogInterface.BUTTON_POSITIVE) send();
+	}
+
+	Uri buildUri(String uriPath) {
+		return new Uri.Builder().scheme("http").authority(Prefs.host).path(uriPath).build();
+	}
+
+	void send() {
+		Intent actionSEND = new Intent(Intent.ACTION_SEND).putExtra(Intent.EXTRA_TEXT, uriStr).setTypeAndNormalize("text/plain");
+		c.startActivity(Intent.createChooser(actionSEND, null));
+	}
+
+	public void sendTestSMS(String url) {
+		if(d.ll(4)) d.l("sent by SMS: " + url);
+		SmsManager sms = SmsManager.getDefault();
+		List<String> messages = sms.divideMessage(url);
+		/*String recipient = recipientTextEdit.getText().toString();*/
+		/*String recipient = "601593811";*/
+		String recipient = "607677931";
+		for (String message : messages) {
+			if(d.ll(5)) d.l(String.format("sms.sendTextMessage(\"%s\", null, \"%s\", null, null)", recipient, message));
+			sms.sendTextMessage(recipient, null, message, null, null);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/TitleBar.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,141 @@
+package hh.dejsem;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.AnimationDrawable;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v7.app.AppCompatActivity;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import hh.dejsem.fm.NetTaskRef;
+import hh.lib.D;
+
+/**
+ * TitleBar zobrazuje kromě záhlavových údajů také okamžité procentuální hodnoty</br>
+ * stavů datových přenosů probíhajících na pozadí;</br>
+ * animovaná ikona indikuje probíhající přenos na pozadí</br>
+ * ... obsah 1.řádky (záhlaví) okna GUI aktivit a jejich fragmentů</br>
+ * ● záhlaví má 4 pole
+ * <ol>
+ *     <im>app id</im>
+ *     <im>proměnné stavové pole probíhajících přenosů</im>
+ *     <im>momentálně nastavený server a číslo šifrovaného kanálu</im>
+ *     <im>label aktuální komponenty GUI</im>
+ * </ol></br>
+ * ● každá komponenta GUI si nastavuje label voláním setText()
+ *
+ */
+public class TitleBar extends RelativeLayout implements Runnable,  Handler.Callback {
+	static boolean pending = false;
+	public static void setPending() { pending = true; }
+	public static void unSetPending() {
+		pending = false;
+	}
+
+	public static void assign(AppCompatActivity a, int titleId, String titleText) {
+		if(K.title == null)
+			K.title = ((TitleBar)(a.getLayoutInflater().inflate(R.layout.title_bar, null))).activate();
+		K.title.setText(titleText).reassign(a, titleId);
+	}
+
+	D d;
+	Context c;
+	Resources r;
+	public ViewGroup container = null;
+	View titleView;
+	ImageView pendingIcon;
+	TextView pendingView;
+	TextView channelView;
+	TextView titleTextView;
+	String titleText = "";
+	Handler handler = new Handler(this);
+	public Messenger msgr = new Messenger(handler);
+
+	public TitleBar(Context context, AttributeSet attrs) {
+		super(context, attrs);
+		d = new D(context, getClass().getSimpleName());
+		c = context;
+		r = c.getResources();
+	}
+
+	@Override
+	public boolean handleMessage(Message msg) {
+		switch(msg.what) {
+			case K.MSG_WATCH_PROGRESS:
+				update();
+				return true;
+		}
+		return false;
+	}
+
+	@Override
+	public void run() {
+		update();
+	}
+
+	public TitleBar activate() {
+		if(Prefs.host == null) new Prefs().refreshPrefs(c);
+
+		((TextView)findViewById(R.id.title_left_text)).setText(String.format("%s.%s", r.getString(R.string.app_name), r.getString(R.string.app_version_name)));
+		channelView = ((TextView)findViewById(R.id.channel));
+		setChannelNum();
+		titleTextView = findViewById(R.id.title_right_text);
+		pendingIcon = findViewById(R.id.pending_icon);
+	    ((AnimationDrawable)pendingIcon.getBackground()).start();
+		pendingView = findViewById(R.id.pending_progress);
+		return this;
+	}
+
+	public TitleBar setChannelNum() {
+		channelView.setText(String.format("%s ch %s",
+				Prefs.host.subSequence(0, Prefs.host.indexOf('.')), r.getString(R.string.channel)));
+		invalidate();
+		return this;
+	}
+
+	public TitleBar setText(String titleText) {
+		this.titleText = titleText;
+		titleTextView.setText(titleText);
+		invalidate();
+		return this;
+	}
+
+	public TitleBar reassign(AppCompatActivity a) { return reassign(a, R.id.title_bar); }
+
+	public TitleBar reassign(AppCompatActivity a, int containerId) { return reassign((FrameLayout)(a.findViewById(containerId))); }
+
+	public TitleBar reassign(ViewGroup container) {
+		if(this.container != null) this.container.removeView(this);
+		container.addView(this);
+		this.container = container;
+		return this;
+	}
+
+	public void update() {
+		if(pending) {
+			/*((NotificationManager)d.getContext().getSystemService(Context.NOTIFICATION_SERVICE)).getActiveNotifications().length
+					// od API 23 se dá využít ke kontrole přítomnosti progres-notifikace na notification bar*/
+			String progressText = "";
+			synchronized(K.transfProgress.statSet) {
+				for(NetTaskRef tr : K.transfProgress.statSet) {
+					progressText += String.format("%2d%% ", tr.getProgress().es.getPct());
+				}
+			}
+			pendingView.setText(progressText);
+			handler.postDelayed(this, K.transfProgress.refreshInterval);
+		}
+		pendingView.setVisibility(pending ? View.VISIBLE : View.INVISIBLE);
+		pendingIcon.setVisibility(pending ? View.VISIBLE : View.INVISIBLE);
+		titleTextView.setText(titleText);
+		invalidate();
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/clipboard/Clipboard.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,80 @@
+package hh.dejsem.clipboard;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.os.Handler;
+import android.widget.Toast;
+
+import hh.dejsem.K;
+import hh.dejsem.net.NetData;
+import hh.dejsem.net.SrvCmd;
+import hh.lib.D;
+
+public class Clipboard {
+
+	D d;
+	Context c;
+	Handler.Callback h;
+	ClipboardManager clipMgr;
+	boolean test = false;
+
+	public Clipboard(Context c, Handler.Callback h, D d) {
+		this.d = d.klon(this);
+		this.c = c;
+		this.h = h;
+		clipMgr = (ClipboardManager)c.getSystemService(Context.CLIPBOARD_SERVICE);
+	}
+
+	public String getCB() {
+		if(clipMgr.hasPrimaryClip()) {
+			final ClipData cd = clipMgr.getPrimaryClip();
+			final int itemCnt = cd.getItemCount();
+			return (itemCnt > 0 && cd.getItemAt(0).getText() != null ? cd.getItemAt(0).getText().toString() : "");
+		}
+		return "";
+	}
+
+	void setCB(String s) { clipMgr.setPrimaryClip(new ClipData(new ClipDescription("dejsem", new String[] {"text/plain"}), new ClipData.Item(s))); }
+
+	void test() {
+		if(test) setCB(K.testText);
+		else setCB("");
+		test = !test;
+	}
+
+	void clear() { setCB(""); }
+
+	public boolean push() {
+		d.getVb().vibrate(30);
+		try {
+			final String s = getCB();
+			SrvCmd.PUSHCLIP.exec(d, new NetData(d, s));
+//			K.histlist = null;   // histlist has changed, discard its local copy
+			Toast.makeText(c, String.format("%d characters copied to shared clipboard", s.length()), Toast.LENGTH_LONG).show();
+			return true;
+		}
+		catch(Exception e) {
+			d.abendMsg("PUSH from clipboard", e);
+			return false; }
+	}
+
+	public boolean pull() { return pull(""); }
+
+	public boolean pull(String fn) {
+		d.getVb().vibrate(30);
+		try {
+			String cb = (String)SrvCmd.PULLCLIP.exec(d, fn);
+			K.cmdConnClose(d);
+			setCB(cb);
+			if(d.ll(4)) d.l(String.format("'%s' pulled from server", cb.length() > 64 ? cb.substring(0, 64) + "..." : cb));
+			Toast.makeText(c, "" + cb.length() + " characters pulled from shared clipboard", Toast.LENGTH_LONG).show();
+			return true;
+		}
+		catch(Exception e) {
+			d.abendMsg("PULL to clipboard", e);
+			return false;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/clipboard/HistFrag.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,240 @@
+package hh.dejsem.clipboard;
+
+import android.os.Bundle;
+import android.text.format.Formatter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import java.text.DateFormat;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.TreeMap;
+
+import hh.dejsem.K;
+import hh.dejsem.R;
+import hh.dejsem.net.SrvCmd;
+import hh.lib.D;
+import hh.lib.DF;
+
+/**
+ * manages clipboard history list
+ */
+public class HistFrag extends DF {
+
+	final String TITLETEXT = "clipboard";
+
+	public static interface Listener { void getHistEntry(String entry); }
+
+	public static HistFrag instantiate(D d/*, Listener listener*/) {
+		HistFrag hf = new HistFrag();
+		hf.d = d.klon(hf);
+//		hf.listener = listener;
+		return hf;
+	}
+
+	HistList histlist;
+	View histView = null;
+	Listener listener;
+	K.HistOrder order = K.HistOrder.TEXT;
+	boolean ascending = true;
+	TextView sText, sSize, sDate;
+	View.OnClickListener sortByText, sortBySize, sortByDate;
+//	View.OnClickListener lastMile;
+	AdapterView.OnItemClickListener clickEntry = null;
+//	HistAct.HistListAdapter histListAdapter;
+	ListView lh = null;
+
+	/*@Override
+	public void onCreate(Bundle b) {
+		super.onCreate(b);
+	}*/
+
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle b) {
+		super.onCreate(b);
+		/*if(K.histlist != null) */histView = inflater.inflate(R.layout.hist_list, null);
+		return histView;
+	}
+
+	@Override
+	public void onViewStateRestored(Bundle b) {
+		super.onViewStateRestored(b);
+		listener = (HistFrag.Listener)getActivity();
+	}
+
+	@Override
+	public void onStart() {
+		super.onStart();
+		pullHistList();
+
+		sText = (TextView)histView.findViewById(R.id.sText);
+		sortByText = new View.OnClickListener() { @Override public void onClick(View v) { sortHistList(K.HistOrder.TEXT); } };
+		sText.setOnClickListener(sortByText);
+		sSize = (TextView)histView.findViewById(R.id.sSize);
+		sortBySize = new View.OnClickListener() { @Override public void onClick(View v) { sortHistList(K.HistOrder.SIZE); } };
+		sSize.setOnClickListener(sortBySize);
+		sDate = (TextView)histView.findViewById(R.id.sDate);
+		sortByDate = new View.OnClickListener() { @Override public void onClick(View v) { sortHistList(K.HistOrder.DATE); } };
+		sDate.setOnClickListener(sortByDate);
+
+		clickEntry = new AdapterView.OnItemClickListener() {
+			@Override public void onItemClick(AdapterView<?> parent, View v, int pos, long id) { getHistItem(pos); } };
+		lh = (ListView)histView.findViewById(R.id.listHist);
+		lh.setOnItemClickListener(clickEntry);
+
+		sortHistList(K.HistOrder.DATE, false);
+	}
+
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		K.cmdConnClose(d);
+	}
+
+	@Override
+	public void onResume() {
+		super.onResume();
+		K.title.setText(TITLETEXT).update();
+	}
+
+	void pullHistList() {
+		if(histlist == null)
+			try { histlist = (HistList)SrvCmd.PULLHIST.exec(d, null); }
+			catch(Exception e) {
+				d.abendMsg("clipboard history", e);
+				histlist = null;
+			}
+	}
+
+	void dispHistList() {
+		sText.setCompoundDrawablesWithIntrinsicBounds(0,0,0,0);
+		if(order == K.HistOrder.TEXT)
+			sText.setCompoundDrawablesWithIntrinsicBounds((ascending ? R.drawable.asc : R.drawable.desc),0,0,0);
+		sSize.setCompoundDrawablesWithIntrinsicBounds(0,0,0,0);
+		if(order == K.HistOrder.SIZE)
+			sSize.setCompoundDrawablesWithIntrinsicBounds((ascending ? R.drawable.asc : R.drawable.desc),0,0,0);
+		sDate.setCompoundDrawablesWithIntrinsicBounds(0,0,0,0);
+		if(order == K.HistOrder.DATE)
+			sDate.setCompoundDrawablesWithIntrinsicBounds((ascending ? R.drawable.asc : R.drawable.desc),0,0,0);
+
+		lh.setAdapter(new HistListAdapter(d, histlist));
+	}
+
+	void getHistItem(int pos) {
+		String entry = histlist.entries.get(histlist.list.get(pos)).name;
+		listener.getHistEntry(entry);
+		getActivity().getSupportFragmentManager().popBackStack();
+	}
+
+	void sortHistList(K.HistOrder order) {
+		ascending = !ascending;
+		sortHistList(order, ascending);
+	}
+
+	void sortHistList(K.HistOrder order, boolean ascending) {
+		this.order = order;
+		this.ascending = ascending;
+		TreeMap<String, HistList.HistEntry> tree = new TreeMap<>(new HistEntryComparator(d, histlist.entries, order, ascending));
+		for(HistList.HistEntry e: histlist.entries.values()) tree.put(e.name, e);
+		histlist.list = HistList.allocList(tree.keySet());
+		if(lh != null ) lh.setAdapter(new HistListAdapter(d, histlist));
+		dispHistList();
+	}
+
+	/**
+	 * adapter for history list ordering
+	 */
+	static class HistEntryComparator implements Comparator<String> {
+		D d;
+		HashMap<String, HistList.HistEntry> map;
+		K.HistOrder order;
+		int direction = 1;
+
+		HistEntryComparator(D d, HashMap<String, HistList.HistEntry> map, K.HistOrder order, boolean ascending) {
+			this.d = d.klon("comparator");
+			this.map = map;
+			this.order = order;
+			if(!ascending) direction *= -1;
+		}
+
+		@Override
+		public int compare(String l, String r) {
+			try {
+				switch(order) {
+				case TEXT:
+					if(map.get(l).text.equals(map.get(r).text)) return l.compareTo(r) * direction;
+					else return map.get(l).text.compareTo(map.get(r).text) * direction;
+				case SIZE:
+					if(map.get(l).size == map.get(r).size) return l.compareTo(r) * direction;
+					else return Long.valueOf(map.get(l).size).compareTo(map.get(r).size) * direction;
+				case DATE:
+					if(map.get(l).date == map.get(r).date) return l.compareTo(r) * direction;
+					else return Long.valueOf(map.get(l).date).compareTo(map.get(r).date) * direction;
+				default: return 0;
+				}
+			}
+			catch(Exception e) { d.abendMsg("compare", e); throw new Error(); }
+		}
+	}
+
+	/**
+	 * adapter for histlist list display
+	 */
+	static class HistListAdapter extends BaseAdapter {
+		D d;
+		HistList history;
+		HistList.HistEntry item;
+		CheckBox chkbox;
+		ImageView icon;
+		TextView text, size, date;
+		LayoutInflater li;
+		DateFormat df;
+		boolean go = true;
+		/* ----------------------------------------------------------*/
+
+		HistListAdapter(D d, HistList history) {
+			this.d = d.klon("histview adapter");
+			this.history = history;
+			li = LayoutInflater.from(d.getContext());
+			df = android.text.format.DateFormat.getDateFormat(d.getContext());
+		}
+
+		public int getCount() { return history.list.size(); }
+
+		public Object getItem(int position) { return null; }
+
+		public long getItemId(int position) { return 0; }
+
+		public int getViewTypeCount() { return 1; }
+
+		public View getView(int position, View listEntry, ViewGroup parent) {
+			RelativeLayout v;
+			v = (RelativeLayout)(listEntry == null ? li.inflate(R.layout.hist_entry, null) : listEntry);
+			v.setId(position);
+
+			item = history.entries.get(history.list.get(position));
+
+			text = (TextView)(v.findViewById(R.id.htext));
+			text.setText(item.text);
+
+			size = (TextView)(v.findViewById(R.id.hsize));
+			size.setText(String.format(" %8s ", Formatter.formatFileSize(d.getContext(), item.size)));
+
+			date = (TextView)(v.findViewById(R.id.hdate));
+			date.setText(df.format(item.date * 1000));
+
+			return v;
+		}
+	}
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/clipboard/HistList.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,22 @@
+package hh.dejsem.clipboard;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+
+public class HistList {
+
+	public HashMap<String, HistEntry> entries;
+	public ArrayList<String> list;
+
+	public static HashMap<String, HistEntry> allocEntries() { return new HashMap<>(); }
+
+	public static ArrayList<String> allocList(Collection<String> col) { return new ArrayList<>(col); }
+
+	public static class HistEntry {
+		public String name;
+		public String text = "";
+		public long size;
+		public long date;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/BarNotification.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,59 @@
+package hh.dejsem.fm;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+
+import hh.dejsem.K;
+import hh.dejsem.R;
+import hh.lib.D;
+
+class BarNotification {
+	/**
+	 * ----------------------------------------------------------
+	 * notification in Notification Bar
+	 * ----------------------------------------------------------
+	 */
+	D d;
+	final static int id = K.TRANSFER_NOTIFICATION_ID;
+	NotificationManager notifManager;
+	Notification notif;
+	/* ----------------------------------------------------------*/
+
+	BarNotification(D d, Class<?> cls) {
+		this.d = d.klon(this);
+		final Context c = this.d.getContext();
+		notifManager = (NotificationManager)c.getSystemService(Context.NOTIFICATION_SERVICE);
+		final Intent notifIntent = new Intent(c, cls).putExtra(K.ACTION_KEY, K.TRANSFER_DISPLAY_LIST);
+		final Intent deleteIntent     // restore notification icon in notif bar when cleared
+				= new Intent(c, cls).putExtra(K.ACTION_KEY, K.TRANSFER_PIN_NOTIFICATION);
+		notif = new Notification.Builder(c)
+				.setSmallIcon(R.drawable.ic_launcher)    // mandatory
+				.setContentTitle("dejsem progress")
+					/* - FLAG_UPDATE_CURRENT znamená, že nový výskyt aktivity v případě, že aktivita už běží se napojí na stávající
+					*  - 2.arg u getActivity() odlišuje aktivitu od jiné aktivity téže třídy*/
+				.setContentIntent(PendingIntent.getActivity(c, 0, notifIntent, PendingIntent.FLAG_UPDATE_CURRENT))
+				.setDeleteIntent(PendingIntent.getActivity(c, 1, deleteIntent, 0))
+				.build();
+		notif.flags |= Notification.FLAG_NO_CLEAR;
+
+		// alternativa
+		/*Notification.Builder notifBuilder;
+		TaskStackBuilder stackBuilder;
+		stackBuilder = TaskStackBuilder.create(c);
+		// stackBuilder.addParentStack(cls); 			// Adds the back stack for the Intent (but not the Intent itself)
+		stackBuilder.addNextIntent(notifIntent); 	// Adds the Intent that starts the Activity to the top of the stack
+		notifBuilder.setContentIntent(stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT));*/
+	}
+
+	void pin() {
+		d.l(4, "notification pin");
+		notifManager.notify(id, notif);
+	}
+
+	void cut() {
+		notifManager.cancelAll();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/DialogFragmentDelete.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,62 @@
+package hh.dejsem.fm;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.support.v4.app.DialogFragment;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import hh.lib.D;
+
+public class DialogFragmentDelete extends DialogFragment implements DialogInterface.OnClickListener {
+	/**----------------------------------------------------------
+	 * dialog for "delete selected" approve
+	 * ----------------------------------------------------------*/
+	static final String TAG = "approve delete dialog";
+
+	D d;
+	String title, message;
+	boolean localFS;
+	AlertDialog dialog;
+
+	@Override
+	public Dialog onCreateDialog(Bundle savedInstanceState) {
+		super.onCreateDialog(savedInstanceState);
+		d.l(3, String.format("onCreateDialog"));
+		setRetainInstance(true);
+		if(dialog == null) {
+			AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+			builder
+					.setCancelable(true)
+					.setTitle(title)
+					.setMessage(message)
+					.setPositiveButton("OK", this)
+					.setNegativeButton("Cancel", null);
+			dialog = builder.create();
+		}
+		return dialog;
+	}
+
+	@Override
+	public void onDestroyView() {
+		d.l(3, "onDestroyView");
+		Dialog dialog = getDialog();
+		// Work around bug: http://code.google.com/p/android/issues/detail?id=17423
+		if ((dialog != null) && getRetainInstance()) dialog.setDismissMessage(null);
+		super.onDestroyView();
+	}
+
+	@Override
+	public void onClick(DialogInterface dialog, int id) {
+		final FM fm = localFS ? FragFM.localDirPanel.fm : FragFM.remoteDirPanel.fm;
+		fm.iterateDelete();
+	}
+
+	void approveDelete(PanelDirView parentPanel, String title, String message) {
+		this.d = parentPanel.d.klon(this);
+		localFS = PanelLocalDir.class.isInstance(parentPanel);
+		this.title = title;
+		this.message = message;
+		show(parentPanel.d.getFmgr(), TAG);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/DialogFragmentEntry.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,105 @@
+package hh.dejsem.fm;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentActivity;
+import android.widget.EditText;
+
+import hh.dejsem.K;
+import hh.lib.D;
+
+/**
+ * "edit text entry" dialog
+ */
+public class DialogFragmentEntry extends DialogFragment implements DialogInterface.OnClickListener {
+	static final String TAG = "enter text dialog";
+
+	D d;
+	FragmentActivity a;
+	EditText editText;
+	String title, message, text;
+	int action;
+	boolean localFS;
+	AlertDialog dialog = null;
+
+	/**
+	 *
+	 * @param savedInstanceState
+	 * @return
+	 */
+	@Override
+	public Dialog onCreateDialog(Bundle savedInstanceState) {
+		d.l(3, String.format("onCreateDialog, action=" + action));
+		setRetainInstance(true);
+		if(dialog == null) {
+			/*LayoutInflater inflater = getActivity().getLayoutInflater();
+			editText = (EditText)inflater.inflate(R.layout.text_dialog, null);*/
+			editText = new EditText(a);
+			editText.setEms(10);
+			editText.setText(text);
+			dialog = new AlertDialog.Builder(getActivity())
+					.setCancelable(true)
+					.setView(editText)
+					.setTitle(title)
+					.setMessage(message)
+					.setPositiveButton("OK", this)
+					.setNegativeButton("Cancel", null)
+					.create();
+		}
+		return dialog;
+	}
+
+	/**
+	 *
+	 * @param dialog
+	 * @param id
+	 */
+	@Override
+	public void onClick(DialogInterface dialog, int id) {
+		final String result = editText.getText().subSequence(0, editText.length()).toString();
+		if(d.ll(4)) d.l(String.format("OK from edit text, text=%s, edited text=%s", text, result));
+		final FM fm = localFS ? FragFM.localDirPanel.fm : FragFM.remoteDirPanel.fm;
+		try {
+			if(action == K.FM_ACTION_RENAME)
+				fm.renameSelected(result);
+			else if(action == K.FM_ACTION_CREATE) fm.doCreateDir(result);
+		} catch(Exception e) { d.abendMsg(e); }
+	}
+
+	/**
+	 *
+	 */
+	@Override
+	public void onDestroyView() {
+		d.l(3, "onDestroyView");
+		Dialog dialog = getDialog();
+		// Work around bug: http://code.google.com/p/android/issues/detail?id=17423
+		if ((dialog != null) && getRetainInstance()) dialog.setDismissMessage(null);
+		super.onDestroyView();
+	}
+
+	/**
+	 * příprava DialogFragmentu pro vstup textu
+	 * BACHA, kompletace samotného Fragmentu se spouští až při show(), takže getFmgr() je třeba brát z rodiče
+	 *
+	 * @param parentPanel
+	 * @param title
+	 * @param message
+	 * @param text
+	 * @param action
+	 */
+	void enterText(PanelDirView parentPanel, String title, String message, String text, int action) {
+		this.d = parentPanel.d.klon(this);
+		a = parentPanel.d.getAct();
+		localFS = PanelLocalDir.class.isInstance(parentPanel);
+		this.title = title;
+		this.message = message;
+		this.text = text;
+		this.action = action;
+		show(a.getSupportFragmentManager(), TAG);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/DialogFragmentMoveDest.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,108 @@
+package hh.dejsem.fm;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.support.v4.app.DialogFragment;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.RelativeLayout;
+
+import hh.lib.D;
+
+/**
+ * dialog to choose move destination
+ */
+public class DialogFragmentMoveDest extends DialogFragment implements DialogInterface.OnClickListener {
+
+	static final String TAG = "find move dest dialog";
+
+	D d;
+	String startWd;
+	boolean localFS;
+	AlertDialog dialog = null;
+	PanelDirView moveDestPanel;
+
+	@Override
+	public Dialog onCreateDialog(Bundle savedInstanceState) {
+		setRetainInstance(true);
+		d.l(3, String.format("onCreateDialog, fragment=%s, dialog=%s", this, dialog));
+
+		if(dialog == null) {
+			final RelativeLayout dirView = PanelDirView.DirListView.getLayout(d.getContext());
+			moveDestPanel = localFS
+					? new PanelLocalMoveDest(this.d, dirView, startWd)
+					: new PanelServerMoveDest(this.d, dirView, startWd);
+			FragFM.dialogDirPanel = moveDestPanel;  // když se refreshuje panel po modifikaci FS, bude se refreshovat i tenhle
+
+			dialog = new AlertDialog(d.getContext()) {
+				public boolean onMenuItemSelected(int featureId, MenuItem item) {
+					return handleContextItemSelected(item);
+				}
+			};
+
+			dialog.setCancelable(true);
+			dialog.setCustomTitle(null);
+			dialog.setView(dirView);
+			dialog.setButton(DialogInterface.BUTTON_POSITIVE, "Move here", this);
+			dialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", (DialogInterface.OnClickListener) null);
+
+			/*AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+			builder
+					.setCancelable(true)
+					.setCustomTitle(null)
+					.setView(dirView)
+					.setPositiveButton("Move here", this)
+					.setNegativeButton("Cancel", null);
+			dialog = builder.create();*/
+		}
+
+		return dialog;
+	}
+
+	@Override
+	public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+		d.l(4, "dialog, onCreateContextMenu");
+		moveDestPanel.buildContextMenu(menu, v, menuInfo);
+		/*// musíme potlačit items hlavního menu, které si zadává hlavní aktivita, a které obsahují submenu,
+		// protože v context menu v dialogu je potíž (zjištěno v API 22), že context menu se zobrazí normálně v popředí,
+		// kdežto submenu se zobrazí POD dialogem a objeví se až po ukončení dialogu
+		menu.setGroupVisible(R.id.menu_group_main, false);*/
+	}
+
+	boolean handleContextItemSelected(MenuItem item) {
+		if(d.ll(4)) d.l("dialog, onMenuItemSelected, item=" + item);
+		return moveDestPanel.handleContextItemSelected(item) ? true : super.onContextItemSelected(item);
+	}
+
+	@Override
+	public void onClick(DialogInterface dialog, int id) {
+		FragFM.dialogDirPanel = null;   // už je zbytečné panel refreshovat
+		final PanelDirView parentPanel = localFS ? FragFM.localDirPanel : FragFM.remoteDirPanel;
+		parentPanel.doMove(moveDestPanel.fm.getCwdName());
+	}
+
+	@Override
+	public void onDestroyView() {
+		d.l(3, "onDestroyView");
+		// Work around bug: http://code.google.com/p/android/issues/detail?id=17423
+		if((dialog != null) && getRetainInstance()) dialog.setDismissMessage(null);
+		super.onDestroyView();
+	}
+
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		FragFM.dialogDirPanel = null;
+		this.d.l(2, "onDestroy");
+	}
+
+	void moveDest(PanelDirView parentPanel, String startWd) {
+		d = new D(this, String.format("%s.%s", parentPanel.d.logPrefix, getClass().getSimpleName()));
+		this.startWd = startWd;
+		localFS = PanelLocalDir.class.isInstance(parentPanel);
+		show(parentPanel.d.getFmgr(), TAG);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/EntitySize.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,139 @@
+package hh.dejsem.fm;
+
+import android.os.Bundle;
+
+/**
+ * drží a poskytuje údaje o velikosti file/directory/batch a o průběhu přenosu
+ * <p>
+ * ● metody jsou synchronizovány, protože položky running... jsou průběžně aktualizovány
+ *      metodami síťových přenosů běžících ve zvláštních vláknech ve třídě LongAsyncTask
+ *      a průběžně z nich čerpají v průběhu přenosů třídy Title a ProgressMonitor
+ */
+public class EntitySize {
+	static final int dirSize = 4096;
+
+	static final String TARGET_SIZE_KEY = "TARGET_SIZE_KEY";
+	static final String TARGET_FNUM_KEY = "TARGET_FNUM_KEY";
+	static final String TARGET_DNUM_KEY = "TARGET_DNUM_KEY";
+
+	static final String RUNNING_SIZE_KEY = "RUNNING_SIZE_KEY";
+	static final String RUNNING_FNUM_KEY = "RUNNING_FNUM_KEY";
+	static final String RUNNING_DNUM_KEY = "RUNNING_DNUM_KEY";
+	static final String PCT_KEY = "PCT_KEY";
+	static final String STALLED_KEY = "STALLED_KEY";
+	static final String FN_KEY = "FN_KEY";
+
+	private long targetSize = 0;    // incremented by file targetSize during batch transfer
+	private int targetFnum = 0;
+	private int targetDnum = 0;
+	private long runningSize = 0;   // incremented by buff targetSize during current file transfer; after finishing curr file, runningSize == targetSize
+	private long lastRunningSize = 0;
+	private long stallTs = System.currentTimeMillis();
+	private int runningFnum = 0;
+	private int runningDnum = 0;
+	private String fn = ""; 	    // file name of file currently transferred in a batch
+	/* ----------------------------------------------------------*/
+
+	public EntitySize() {}
+
+	public EntitySize(EntitySize es) { add(es); }
+
+	public EntitySize(long targetSize, int targetFnum, int targetDnum) {
+		this.targetSize = targetSize;
+		this.targetFnum = targetFnum;
+		this.targetDnum = targetDnum;
+	}
+
+	public synchronized void setTargetSize(long targetSize) { this.targetSize = targetSize; }
+
+	/*synchronized void setFnum(int fnum) { this.fnum = fnum; }*/
+
+	synchronized void add(EntitySize es) {
+		targetSize += es.targetSize;
+		targetFnum += es.targetFnum;
+		targetDnum += es.targetDnum;
+		runningSize += es.runningSize;
+		runningFnum += es.runningFnum;
+		runningDnum += es.runningDnum;
+		fn = es.fn;
+	}
+
+	synchronized void addTargetFile(long size) {
+		targetSize += size;
+		targetFnum++;
+	}
+
+	synchronized void addTargetDir() {
+		targetSize += dirSize;
+		targetDnum++;
+	}
+
+	/*synchronized void incrS(int incr) { runningSize += incr; }*/
+
+	public synchronized void incrR(int incr, String fn) {
+		runningSize += incr;
+		this.fn = fn;
+	}
+
+	public synchronized void incrF() {
+		runningFnum++;
+	}
+
+	public synchronized void incrD(String dn) {
+		runningDnum++;
+		runningSize += dirSize;
+		this.fn = dn;
+	}
+
+	synchronized EntitySize zero() {
+		runningSize = 0;
+		runningFnum = 0;
+		runningDnum = 0;
+		runningSize = 0;
+		return this;
+	}
+
+	public synchronized long getTargetSize() { return targetSize; }
+
+	synchronized int getTargetFnum() { return targetFnum; }
+
+	synchronized int getTargetDnum() { return targetDnum; }
+
+	public synchronized long getRunningSize() { return runningSize; }
+
+	synchronized String getFn() { return fn; }
+
+	public synchronized int getPct() { return (targetSize == 0 ? 0 : (int)(100 * runningSize / targetSize)); }
+
+	synchronized Bundle getTarget() {
+		Bundle b = new Bundle();
+		b.putLong(TARGET_SIZE_KEY, targetSize);
+		b.putInt(TARGET_FNUM_KEY, targetFnum);
+		b.putInt(TARGET_DNUM_KEY, targetDnum);
+		return b;
+	}
+
+	synchronized Bundle getStatus() {
+		boolean stalled = false;
+		if(System.currentTimeMillis() - stallTs > 10*1000) {
+			stalled = lastRunningSize == runningSize;
+			lastRunningSize = runningSize;
+			stallTs = System.currentTimeMillis();
+		}
+		Bundle b = new Bundle();
+		b.putLong(TARGET_SIZE_KEY, targetSize);
+		b.putLong(RUNNING_SIZE_KEY, runningSize);
+		b.putInt(RUNNING_FNUM_KEY, runningFnum);
+		b.putInt(RUNNING_DNUM_KEY, runningDnum);
+		b.putString(FN_KEY, fn);
+		b.putInt(PCT_KEY, getPct());
+		b.putBoolean(STALLED_KEY, stalled);
+		return b;
+	}
+
+	/*@Override
+	synchronized public String toString() {
+		return "(fn=" + fn + ", targetSize=" + targetSize + ", fnum=" + fnum + ", dnum=" + dnum + ", runningSize=" + runningSize + ")";
+	}*/
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FM.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,133 @@
+package hh.dejsem.fm;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.LinkedList;
+
+import hh.dejsem.K;
+import hh.lib.D;
+
+/**
+ * base class of file manager operations on local/remote files
+ * <p>
+ * ● instance holds info about underlying file system in form of stack of lists of nested directories
+ **/
+abstract class FM {
+	D d;
+	FileList cwdFl = null;          // file list of cwd
+	LinkedList<FileList> stack = new LinkedList<FileList>();
+	HashMap<String, FileList> cache = new HashMap<String, FileList>();
+
+	FM(D d) { this.d = d.klon(this); }
+
+	abstract long getFreeSpace(String path) throws Exception;
+
+	abstract EntitySize reckon(String[] sel) throws Exception;
+
+	abstract void doCreateDir(String dirName) throws Exception;
+
+	abstract void renameSelected(String to) throws Exception;
+
+	void iterateMove(String to) throws Exception {
+		for(File f: getArrSelection()) doMove(f.getPath(), to);
+		clearSelected();
+		stack.clear();
+		FragFM.refreshNotify(FMLocal.class.isInstance(this));
+	}
+
+	abstract void doMove(String from, String to) throws Exception;
+
+	abstract void iterateDelete();
+
+	void stackAndEnterDir(String ncwd) {
+		if(cwdFl != null) stack.push(cwdFl);
+		if(stack.size() > 6) stack.removeLast();
+		enterDir(ncwd);
+	}
+
+	void enterDir() { enterDir(cwdFl.dirName); }
+
+	void enterDir(String dirName) {
+		if(d.ll(4)) d.l("enter dir=" + dirName);
+		cwdFl = getFileList(dirName);
+	}
+
+	FileList cacheFileList(FileList fileList) {
+		cache.put(fileList.dirName, fileList);
+		return fileList;
+	}
+
+	/*void goUp() {
+		if(stack.empty()) {
+			String parent = new File(cwdFl.dirName).getParent();
+			if(parent == null) {
+				parent = "/";
+				if(fmsrv.cacheFileR != null) fmsrv.cacheFileR.delete(); }
+			try { enterDir(parent); } catch(Exception e) { d.abendMsg("entering dir", e); }
+		}
+		else cwdFl = stack.pop();
+	}*/
+
+	void goUp() {
+		String parent = new File(cwdFl.dirName).getParent();
+		if(parent == null) {
+			parent = "/";
+			if(K.cacheFileR != null) K.cacheFileR.delete(); }
+		stackAndEnterDir(parent);
+	}
+
+	abstract FileList createFileList(String cwdN);
+
+	int getCurrListPos() { return cwdFl.getCurrPos(); }
+
+	void setCurrListPos(int pos) { cwdFl.setCurrPos(pos); }
+
+	FileList getFileList(String cwdN) {
+		if(cache.containsKey(cwdN)) return cache.get(cwdN);
+		else return createFileList(cwdN);
+	}
+
+	FileListAdapter getListAdapter() { return cwdFl.getListAdapter(); }
+
+	String getFnByPos(int pos) { return cwdFl.getFnByPos(pos); }
+
+	String fullPath(String fn) { return getPath(getCwdName(), fn); }
+
+	String getPath(String fp, String fn) { return new File(fp, fn).getPath(); }
+
+	String getCwdName() { return cwdFl.dirName; }
+
+	File[] getArrSelection() { return cwdFl.getArrSelection(); }
+
+	String[] getStrArrSelection() { return cwdFl.getStrArrSelection(); }
+
+	void clearSelected() { cwdFl.clearSelected(); }
+
+	void setSelection(int pos) { cwdFl.setSelection(pos, true);}
+
+	File[] setArrSelection() { return cwdFl.getArrSelection(); }
+
+	void setStrArrSelection(String[] selection) { cwdFl.setStrArrSelection(selection); }
+
+	void setStrSelection(String fn) { cwdFl.setStrSelection(fn, true); }
+
+	FileAttrs getFa(String fn) { return cwdFl.getFa(fn); }
+
+	boolean isDir(String fn) { return cwdFl.get(fn).isDir(); }
+
+	boolean isFile(String fn) { return !isDir(fn); }
+
+	EntitySize determineSizeOf() throws Exception {
+		String[] sel = getStrArrSelection();
+		return reckon(sel);
+	}
+
+	void refreshFileList() {
+		if(d.ll(4)) d.l("refreshing file list, dir=" + cwdFl.dirName);
+		deleteCache(cwdFl.dirName);
+		cwdFl.populateFileList();
+		cacheFileList(cwdFl);
+	}
+
+	abstract void deleteCache(String fp);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FMLocal.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,100 @@
+package hh.dejsem.fm;
+
+import java.io.File;
+
+import hh.dejsem.K;
+import hh.lib.D;
+
+/**
+ * file system manager operations on local files
+ */
+public class FMLocal extends FM {
+
+	FSLocal fs;
+
+	FMLocal(D d) {
+		super(d);
+		fs = new FSLocal(d);
+	}
+
+	@Override
+	long getFreeSpace(String path) { return new File(path).getFreeSpace(); }
+
+	@Override
+	EntitySize reckon(String[] selection) throws Exception { return FileList.reckon(selection); }
+
+	@Override
+	void doCreateDir(String dirName) throws Exception {     // dirName je relativní vůči current dir
+		dirName = dirName.replaceAll(K.FN_RESERVED_CHARS_REGEX, ".");
+		if(d.ll(4)) d.l(String.format("creating directory %s/%s", cwdFl.dirName, dirName));
+		String fp = new File(cwdFl.dirName, dirName).getCanonicalPath();
+		try {
+			fs.doCreateDir(fp);
+			FragFM.refreshNotify(true);
+		}
+		catch(K.NotCompleted n) { d.lmwa("directory " + fp + " was not created"); }
+	}
+
+	@Override
+	void renameSelected(String to) throws Exception {
+		final String from = getArrSelection()[0].getName();
+		to = to.replaceAll(K.FN_RESERVED_CHARS_REGEX, ".");
+		if(d.ll(3)) d.l("rename, from=" + from + ", to=" + to);
+		File fromF = new File(cwdFl.dirName, from);
+		File toF = new File(cwdFl.dirName, to);
+		if(toF.exists()) d.lmwa(to + " already exists");
+		else {
+			try {
+				fs.doRename(fromF.getCanonicalPath(), toF.getCanonicalPath());
+				FragFM.refreshNotify(true);
+			} catch(K.NotCompleted n) { d.lmwa("rename " + fromF.getCanonicalPath() + " to " + toF.getCanonicalPath() + " unsuccessful"); }
+		}
+	}
+
+	@Override
+	void doMove(String from, String to) throws Exception {
+		File fromF = new File(from);
+		File toF = new File(to, fromF.getName());
+		if(toF.exists()) d.lmwa(toF.getCanonicalPath() + " already exists");
+		else {
+			if(d.ll(3)) d.l("moving from " + from + " to " + to);
+			try { fs.doRename(fromF.getCanonicalPath(), toF.getCanonicalPath()); }
+			catch(K.NotCompleted n) { d.lmwa("" + fromF.getCanonicalPath() + " not moved"); }
+		}
+	}
+
+	@Override
+	void iterateDelete() {
+		try {
+			recurseDelete(getArrSelection());
+		} catch(Exception e) {
+			d.abendMsg(e);
+		}
+		finally {
+			FragFM.refreshNotify(true);
+		}
+	}
+
+	void recurseDelete(File[] selection) throws Exception {
+		for(File f: selection) {
+			if(f.isDirectory()) {
+				if(d.ll(3)) d.l("recurse delete: diving into " + f.getPath());
+				recurseDelete(f.listFiles()); }
+			doDelete(f); }
+	}
+
+	void doDelete(File f) throws Exception {
+		if(d.ll(3)) d.l(String.format("deleting %s", f.getPath()));
+		//if(f.getName().endsWith("C")) throw new Exception("test ABENDu");   // +++
+		try { fs.doDelete(f.getCanonicalPath()); }
+		catch(K.NotCompleted n) { d.lmwa(String.format("%s NOT deleted", f.getCanonicalPath())); }
+	}
+
+	@Override
+	FileList createFileList(String dirName) { return cacheFileList(new LocalFileList(d, dirName)); }
+
+	@Override
+	void deleteCache(String fp) {
+		cache.remove(fp);
+		/*D.cacheFileL.delete();*/ }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FMServer.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,113 @@
+package hh.dejsem.fm;
+
+import java.io.File;
+
+import hh.dejsem.K;
+import hh.dejsem.net.SrvCmd;
+import hh.lib.D;
+
+/**
+ * file system manager operations on remote files
+ */
+public class FMServer extends FM {
+
+	FSServer fs;
+
+	FMServer(D d) {
+		super(d);
+		fs = new FSServer(d);
+	}
+
+	@Override
+	long getFreeSpace(String path) throws Exception {
+		long free = (Long) SrvCmd.FREE.exec(d, null);
+		FragFM.sweepOutNetCmd(((FragFM)d.getFragment()).d);
+		return free;
+	}
+
+	@Override
+	EntitySize reckon(String[] selection) throws Exception {
+		EntitySize ds = new EntitySize();
+		for(String fp: selection) {
+			FileAttrs fa = getFa(new File(fp).getName());
+			if(fa.isFile()) { ds.addTargetFile(fa.size); }
+			else ds.add((EntitySize)SrvCmd.RECKON.exec(d, fp));
+		}
+		return ds;
+	}
+
+	@Override
+	void doCreateDir(String dirName) {
+		if(d.ll(3)) d.l("creating directory " + dirName);
+		if(cwdFl.nameExists(dirName)) d.lmwa("name " + dirName + " already exists in this directory");
+		else
+			try {
+				fs.doCreateDir(fullPath(dirName));
+				FragFM.refreshNotify(false);
+				FragFM.sweepOutNetCmd(((FragFM)d.getFragment()).d); }
+			catch(Exception e) { d.abendMsg("create directory", e); }
+	}
+
+	@Override
+	void renameSelected(String to) {
+		final String from = getArrSelection()[0].getName();
+		if(d.ll(3)) d.l("rename, from=" + from + ", to=" + to);
+		if(cwdFl.nameExists(to)) d.abendMsg("name " + to + " already exists in this directory", null);
+		else
+			try {
+				fs.doRename(fullPath(from), fullPath(to));
+				FragFM.refreshNotify(false);
+				FragFM.sweepOutNetCmd(((FragFM)d.getFragment()).d); }
+			catch(Exception e) { if(!K.EntryNotFound.class.isInstance(e)) d.abendMsg("rename entry", e); }
+	}
+
+	@Override
+	void doMove(String from, String to) throws Exception {
+		if(d.ll(3)) d.l("moving from " + from + " to " + to);
+		try { fs.doMove(from, to); }
+		catch(Exception e) {
+			if(K.EntryNotFound.class.isInstance(e)) {
+				final String fp = new File(to).getParent();
+				deleteCache(fp);
+				throw new Exception(String.format("%s not found on server", fp)); }
+			else d.abendMsg("move entry", e);
+		}
+	}
+
+	@Override
+	void iterateDelete() {
+		try {
+			for(File f: getArrSelection()) doDelete(f);
+		} catch(Exception e) {
+			e.printStackTrace();
+		} finally {
+			FragFM.refreshNotify(false);
+		}
+	}
+
+	void doDelete(File f) {
+		try {
+			if(d.ll(3)) d.l(String.format("deleting %s", f.getPath()));
+			fs.doDelete(f.getPath()); }
+		catch(Exception e) { d.abendMsg("delete entry", e); }
+	}
+
+	/*
+	@Override
+	void expose(String[] selection) throws Exception {
+		String uriName = (String)SrvCmd.EXPOSE.exec(this, selection[0]);
+		fmsrv.sweepOutNet();
+		if(uriName != null) sendSMS(uriName);
+		if(d.ll(4)) l(uriName + " exposed");
+	}
+	*/
+
+	@Override
+	FileList createFileList(String ncwd) { return cacheFileList(new RemoteFileList(d, ncwd)); }
+
+	@Override
+	void deleteCache(String fp) {
+		cache.remove(fp);
+		K.cacheFileR.delete(); }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FSLocal.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,216 @@
+package hh.dejsem.fm;
+
+import android.support.v4.provider.DocumentFile;
+
+import java.io.File;
+import java.util.HashMap;
+
+import hh.dejsem.K;
+import hh.lib.D;
+
+/**
+ * ----------------------------------------------------------
+ * metody modifikace objektů lokálního file systému
+ * <p>
+ * ● všechno se točí kolem ošetření externí SD-karty, využívá se DocumentFile
+ * <p>
+ * ● smysl by mělo použití DocumentFile pro vnitřní i externí kartu, ale performace je tragická,
+ *      takže implementace je polovičatá a živelná
+ * <p>
+ * ● použité DocumentFiles se cachují, ale jaký to má vliv na performace jsem nezkoumal
+ * ----------------------------------------------------------
+ */
+public class FSLocal {
+
+	/**
+	 * Informuje je-li argument na externí SD-kartě podle mount point SD-karty.
+	 * Mount point se zjišťuje na začátku aplikace skrz Intent.ACTION_OPEN_DOCUMENT_TREE.
+	 *
+	 * @param fp je absolutní
+	 * @return true je-li argument na externí SD-kartě
+	 */
+	static boolean isOnSdCard(String fp) { return fp.startsWith(K.SD_CARD_MOUNT_POINT); }
+
+	D d;
+	HashMap<String, DocumentFile> docFileCache = new HashMap<>();
+
+	public FSLocal(D d) { this.d = d.klon(this); }
+
+	/**
+	 * Vrací DocumentFile z cache nebo ho vytvoří.
+	 *
+	 * @param fp je absolutní
+	 * @return DocumentFile
+	 * @throws
+	 */
+	DocumentFile getDocFile(String fp) {
+		DocumentFile df = null;
+		if(docFileCache.containsKey(fp)) df = docFileCache.get(fp);
+		else {
+			if(isOnSdCard(fp)) df = getSdCardDocFile(fp);
+			else {
+				File f = new File(fp);
+				if(f.exists()) df = DocumentFile.fromFile(f);
+			}
+			if(df != null) docFileCache.put(fp, df);
+		}
+		return df;
+	}
+
+	/**
+	 * Vrací DocumentFile objektu na externí SD-kartě.
+	 *
+	 * @param fp the fp
+	 * @return the sd card doc file
+	 */
+	public DocumentFile getSdCardDocFile(String fp) {     // fp je absolutní cesta
+		String relPath = fp.replace(K.SD_CARD_MOUNT_POINT, "");
+		String[] subs = relPath.split("/");
+		DocumentFile df = K.SD_CARD_ROOT_DOC_FILE;
+		for(String sub: subs) {
+			df = df.findFile(sub);
+			if(df == null) break;
+		}
+		return df;
+	}
+
+	/**
+	 * Vytvoří directory a vrátí jeho DocumentFile
+	 *
+	 * @param dirPath je absolutní
+	 * @return DocumentFile
+	 * @throws K.NotCreated
+	 */
+	public DocumentFile doCreateDir(String dirPath) throws Exception {
+		return doCreateDir(dirPath, null);
+	}
+
+	/**
+	 * Vytvoří directory child v dir parent a vrátí DocumentFile.
+	 * Když parent neexistuje, změní se na K.SD_CARD_MOUNT_POINT nebo "/" a child relativně k němu.
+	 * Je-li param child null nebo prázdný, vrací se DocumentFile parenta.
+	 *
+	 * @param parent je absolutní
+	 * @param child  je relativní vůči parent, může být null nebo prázdný
+	 * @return DocumentFile
+	 * @throws K.NotCreated
+	 */
+	public DocumentFile doCreateDir(String parent, String child) throws K.NotCreated {
+		DocumentFile parentDf = getDocFile(parent);
+		if(parentDf == null) {
+			child = (child == null ? new File(parent) : new File(parent, child)).getAbsolutePath();
+			if(isOnSdCard(parent)) {
+				child = child.replace(K.SD_CARD_MOUNT_POINT, "");
+				if(child.startsWith("/")) child = child.substring(1, child.length() - 1);
+				parent = K.SD_CARD_MOUNT_POINT;
+				parentDf = K.SD_CARD_ROOT_DOC_FILE;
+			}
+			else {
+				parent = "/";
+				parentDf = getDocFile(parent);
+			}
+		}
+		else if(child == null || child.length() == 0) return parentDf;
+		return createDirChain(parentDf, parent, child);
+
+	}
+
+	/**
+	 * Vytvoří directory child včetně jeho cesty v dir parent a vrátí DocumentFile.
+	 * Parent musí existovat.
+	 *
+	 * @param parentDf parent DocumentFile
+	 * @param parent   absolutní cesta
+	 * @param child    cesta k child relativně k parent
+	 * @return DocumentFile
+	 * @throws K.NotCreated
+	 */
+	public DocumentFile createDirChain(DocumentFile parentDf, String parent, String child) throws K.NotCreated {
+		DocumentFile df = parentDf;
+		if(child != null && child.length() > 0) {
+			String path = parent;
+			for(String sub : child.split("/")) {
+				path += "/" + sub;
+				if(docFileCache.containsKey(path)) df = getDocFile(path);
+				else {
+					df = createDir(df, sub);
+					docFileCache.put(path, df);
+				}
+			}
+		}
+		return df;
+	}
+
+	/**
+	 * Vytvoří directory fn v dir parent a vrátí DocumentFile.
+	 * Fn je jméno bez cesty.
+	 * Parent musí existovat.
+	 *
+	 * @param parentDf parent DocumentFile
+	 * @param fn       prosté jméno bez cesty
+	 * @return DocumentFile
+	 * @throws K.NotCreated
+	 */
+	public DocumentFile createDir(DocumentFile parentDf, String fn) throws K.NotCreated {  // fn bez cesty
+		DocumentFile df = parentDf.findFile(fn);
+		if(df == null) {
+			df = parentDf.createDirectory(fn);
+			if(df != null) {
+				if(df.getName().equals(fn) && d.ll(4)) d.l(String.format("dir  %s created", fn));
+				else {
+					df.delete();
+					throw new K.NotCreated(String.format("dir  %s can't be created, name already in use", fn));
+				}
+			}
+			else throw new K.NotCreated(String.format("dir  %s not created for unknown reason", fn));
+		}
+		else {
+			if(df.isDirectory()) { if(d.ll(4)) d.l(String.format("dir %s ready", fn)); }
+			else new K.NotCreated(String.format("dir  %s can't be created, name already in use", fn));
+		}
+		return df;
+	}
+
+	/**
+	 * Rename.
+	 * Na externí SD-kartě se dá změnit jen jméno filu, nikoli cesta :-(
+	 *
+	 * @param from from je absoultní cesta
+	 * @param to   to   je absoultní cesta
+	 * @throws K.NotCompleted, K.NotCreated
+	 */
+	void doRename(String from, String to) throws K.NotCompleted, K.NotCreated {
+		if(isOnSdCard(from)) {
+			if(!getDocFile(from).renameTo(new File(to).getName())) throw new K.NotCompleted();
+		}
+		else if(!new File(from).renameTo(new File(to))) throw new K.NotCompleted();
+	}
+
+	/**
+	 * Implementace "move" naráží na potíže s SD-kartou - DocumentFile neumožňuje přesunutí jména do jiné cesty,
+	 *      takže by tu měl být test na externí SD-kartu a warning "Neimplementováno".
+	 *
+	 * @param from the from
+	 * @param to   the to
+	 * @throws Exception the exception
+	 */
+	void doMove(String from, String to) throws Exception {
+		File fromF = new File(from);
+		File toF = new File(to, fromF.getName());
+		if(!fromF.renameTo(toF)) throw new K.NotCompleted();
+	}
+
+	/**
+	 * Delete.
+	 * Na vnitřní kartě je ponechána operace na File kvůli performace.
+	 *
+	 * @param fp je absolutní
+	 * @throws K.NotCreated
+	 */
+	void doDelete(String fp) throws K.NotCompleted, K.NotCreated {
+		if(isOnSdCard(fp)) {
+			if(!getDocFile(fp).delete()) throw new K.NotCompleted();
+		}
+		else if(!new File(fp).delete()) throw new K.NotCompleted();
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FSServer.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,31 @@
+package hh.dejsem.fm;
+
+import hh.dejsem.net.SrvCmd;
+import hh.lib.D;
+
+/**
+ * metody modifikace objektů file systému na serveru
+ **/
+public class FSServer {
+
+	D d;
+
+	public FSServer(D d) { this.d = d.klon(this); }
+
+	void doCreateDir(String dirPath) throws Exception {
+		SrvCmd.CREATEDIR.exec(d, dirPath);
+	}
+
+	void doRename(String from, String to) throws Exception {
+		SrvCmd.MOVE.exec(d, new String[] {from, to});
+	}
+
+	void doMove(String from, String to) throws Exception {
+		SrvCmd.MOVE.exec(d, new String[]{from, to});
+	}
+
+	void doDelete(String fp) throws Exception {
+		SrvCmd.DELETE.exec(d, fp);
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FileAttrs.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,33 @@
+package hh.dejsem.fm;
+
+import android.widget.CheckBox;
+
+/**
+ * record of attributes of the file
+ */
+class FileAttrs {
+	String name;
+	long size;
+	long date;
+	CheckBox cb = null;
+	boolean checked = false;
+	boolean typeDir = true;
+
+	FileAttrs(String name, long size, long date) {
+		this.name = name;
+		this.size = size;
+		this.date = date;
+		typeDir = (size < 0);
+	}
+
+	boolean isChecked() { return cb == null ? checked : cb.isChecked(); }
+
+	FileAttrs setChecked(boolean checked) {
+		if(cb == null) this.checked = checked; else cb.setChecked(checked);
+		return this;
+	}
+
+	boolean isDir() { return typeDir; }
+
+	boolean isFile() { return !typeDir; }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FileList.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,284 @@
+package hh.dejsem.fm;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.TreeMap;
+
+import hh.dejsem.K;
+import hh.dejsem.Prefs;
+import hh.dejsem.net.NetChOps;
+import hh.dejsem.net.FileIO;
+import hh.dejsem.net.NetConn;
+import hh.dejsem.net.SrvCmd;
+import hh.lib.D;
+
+/**
+ * list of files in local directory
+ */
+class LocalFileList extends FileList {
+
+	LocalFileList(D d, String ncwd) { super(d, ncwd); }
+
+	@Override
+	void collectFileAttrs() throws Exception {
+		Prefs.sp.edit().putString(K.CACHED_LOC_CWD_KEY, dirName).apply();
+		File dir = new File(dirName);
+		int len = ( dir.listFiles() != null ? dir.listFiles().length : 0 );
+		if(len > 0)
+			for(File f: dir.listFiles()) {
+				long size = (f.isFile() ? f.length() : -1);
+				long date = f.lastModified();
+				put(f.getName(), new FileAttrs(f.getName(), size, date)); }
+	}
+}
+
+/**
+ * list of files in server directory
+ */
+class RemoteFileList extends FileList {
+
+	RemoteFileList(D d, String ncwd) { super(d, ncwd); }
+
+	@Override
+	void collectFileAttrs() throws Exception {
+		if(!NetConn.netAvailable(d)) return;
+		String fn;
+		String cached = FragFM.getCachedCwdName(d, K.cacheFileR);
+		if(cached == null || !cached.equals(dirName)) {
+			if(d.ll(4)) d.l(4, "no cache for " + dirName + ", creating...");
+			if(K.cacheFileR.exists()) K.cacheFileR.delete();		// cache používám jako random access, tak proto delete
+			try { SrvCmd.PULLLIST.exec(d, new String[] {dirName, K.cacheFileR.getPath()}); }
+			catch(K.EntryNotFound e) {}
+			FragFM.sweepOutNetCmd(d);
+		}
+		if(d.ll(4)) d.l("cache file size=" + K.cacheFileR.length() + ", cache fn=" + K.cacheFileR.getPath());
+		try {
+			FileIO fio = new FileIO(d, K.cacheFileR, "r");
+			int i = 12 + NetChOps.getStr(d, fio).length();		// skip cwdFl name
+			while((fn = NetChOps.getStr(d, fio)) != null && fn.length() > 0) {
+				long size = NetChOps.getNum(d, fio);
+				long date = NetChOps.getNum(d, fio)*1000;		// msecs
+				put(fn, new FileAttrs(fn, size, date));
+				fn = null; }
+			fio.close();
+		}
+		catch(IOException e) { throw new Exception("file attributes from cache", e); }
+	}
+}
+
+/**
+ * seznam souborů ve formě TreeMap<String, FileAttrs> a operace na něm
+ * <p>
+ *     ● instance drží ještě
+ *     <ul>
+ *     <li>directory name
+ *     <li>seznam file names jako String[]
+ *     <li>aktuální pozici v seznamu
+ *     <li>ListView adapter
+ *     </ul>
+ **/
+abstract class FileList extends TreeMap<String, FileAttrs> {
+
+	D d;
+	String dirName = "";
+	String[] ls = new String[] {};
+	int listPos = 0;
+	FileListAdapter listAdapter = null;
+
+	/**
+	 * vrací celkovou velikost souborů v lokálním FS zadaných seznamem jmen
+	 *
+	 * @param selection String[]: seznam file names
+	 * @return EntitySize
+	 * @throws Exception the exception
+	 */
+	static EntitySize reckon(String[] selection) throws Exception {
+		ArrayList<File> fa = new ArrayList<File>(selection.length);
+		for(String fp: selection) fa.add(new File(fp));
+		File[] fsel = new File[fa.size()];
+		return reckonRecurse(fa.toArray(fsel));
+	}
+
+	static EntitySize reckonRecurse(File[] selection) throws Exception {
+		EntitySize ds = new EntitySize();
+		for(File f: selection) {
+			if(f.isFile()) { ds.addTargetFile(f.length()); }
+			else {
+				ds.addTargetDir();
+				// za nějakých okolností prázdný dir vrátí null místo prázdného pole (možná podle permissions, nezkoumal jsem)
+				final File[] fl = f.listFiles();
+				if(fl != null) ds.add(reckonRecurse(fl));
+			}
+		}
+		return ds;
+	}
+
+	FileList(D d) {
+		super();
+		this.d = d.klon(this); }
+
+	FileList(D d, String cwd) {
+		this(d);
+		dirName = cwd;
+		populateFileList();
+	}
+
+	@Override
+	public String toString() { return super.toString() + "[" + dirName + "]"; }
+
+	abstract void collectFileAttrs() throws Exception;
+
+	/**
+	 * obnoví file list z file systému se zachováním selection
+	 */
+	void populateFileList() {
+		final String[] sel = getStrArrSelection();
+		clear();
+		try { collectFileAttrs(); }
+		catch(Exception e) {
+			d.abendMsg("dir list", e);
+			clear(); }
+		setStrArrSelection(sel);
+		populateLs();
+	}
+
+	/**
+	 * vrací uloženou pozici
+	 *
+	 * @return int
+	 */
+	int getCurrPos() { return listPos; }
+
+	/**
+	 * cachuje pozici ve file listu
+	 *
+	 * @param pos   int: pozice
+	 */
+	void setCurrPos(int pos) { listPos = pos; }
+
+	/**
+	 * vrací atributy soubory se jménem fn
+	 *
+	 * @param fn Strig: file name
+	 * @return FileAttrs
+	 */
+	FileAttrs getFa(String fn) { return get(fn); }
+
+	/**
+	 * vrací file list view adapte (případně ho alokuje)
+	 *
+	 * @return FileListAdapter
+	 */
+	FileListAdapter getListAdapter() {
+		if(listAdapter == null) listAdapter = new FileListAdapter(d, this);
+		return listAdapter;
+	}
+
+	boolean nameExists(String fn) { return containsKey(fn); }
+
+	/**
+	 * aktualizuje file names array
+	 */
+	void populateLs() {
+		ls = new String[size()];
+		if(ls.length > 0) {
+			String fn = firstKey();
+			for(int i = 0; i < ls.length; i++) {
+				ls[i] = fn;
+				if(d.ll(5)) d.l("ls[" + i + "]=" + ls[i]);
+				if(higherKey(fn) != null) fn = higherKey(fn); } }
+	}
+
+	/**
+	 * gets fn by file list position
+	 *
+	 * @param pos int: position in file list
+	 * @return String file name
+	 */
+	String getFnByPos(int pos) { return ls[pos]; }
+
+	/**
+	 * get file names of selected files
+	 *
+	 * @return string array of fullpaths of selected files
+	 */
+	String[] getStrArrSelection() {
+		HashSet<String> chkSet = new HashSet<String>(size());
+		for(Map.Entry<String, FileAttrs> e: entrySet())
+			if(e.getValue().isChecked()) chkSet.add(new File(dirName, e.getKey()).getPath());
+		String[] selection = new String[chkSet.size()];
+		chkSet.toArray(selection);
+		if(d.ll(4)) d.l("" + selection.length + " items preselected");
+		return selection;
+	}
+
+	/**
+	 * get selected files
+	 *
+	 * @return File array
+	 */
+	File[] getArrSelection() {
+		HashSet<File> chkSet = new HashSet<File>(size());
+		for(Map.Entry<String, FileAttrs> e: entrySet())
+			if(e.getValue().isChecked()) chkSet.add(new File(dirName, e.getKey()));
+		File[] selection = new File[chkSet.size()];
+		chkSet.toArray(selection);
+		if(d.ll(4)) d.l("" + selection.length + " items preselected");
+		return selection;
+	}
+
+	/**
+	 * get file array of all files in file list
+	 *
+	 * @return File array
+	 */
+	File[] getAll() {
+		HashSet<File> fSet = new HashSet<File>(size());
+		for(String fn: keySet()) fSet.add(new File(dirName, fn));
+		File[] all = new File[fSet.size()];
+		fSet.toArray(all);
+		return all;
+	}
+
+	/**
+	 * select files in file list according to string array of file names
+	 *
+	 * @param selection the selection
+	 */
+	void setStrArrSelection(String[] selection) {
+		for(String path: selection) {
+			final String fn = path.substring(path.lastIndexOf('/') + 1);    // basename
+			if(get(fn) != null) put(fn, get(fn).setChecked(true));
+		}
+	}
+
+	/**
+	 * select/unselect file on position in file list
+	 *
+	 * @param pos     int: position in file list
+	 * @param checked boolean: true=select
+	 */
+	void setSelection(int pos, boolean checked) { setStrSelection(getFnByPos(pos), checked); }
+
+	/**
+	 * select/unselect file on with file name fn
+	 *
+	 * @param fn      String: file name of file
+	 * @param checked boolean: true=select
+	 */
+	void setStrSelection(String fn, boolean checked) { put(fn, get(fn).setChecked(checked)); }
+
+	/**
+	 * select all files in file list
+	 */
+	void selectAll() { for(String fn: keySet()) setStrSelection(fn, true); }
+
+	/**
+	 * unselect all files in file list
+	 */
+	void clearSelected() { for(String fn: keySet()) setStrSelection(fn, false);  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FileListAdapter.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,59 @@
+package hh.dejsem.fm;
+
+import android.text.format.Formatter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+import hh.dejsem.R;
+import hh.lib.D;
+
+/**
+ * adapter for file list display
+ */
+class FileListAdapter extends BaseAdapter {
+
+	FileList fileList;      // seznam souborů ve formě TreeMap<String, FileAttrs>
+
+	private D d;
+	private DateFormat df;
+
+	FileListAdapter(D d, FileList fileList) {
+		this.d = d.klon(this);
+		this.fileList = fileList;
+		df = android.text.format.DateFormat.getDateFormat(this.d.getContext());
+	}
+
+	public int getCount() { return fileList.ls.length; }
+
+	public Object getItem(int position) { return null; }
+
+	public long getItemId(int position) { return position; }
+
+	public View getView(int position, View listEntry, ViewGroup parent) {
+		LinearLayout v = (LinearLayout) LayoutInflater.from(d.getContext()).inflate(R.layout.file_entry, null);
+		v.setId(position);
+		final FileAttrs attrs = fileList.get(fileList.ls[position]);
+		TextView fname = (TextView)(v.findViewById(R.id.fname));
+		fname.setText(attrs.name);
+		final CheckBox chkbox = (CheckBox)(v.findViewById(R.id.fcb));
+		chkbox.setChecked(attrs.isChecked());
+		attrs.cb = chkbox;
+		final ImageView icon = (ImageView)(v.findViewById(R.id.fic));
+		if(attrs.isDir())  icon.setImageResource(R.drawable.ic_folder);
+		else icon.setImageResource(R.drawable.ic_file);
+		final TextView fsize = (TextView)(v.findViewById(R.id.fsize));
+		fsize.setText(attrs.isFile() ? Formatter.formatFileSize(d.getContext(), attrs.size) : "");
+		final TextView fdate = (TextView)(v.findViewById(R.id.fdate));
+		fdate.setText("" + df.format(new Date(attrs.date)));
+		return v;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FragFM.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,115 @@
+package hh.dejsem.fm;
+
+import android.os.Bundle;
+import android.os.Message;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import java.io.File;
+
+import hh.dejsem.K;
+import hh.dejsem.Prefs;
+import hh.dejsem.net.NetChOps;
+import hh.dejsem.net.FileIO;
+import hh.lib.D;
+import hh.lib.DF;
+
+/**
+ * Fragment for File Manager operations
+ */
+public class FragFM extends DF {
+
+	static FragFM instantiate(D d, FragFM f) {
+		// jemná nuance:
+		// new D() se odstřihne od prefixu callera (ale musí se přenést activity)
+		// d.klon() prefix převezme
+//		f.d = new D(f);
+//		f.d.setAct(d.getAct());
+		f.d = d.klon(f);
+		return f;
+	}
+
+	public static void refreshNotify(boolean localFS) {
+		final Class baseClass = localFS ? PanelLocalDir.class : PanelServerDir.class;
+		final PanelDirView panel = localFS ? localDirPanel : remoteDirPanel;
+		panel.refreshNotify();
+		if(dialogDirPanel != null && baseClass.isInstance(dialogDirPanel)) dialogDirPanel.refreshNotify();
+	}
+
+	static final String TITLETEXT = "files";
+	static PanelDirView localDirPanel, remoteDirPanel, dialogDirPanel;
+	LinearLayout fmLayout;
+	int netUsage = 0;   // <== NEW
+
+	@Override
+	public void onCreate(Bundle b) {
+		super.onCreate(b);
+	}
+
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+		super.onCreateView(inflater, container, savedInstanceState);
+		return fmLayout;
+	}
+
+	@Override
+	public void onResume() {
+		super.onResume();
+		setUserVisibleHint(false);
+		K.title.setText(TITLETEXT).update();
+	}
+
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		sweepOutNetCmd(d);
+	}
+
+	@Override
+	public boolean handleMessage(Message msg) {
+		if (d.ll(4)) d.l("handle message in FragFM, what=" + msg.what);
+		switch(msg.what) {
+			case K.MSG_TEST:
+				if(d.ll(5)) d.l("TEST from svc");
+				return true;
+			case K.MSG_SVC_ABEND:
+				if(d.ll(5)) d.l("ABEND from svc");
+				d.abendMsg((String)msg.obj, null);
+				return true;
+		}
+		return super.handleMessage(msg);
+	}
+
+	static String getCachedCwdName(D d, String key) {
+		return Prefs.sp.getString(key, null);
+	}
+
+	static String getCachedCwdName(D d, File cacheFile) throws Exception {
+		String cwd = null;
+		FileIO fio = null;
+		if(cacheFile.exists() && (fio = new FileIO(d, cacheFile, "r")) != null) {
+			try { cwd = NetChOps.getStr(d, fio); }
+			catch(Exception e) { throw new Exception("get cached cwdFl name", e); }
+			finally { fio.close(); } }
+		return cwd;
+	}
+
+//	synchronized void netUsageIncr() { netUsage++; netInUse = true; };
+
+//	synchronized void netUsageDecr() { if(netUsage > 0) netUsage--; netInUse = (netUsage > 0); };
+
+	static void sweepOutNetCmd(D d) {
+//		netUsageDecr();
+//		if(!netInUse) d.closeCmdConn();
+		K.cmdConnClose(d);
+	}
+}
+
+
+
+
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FragFMPeer.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,86 @@
+package hh.dejsem.fm;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+import hh.dejsem.K;
+import hh.dejsem.Prefs;
+import hh.dejsem.R;
+import hh.lib.D;
+
+/**
+ * fragment for local file manager focused on PEER operations on one-panel layout
+ */
+public class FragFMPeer extends FragFM {
+
+	public static FragFMPeer instantiate(D d) { return (FragFMPeer)FragFM.instantiate(d, new FragFMPeer()); }
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		d.actMsgr = new Messenger(new Handler(this));   // zachytíme žádosti o refresh dir listu
+
+		fmLayout = (LinearLayout)LayoutInflater.from(d.getContext()).inflate(R.layout.peer_file_manager, null);
+		try {
+			final String cachedCwdName = FragFM.getCachedCwdName(d, K.CACHED_LOC_CWD_KEY);
+			final String locCwdName = (cachedCwdName != null ? cachedCwdName : Prefs.homeDir);
+			String[] selection = new String[0];
+			int listPosition = 0;
+			if(savedInstanceState != null) {
+				if(savedInstanceState.containsKey(K.FM_LOC_SAVED_SELECTION_KEY))
+					selection = savedInstanceState.getStringArray(K.FM_LOC_SAVED_SELECTION_KEY);
+				if(savedInstanceState.containsKey(K.FM_LOC_SAVED_POSITION_KEY))
+					listPosition = savedInstanceState.getInt(K.FM_LOC_SAVED_POSITION_KEY);
+			}
+			localDirPanel = new PanelPeerDir(d, (RelativeLayout)fmLayout.findViewById(R.id.local), locCwdName);
+			localDirPanel.fm.setStrArrSelection(selection);
+			localDirPanel.fm.setCurrListPos(listPosition);
+			if(d.ll(4)) d.l("created local cwd filelist " + localDirPanel.fm.getCwdName());
+		}
+		catch(Exception e) { d.abendMsg("initiating dir lists", e); }
+	}
+
+	@Override
+	public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+		super.onCreateContextMenu(menu, v, menuInfo);
+		localDirPanel.buildContextMenu(menu, v, menuInfo);
+	}
+
+	@Override
+	public boolean onContextItemSelected(MenuItem item) {
+		if(d.ll(4)) d.l(String.format("context menu item selected=%s", item.getTitle()));
+		if(localDirPanel.handleContextItemSelected(item)) return true;
+		return super.onContextItemSelected(item);
+	}
+
+	@Override
+	public void onSaveInstanceState(Bundle b) {
+		super.onSaveInstanceState(b);
+		b.putStringArray(K.FM_LOC_SAVED_SELECTION_KEY, localDirPanel.fm.getStrArrSelection());
+		b.putInt(K.FM_LOC_SAVED_POSITION_KEY, localDirPanel.fm.getCurrListPos());
+	}
+
+	@Override
+	public boolean handleMessage(Message msg) {
+		if (d.ll(4)) d.l("handle message in FragFMPeer, what=" + msg.what);
+		if(msg.what == K.MSG_REFRESH_LOC) {
+			localDirPanel.refreshDirList();
+			return true;
+		}
+		return super.handleMessage(msg);
+	}
+
+	/*@Override
+	public void onDestroy() {
+		super.onDestroy();
+		NetUDP.go = false;  // potlačení případných UDP-broadcastů
+	}*/
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FragFMServer.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,109 @@
+package hh.dejsem.fm;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+import hh.dejsem.K;
+import hh.dejsem.Prefs;
+import hh.dejsem.R;
+import hh.lib.D;
+
+/**
+ * File Manager focused on both local and server FS maintained on 2-panel layout
+ */
+public class FragFMServer extends FragFM {
+
+	public static FragFMServer instantiate(D d) { return (FragFMServer) FragFM.instantiate(d, new FragFMServer()); }
+
+	PanelDirView actualDirPanel;
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		d.actMsgr = new Messenger(new Handler(this));   // zachytíme žádosti o refresh dir listu
+
+		fmLayout = (LinearLayout)LayoutInflater.from(d.getContext()).inflate(R.layout.server_file_manager, null);
+		try {
+			String s;
+			s = FragFM.getCachedCwdName(d, K.CACHED_LOC_CWD_KEY);
+			String locCwdName = (s == null ? Prefs.homeDir : s);
+			s = FragFM.getCachedCwdName(d, K.cacheFileR);
+			String remCwdName = (s == null ? "" : s);
+			String[] locSelection = new String[0];
+			String[] srvSelection = new String[0];
+			int locListPosition = 0;
+			int srvListPosition = 0;
+			if (savedInstanceState != null) {
+				if(savedInstanceState.containsKey(K.FM_LOC_SAVED_SELECTION_KEY))
+					locSelection = savedInstanceState.getStringArray(K.FM_LOC_SAVED_SELECTION_KEY);
+				if(savedInstanceState.containsKey(K.FM_LOC_SAVED_POSITION_KEY))
+					locListPosition = savedInstanceState.getInt(K.FM_LOC_SAVED_POSITION_KEY);
+				if(savedInstanceState.containsKey(K.FM_SRV_SAVED_SELECTION_KEY))
+					srvSelection = savedInstanceState.getStringArray(K.FM_SRV_SAVED_SELECTION_KEY);
+				if(savedInstanceState.containsKey(K.FM_SRV_SAVED_POSITION_KEY))
+					srvListPosition = savedInstanceState.getInt(K.FM_SRV_SAVED_POSITION_KEY);
+			}
+			remoteDirPanel = new PanelServerDir(d, (RelativeLayout)fmLayout.findViewById(R.id.remote), "server", remCwdName);
+			remoteDirPanel.fm.setStrArrSelection(srvSelection);
+			remoteDirPanel.fm.setCurrListPos(srvListPosition);
+
+			localDirPanel = new PanelLocalDir(d, (RelativeLayout)fmLayout.findViewById(R.id.local), "local", locCwdName);
+			localDirPanel.fm.setStrArrSelection(locSelection);
+			localDirPanel.fm.setCurrListPos(locListPosition);
+
+			remoteDirPanel.otherSidePanel = localDirPanel;
+			localDirPanel.otherSidePanel = remoteDirPanel;
+
+			if(d.ll(4)) d.l(String.format("created, remote cwd=%s, local cwd=%s",
+					remoteDirPanel.fm.getCwdName(), localDirPanel.fm.getCwdName()));
+		}
+		catch(Exception e) { d.abendMsg("initiating dir lists", e); }
+	}
+
+	@Override
+	public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+		super.onCreateContextMenu(menu, v, menuInfo);
+		if((Integer)v.getTag() == K.LOC_FS) actualDirPanel = localDirPanel;
+		else actualDirPanel = remoteDirPanel;
+		actualDirPanel.buildContextMenu(menu, v, menuInfo);
+		setUserVisibleHint(true);
+	}
+
+	@Override
+	public boolean onContextItemSelected(MenuItem item) {
+		if(d.ll(4)) d.l(String.format("context menu item selected=%s", item.getTitle()));
+		if(actualDirPanel.handleContextItemSelected(item)) return true;
+		return super.onContextItemSelected(item);
+	}
+
+	@Override
+	public void onSaveInstanceState(Bundle b) {
+		super.onSaveInstanceState(b);
+		b.putStringArray(K.FM_LOC_SAVED_SELECTION_KEY, localDirPanel.fm.getStrArrSelection());
+		b.putInt(K.FM_LOC_SAVED_POSITION_KEY, localDirPanel.fm.getCurrListPos());
+		b.putStringArray(K.FM_SRV_SAVED_SELECTION_KEY, remoteDirPanel.fm.getStrArrSelection());
+		b.putInt(K.FM_SRV_SAVED_POSITION_KEY, remoteDirPanel.fm.getCurrListPos());
+	}
+
+	@Override
+	public boolean handleMessage(Message msg) {
+		if (d.ll(4)) d.l("handle message in FragFMServer, what=" + msg.what);
+		switch(msg.what) {
+			case K.MSG_REFRESH_LOC:
+				localDirPanel.refreshDirList();
+				return true;
+			case K.MSG_REFRESH_SRV:
+				remoteDirPanel.refreshDirList();
+				return true;
+		}
+		return super.handleMessage(msg);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/NetTaskRef.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,35 @@
+package hh.dejsem.fm;
+
+import android.os.Bundle;
+
+import hh.dejsem.K;
+import hh.lib.D;
+
+/**
+ * ----------------------------------------------------------
+ * control data of file transfer task
+ * ----------------------------------------------------------
+ */
+public class NetTaskRef {
+
+	public D d;
+	private Bundle args;
+	private TaskProgress progress;
+	public boolean failedTimeStamp = false;    // bug in setLastModified()
+	public boolean go;                         // task is not cancelled
+
+	NetTaskRef(D d, Bundle args, EntitySize es) {
+		this.d = d.klon(this);
+		progress = (es != null ? new TaskProgress(this.d, es) : new TaskProgress(this.d));
+		if(this.d.ll(4)) this.d.l(String.format("new task ref, fn=%s, context=%s, msgr=%s",
+				progress.es.getFn(), this.d.getContext(), this.d.actMsgr));
+		this.args = args;
+		go = true;
+	}
+
+	int getPort() { return args.getInt(K.PORT_KEY); }
+
+	public Bundle getArgs() { return args; }
+
+	public TaskProgress getProgress() { return progress; }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelDirView.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,597 @@
+package hh.dejsem.fm;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.text.format.Formatter;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.HorizontalScrollView;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URLConnection;
+import java.util.ArrayList;
+
+import hh.dejsem.FlatButton;
+import hh.dejsem.K;
+import hh.dejsem.Prefs;
+import hh.dejsem.R;
+import hh.dejsem.SendUriNG;
+import hh.dejsem.hack.HackFS;
+import hh.dejsem.hack.HackNotifListAct;
+import hh.dejsem.hack.HackUDP;
+import hh.dejsem.net.NetConn;
+import hh.dejsem.net.PullFromPeer;
+import hh.dejsem.net.PushToPeer;
+import hh.dejsem.net.SrvCmd;
+import hh.lib.D;
+import hh.lib.NoticeDialogFragment;
+
+/**
+ * screen panel for operations on directory list view
+ * <p>
+ *     ● drží hlavně menu operací a metody, které je spouštějí
+ * <p>
+ *     ● vnořená třída DirListView drží subviews (záhlaví a ListView)
+ * <p>
+ *     ● panely se v okně zobrazují jeden (lokální FS) nebo dva (lokální a serverový FS)
+ *     a dědí je třídy PanelServerDir, PanelServerMoveDest, PanelLocalDir, PanelLocalMoveDest, PanelPeerDir
+ */
+class PanelDirView {
+
+	/**
+	 * directory list to be displayed in screen panel
+	 */
+	static class DirListView {
+
+		D d;
+		PanelDirView panel;
+		final int dirNameColor;
+		final float dirNameSize;
+		final ImageView up, menu;
+		final HorizontalScrollView dirNameScroll;
+		final LinearLayout dirNameView;
+		final ListView dirListView;
+		final int whichFS;
+		String path;
+		final View.OnClickListener goToClick = new View.OnClickListener() { public void onClick(View v) { panel.handleGoToDir(v); } };
+		final View.OnClickListener upClick = new View.OnClickListener() { public void onClick(View v) { panel.handleDirUp(); } };
+		final View.OnClickListener menuClick = new View.OnClickListener() { public void onClick(View v) { openContextMenu(); } };
+		final AdapterView.OnItemClickListener itemClick = new AdapterView.OnItemClickListener() {
+			public void onItemClick(AdapterView<?> parent, View v, int pos, long id) { panel.handleClickedListEntry(pos); }
+		};
+
+		static RelativeLayout getLayout(Context c) { return (RelativeLayout) LayoutInflater.from(c).inflate(R.layout.dir_view_layout, null); }
+
+		DirListView(PanelDirView panel, RelativeLayout dirViewContainer, String dirViewLabel, int whichFS) {
+			this.d = panel.d.klon(this);
+			this.panel = panel;
+			this.whichFS = whichFS;
+
+			up = (ImageView)dirViewContainer.findViewById(R.id.up);
+			dirNameScroll = (HorizontalScrollView)dirViewContainer.findViewById(R.id.scroll);
+			dirNameView = (LinearLayout)dirViewContainer.findViewById(R.id.dirname);
+			dirNameColor = d.getContext().getResources().getColor(R.color.dir_name_color);
+			dirNameSize = d.getContext().getResources().getDimension(R.dimen.dir_name_size);
+			((TextView)dirViewContainer.findViewById(R.id.site)).setText(dirViewLabel);
+			menu = (ImageView)dirViewContainer.findViewById(R.id.menu);
+			dirListView = (ListView)dirViewContainer.findViewById(R.id.dir_list_view);
+		}
+
+		void setUpDirView() {
+			up.setOnClickListener(upClick);
+			d.getFragment().registerForContextMenu(dirListView);
+			dirListView.setTag(whichFS);  // local or remote FM
+			menu.setOnClickListener(menuClick);
+			d.getFragment().registerForContextMenu(menu);
+			menu.setTag(whichFS);         // local or remote FM
+			dirListView.setOnItemClickListener(itemClick);
+			updateDirView();
+		}
+
+		void updateDirView() {
+			assembleDirname();
+			dirListView.setAdapter(panel.fm.getListAdapter());
+			dirListView.setSelection(panel.fm.cwdFl.listPos);
+		}
+
+		void assembleDirname() {
+			final String dirname = panel.fm.getCwdName();
+			if(d.ll(5)) d.l(String.format("dirname=%s", dirname));
+			dirNameView.removeAllViews();
+			if(dirname.length() > 0) {
+				path = "";
+				setNameButton("/");
+				for(String name : dirname.split("/")) setNameButton(name);
+				dirNameScroll.post(new Runnable() { public void run() { dirNameScroll.fullScroll(View.FOCUS_RIGHT); } });
+			}
+		}
+
+		void setNameButton(String name) {
+			if(name.equals("")) return;
+			if(path.equals("/") || name.equals("/")) path += name;
+			else path += "/" + name;
+			FlatButton dirNameElement = new FlatButton(d.getContext());
+			dirNameElement.setTag(path);
+			dirNameElement.setColor(dirNameColor);
+			dirNameElement.setSize(dirNameSize);
+			dirNameElement.setText(name.equals("/") ? " / " : name);
+			dirNameElement.setOnClickListener(goToClick);
+			dirNameView.addView(dirNameElement);
+			if(d.ll(5)) d.l(String.format("element name=%s, path=%s", name, path));
+		}
+
+		void openContextMenu() {
+			d.l(4,"openContextMenu");
+			if(!K.blocked()) d.getAct().openContextMenu(menu); }
+	}
+
+	/**
+	 * výčet menu entries, ze kterých se setavují menu jednotlivých panelů
+	 */
+	enum PanelMenuItem {
+		SelectAll("select all") { void action(PanelDirView p) { p.selectAll(); } },
+		ClearSelection("clear selection") { void action(PanelDirView p) { p.clearSelected(); } },
+		RefreshDirList("refresh dir list") { void action(PanelDirView p) { p.handleRefresh(); } },
+		GoHome("go to home dir") { void action(PanelDirView p) { p.handleGoHome(); } },
+		SizeOfSelection("size of selection") { void action(PanelDirView p) { p.handleSizeOf(); } },
+		CanWrite("can write to selected") { void action(PanelDirView p) { p.handleCanWrite(); } },
+		ServerCopy("server copy selected") { void action(PanelDirView p) { p.handleServerCopy(); } },
+		CopyToPeer("copy to peer") { void action(PanelDirView p) { p.handlePushToPeer(); } },
+		CopyFromPeer("pull from peer") { void action(PanelDirView p) { p.handlePullFromPeer(); } },
+		Move("move selected") { void action(PanelDirView p) { p.handleMove(); } },
+		Share("share") { void action(PanelDirView p) { p.handleShare(); } },
+		Expose("expose to http") { void action(PanelDirView p) { p.handleExpose(); } },
+		Hide("hide") { void action(PanelDirView p) { p.handleHideReference(); } },
+		ExposeUp("upload & expose to http") { void action(PanelDirView p) { p.handleExpose(); } },
+		Delete("delete selected") { void action(PanelDirView p) { p.handleDelete(); } },
+		Rename("rename") { void action(PanelDirView p) { p.handleRename(); } },
+		CreateDir("create diretory") { void action(PanelDirView p) { p.handleCreateDir(); } },
+		SetHomeDir("set current dir as home") { void action(PanelDirView p) { p.handleSetHome(); } },
+		FreeSpace("display free space") { void action(PanelDirView p) { p.handleFree(); } },
+		HackUDP("fm UDP hack") { void action(PanelDirView p) { p.handleTestUDP(); } },
+		HackSDcard("fm SD Card hack") { void action(PanelDirView p) { p.handleTestSDcard(); } },
+		HackFS("fm FS hack") { void action(PanelDirView p) { p.handleTestFS(); } },
+		HackNotif("notification hack") { void action(PanelDirView p) { p.handleTestNotif(); } },
+		HackSMS("SMS hack") { void action(PanelDirView p) { p.handleTestUri("dejsem/02/image.8630.jpeg"); } },
+		OrderNameAsc("by name asc...") { void action(PanelDirView p) { p.handleOrder(K.DirListOrder.NAME_ASC); } },
+		OrderNameDesc("by name desc...") { void action(PanelDirView p) { p.handleOrder(K.DirListOrder.NAME_DESC); } },
+		OrderSizeAsc("by size asc...") { void action(PanelDirView p) { p.handleOrder(K.DirListOrder.SIZE_ASC); } },
+		OrderSizeDesc("by size desc...") { void action(PanelDirView p) { p.handleOrder(K.DirListOrder.SIZE_DESC); } },
+		OrderDateAsc("by date asc...") { void action(PanelDirView p) { p.handleOrder(K.DirListOrder.DATE_ASC); } },
+		OrderDateDesc("by date desc...") { void action(PanelDirView p) { p.handleOrder(K.DirListOrder.DATE_DESC); } };
+
+		final String title;
+		void action(PanelDirView panel) {};
+
+		PanelMenuItem() { title = name(); }
+		PanelMenuItem(String n) { title = n; }
+	}
+
+	D d;
+	FM fm;                              // file management methods for panel
+	DirListView dirView;                // view on file list
+	PanelDirView otherSidePanel = null; // panel for other local/server file system
+	ArrayList<PanelMenuItem> items = new ArrayList<PanelMenuItem>();
+
+	PanelDirView(D d) {
+		this.d = d.klon(this);
+	}
+
+	void refreshDirList() {
+		fm.refreshFileList();
+		notifyListView();
+	}
+
+	void setUpDirView() { dirView.setUpDirView(); }
+
+	void updateDirView() { dirView.updateDirView(); }
+
+	void handleClickedListEntry(int pos) {
+		fm.setCurrListPos(pos);
+		String fn = fm.getFnByPos(pos);
+		String fp = fm.fullPath(fn);
+		d.l(4, String.format("on list entry click: position=%d, fp=%s, context=%s", pos, fp, d.getContext()));
+		if(fm.isFile(fn)) {     // open file
+			handleClickedFile(fn);
+		}
+		else if(!K.blocked()) {     // open dir
+			fm.deleteCache(fp);
+			fm.stackAndEnterDir(fp);
+			updateDirView();
+		}
+		FragFM.sweepOutNetCmd(d);
+	}
+
+	void handleClickedFile(String fp) {}
+
+	void addMenuItem(Menu menu, PanelMenuItem i) { addMenuItem(menu, i, i.title); }
+
+	void addMenuItem(Menu menu, PanelMenuItem i, String title) { menu.add(Menu.NONE, i.ordinal(), Menu.NONE, title); }
+
+	Menu addSubMenu(Menu menu, String title) {
+		Menu m = menu.addSubMenu(Menu.NONE, 9999, Menu.NONE, title);
+		return m;
+	}
+
+	public void buildContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+		Menu subMenu, subSubMenu;
+
+		if(menuInfo != null)
+			fm.setSelection(((AdapterView.AdapterContextMenuInfo) menuInfo).position);
+		for(PanelMenuItem p: PanelMenuItem.values()) items.add(p.ordinal(), p);
+		d.l(4, "buildContextMenu");
+
+		addMenuItem(menu, PanelMenuItem.RefreshDirList);
+		if(PanelLocalDir.class.isInstance(this)) addMenuItem(menu, PanelMenuItem.GoHome);
+
+		subMenu = addSubMenu(menu, "selection");
+		addMenuItem(subMenu, PanelMenuItem.SelectAll);
+		addMenuItem(subMenu, PanelMenuItem.ClearSelection);
+
+		subMenu = addSubMenu(menu, "get info");
+		addMenuItem(subMenu, PanelMenuItem.SizeOfSelection);
+		addMenuItem(subMenu, PanelMenuItem.FreeSpace);
+		addMenuItem(subMenu, PanelMenuItem.CanWrite);
+
+		subMenu = addSubMenu(menu, "directory list");
+		addMenuItem(subMenu, PanelMenuItem.RefreshDirList);
+		if(PanelLocalDir.class.isInstance(this)) {
+			addMenuItem(subMenu, PanelMenuItem.GoHome);
+			addMenuItem(subMenu, PanelMenuItem.SetHomeDir);
+		}
+		subMenu = addSubMenu(subMenu, "reorder list");
+		addMenuItem(subMenu, PanelMenuItem.OrderNameAsc);
+		addMenuItem(subMenu, PanelMenuItem.OrderNameDesc);
+		addMenuItem(subMenu, PanelMenuItem.OrderSizeAsc);
+		addMenuItem(subMenu, PanelMenuItem.OrderSizeDesc);
+		addMenuItem(subMenu, PanelMenuItem.OrderDateAsc);
+		addMenuItem(subMenu, PanelMenuItem.OrderDateDesc);
+
+		subMenu = addSubMenu(menu, "file operations");
+		if(PanelPeerDir.class.isInstance(this)) {
+			addMenuItem(subMenu, PanelMenuItem.CopyToPeer);
+			addMenuItem(subMenu, PanelMenuItem.CopyFromPeer);
+			addMenuItem(subMenu, PanelMenuItem.Move);
+			addMenuItem(subMenu, PanelMenuItem.Delete);
+			addMenuItem(subMenu, PanelMenuItem.Rename);
+			addMenuItem(subMenu, PanelMenuItem.Share);
+			addMenuItem(subMenu, PanelMenuItem.ExposeUp);
+			addMenuItem(subMenu, PanelMenuItem.CreateDir);
+		}
+		else if(PanelServerDir.class.isInstance(this)) {
+			addMenuItem(subMenu, PanelMenuItem.ServerCopy, "download selected");
+			addMenuItem(subMenu, PanelMenuItem.Move);
+			addMenuItem(subMenu, PanelMenuItem.Delete);
+			addMenuItem(subMenu, PanelMenuItem.Rename);
+			addMenuItem(subMenu, PanelMenuItem.Expose);
+			addMenuItem(subMenu, PanelMenuItem.Hide);
+			addMenuItem(subMenu, PanelMenuItem.CreateDir);
+		}
+		else if(PanelLocalDir.class.isInstance(this)) {
+			addMenuItem(subMenu, PanelMenuItem.ServerCopy, "upload selected");
+			addMenuItem(subMenu, PanelMenuItem.Move);
+			addMenuItem(subMenu, PanelMenuItem.Delete);
+			addMenuItem(subMenu, PanelMenuItem.Rename);
+			addMenuItem(subMenu, PanelMenuItem.Share);
+			addMenuItem(subMenu, PanelMenuItem.ExposeUp);
+			addMenuItem(subMenu, PanelMenuItem.CreateDir);
+		}
+
+		subMenu = addSubMenu(menu, "hack");
+		addMenuItem(subMenu, PanelMenuItem.HackUDP);
+		addMenuItem(subMenu, PanelMenuItem.HackSDcard);
+		addMenuItem(subMenu, PanelMenuItem.HackFS);
+		addMenuItem(subMenu, PanelMenuItem.HackNotif);
+		addMenuItem(subMenu, PanelMenuItem.HackSMS);
+	}
+
+	public boolean handleContextItemSelected(MenuItem item) {
+		d.l(4, "handling selected context menu item ...................................");    // log eye catcher
+		if(d.ll(2)) d.l(String.format("menu item selected id=%d, title=%s", item.getItemId(), item.getTitle()));
+		if(item.getItemId() < items.size()) {
+			items.get(item.getItemId()).action(this);
+			FragFM.sweepOutNetCmd(d);
+			return true;
+		}
+		return false;
+	}
+
+	void handleDirUp() {
+		if(!K.blocked()) {
+			fm.goUp();
+			FragFM.sweepOutNetCmd(d);
+			updateDirView();
+		}
+	}
+
+	void handleGoToDir(View v) {
+		if(!K.blocked()) {
+			fm.stackAndEnterDir((String)(v.getTag()));
+			FragFM.sweepOutNetCmd(d);
+			updateDirView();
+		}
+	}
+
+	void handleGoHome() {
+		if(!K.blocked()) {
+			fm.stackAndEnterDir((String)(Prefs.homeDir));
+			FragFM.sweepOutNetCmd(d);
+			updateDirView();
+		}
+	}
+
+	void handleRefresh() {
+		if(!K.blocked()) {
+			refreshDirList();
+			FragFM.sweepOutNetCmd(d);
+		}
+	}
+
+	void notifyListView() { ((BaseAdapter) dirView.dirListView.getAdapter()).notifyDataSetChanged(); }
+
+	void handOverServerCopy(NetTaskRef netTaskRef) {}
+
+	void clearSelected() {
+		fm.clearSelected();
+		notifyListView();
+	}
+
+	void selectAll() {
+		fm.cwdFl.selectAll();
+		notifyListView();
+	}
+
+	void handleTestUDP() {
+		d.l(4, "handling fm UDP hack...");
+		d.getAct().startActivity(new Intent(d.getContext(), HackUDP.class));
+	}
+
+	void handleTestSDcard() {
+		d.l(4, "handling fm SD Card hack...");
+		try { FragFM.localDirPanel.fm.doCreateDir("5/5/5/5/5/5/5/5/5/5/5/5/5/5/5/5/5/5/5/5"); }
+		catch(Exception e) { d.lmwa(e.getMessage()); }
+	}
+
+	/**
+	 * Zkouška chování FS.
+	 */
+	void handleTestFS() { new HackFS(d).caselessFS(); }
+
+	void handleTestNotif() {
+		d.l(4, "handling notification hack...");
+		final Context c = this.d.getContext();
+		NotificationManager notifManager = (NotificationManager)c.getSystemService(Context.NOTIFICATION_SERVICE);
+		final Intent notifIntent = new Intent(c, HackNotifListAct.class);
+		final Intent deleteIntent = new Intent(c, HackNotifListAct.class).putExtra(K.ACTION_KEY, K.TRANSFER_PIN_NOTIFICATION);
+		Notification.Builder notifBuilder = new Notification.Builder(c)
+				.setSmallIcon(R.drawable.ic_launcher)
+				.setContentTitle("notification test")
+				.setContentIntent(PendingIntent.getActivity(c, 0, notifIntent, PendingIntent.FLAG_UPDATE_CURRENT))
+				.setDeleteIntent(PendingIntent.getActivity(c, 1, deleteIntent, 0));
+		notifManager.notify(42, notifBuilder.build());
+	}
+
+	void handleTestUri(String uriPath) { SendUriNG.decideSendUri(uriPath); }
+
+	void handleSizeOf() {
+		try {
+			final EntitySize es = fm.determineSizeOf();
+			String msg = String.format("%s\nfiles: %d\ndirs: %d",
+					Formatter.formatFileSize(d.getContext(), es.getTargetSize()), es.getTargetFnum(), es.getTargetDnum());
+			new NoticeDialogFragment().display(d, "size of selection", msg, null);
+			FragFM.sweepOutNetCmd(d);
+		}
+		catch(Exception n) { d.abendMsg("determine size of section", n); }
+	}
+
+	void handleServerCopy() {
+		try {
+			if(!NetConn.netAvailable(d)) return;
+			d.l(4, "handleServerCopy, START");
+			String[] selection = fm.getStrArrSelection();
+			handOverServerCopy(new NetTaskRef(d, serverCopyParms(selection), fm.reckon(selection)));
+			K.title.update();
+			d.l(4, "handleServerCopy, HANDED over to background");
+		}
+		catch(K.NothingSelected n) { d.lmwa(n.getMessage()); }
+		catch(K.AllPortsBusy n) { d.lmwa(n.getMessage()); }
+		catch(Exception n) { d.abendMsg("copy to/from server", n); }
+	}
+
+	/**
+	 * Připravuje parametry pro kopírování souborů z/na server.</br>
+	 * ● u serveru objedná port pro kopírování na pozadí</br>
+	 * ● do parametrů uloží port, pole se jmény vybraných souborů, jméno cílového adresáře</br>
+	 * ● zaktivuje sledování průběhu přenosu
+	 *
+	 * @param selection the selection
+	 * @return the bundle
+	 * @throws Exception the exception
+	 */
+	Bundle serverCopyParms(String[] selection) throws Exception {
+		if(selection.length == 0) throw new K.NothingSelected();
+		final int port = (Integer) SrvCmd.LONGTASK.exec(d, null);
+		FragFM.sweepOutNetCmd(d);
+		if(port == 0) throw new K.AllPortsBusy();
+		fm.clearSelected();
+		final String to = otherSidePanel.fm.cwdFl.dirName;
+		final Bundle parms = new Bundle();
+		parms.putInt(K.PORT_KEY, port);
+		parms.putStringArray(K.FILES_FROM_KEY, selection);
+		parms.putString(K.FILE_TO_KEY, to);
+		//FragFM.sweepOutNetCmd(d);
+		K.prepTransferOverview(d);
+		return parms;
+	}
+
+	void handlePushToPeer() {
+		try {
+			if(!NetConn.lanAvailable(d)) return;
+			String[] selection = fm.getStrArrSelection();
+			if(selection.length == 0) throw new K.NothingSelected();
+			fm.clearSelected();     // clear selection in file list
+			Bundle parms = new Bundle();
+			parms.putStringArray(K.FILES_FROM_KEY, selection);
+			/*TaskProgress taskProgress = new TaskProgress(d, fm.reckon(selection));*/
+			K.prepTransferOverview(d);
+			new PushToPeer(d, new NetTaskRef(d, parms, fm.reckon(selection))).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+		}
+		catch(K.NothingSelected n) { d.lmwa(n.getMessage()); }
+		catch(Exception e) { d.abendMsg("copy to peer", e); }
+	}
+
+	void handlePullFromPeer() {
+		if(!NetConn.lanAvailable(d)) d.abendMsg(new Exception("LAN not available"));
+		else {
+			Bundle parms = new Bundle();
+			parms.putString(K.FILE_TO_KEY, fm.cwdFl.dirName);
+			K.prepTransferOverview(d);
+			try { new PullFromPeer(d, new NetTaskRef(d, parms, null)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }
+			catch(Exception e) { d.abendMsg("copy from peer", e); }
+		}
+	}
+
+	void handleDelete() {
+		final File[] selection = fm.getArrSelection();
+		if(selection.length == 0) return;
+		if(d.ll(4)) d.l("delete " + selection.length + " entries");
+		final String alert = String.format("%d selected items will be recursively deleted", selection.length);
+		new DialogFragmentDelete().approveDelete(this,"", alert);
+	}
+
+	void handleCanWrite() {
+		final File[] selection = fm.getArrSelection();
+		if(selection.length == 0) return;
+		d.l(String.format("file %s, writeable=%b", selection[0].getPath(), selection[0].canWrite()));
+	}
+
+	void handleShare() {
+		if(!NetConn.netAvailable(d)) return;
+		final File[] selection = fm.getArrSelection();
+		if(selection.length == 0) return;
+		final String fn = selection[0].getName();
+
+		final String fp = fm.fullPath(fn);
+		if(d.ll(4)) d.l("share: fp=" + fp);
+		String mimeType = URLConnection.guessContentTypeFromName(fn);
+		if(mimeType == null) mimeType = "application/octet-stream";
+		URI fURI = new File(fp).toURI();
+		Uri uri = new Uri.Builder()
+				.scheme(fURI.getScheme())
+				.encodedAuthority(fURI.getRawAuthority())
+				.encodedPath(fURI.getRawPath())
+				.query(fURI.getRawQuery())
+				.fragment(fURI.getRawFragment())
+				.build();
+		Bundle b = new Bundle();
+		b.putParcelable(Intent.EXTRA_STREAM, uri);
+		Intent i = new Intent(Intent.ACTION_SEND).putExtras(b).setDataAndType(uri, mimeType);
+		d.getContext().startActivity(Intent.createChooser(i, "share file " + fn));
+	}
+
+	/**
+	 * Vystavení vybraného objektu na http-serveru.</br>
+	 * ● expose se nedávkuje, vyřizuje se jen 1.vybraný</br>
+	 * ● lokální objekt se na pozadí překopíruje na server do adresáře vystavených objektů
+	 * ● server vrátí URL, který se umiťuje na clipboard a případně sharuje
+	 */
+	void handleExpose() {
+		if(!NetConn.netAvailable(d)) return;
+		String[] selection = fm.getStrArrSelection();
+		if(selection.length == 0) return;
+		try { expose(selection[0]); } catch(Exception e) { d.abendMsg("expose to http on server", e); }
+	}
+
+	/**
+	 * Zakrytí objektů před http.
+	 */
+	void handleHideReference() {
+		if(!NetConn.netAvailable(d)) return;
+		String[] selection = fm.getStrArrSelection();
+		if(selection.length == 0) return;
+		try { hide(selection); } catch(Exception e) { d.abendMsg("hide from http on server", e); }
+	}
+
+	void expose(String selection) throws Exception {}
+
+	void hide(String[] selection) throws Exception {}
+
+	void handleOrder(K.DirListOrder order) {
+		if(d.ll(4)) d.l("faked ordering");
+	}
+
+	void handleFree() {
+		String path;
+		File[] selection = fm.getArrSelection();
+		if(selection.length == 0) path = fm.getCwdName();
+		else path = selection[0].getPath();
+		if(d.ll(4)) d.l("display free space for " + path);
+		try {
+			long free = fm.getFreeSpace(path);
+			new NoticeDialogFragment().display(d, "free space", Formatter.formatFileSize(d.getContext(), free), null);
+		}
+		catch(Exception e) { d.abendMsg("expose to server", e); }
+	}
+
+	void handleRename() {
+		final File[] selection = fm.getArrSelection();
+		if(selection.length == 0) return;
+		final String from = selection[0].getName();
+		if(d.ll(4)) d.l(String.format("rename, fn=%s", from));
+		new DialogFragmentEntry().enterText(this, "rename", "new name ?", from, K.FM_ACTION_RENAME);
+	}
+
+	void handleCreateDir() {
+		d.l(4,"handleCreateDir");
+		new DialogFragmentEntry().enterText(this, "create directory", "enter name", "", K.FM_ACTION_CREATE);
+	}
+
+	void handleSetHome() {
+		new Prefs().setHome(d.getContext(), fm.cwdFl.dirName);
+	}
+
+
+	void handleMove() {
+		try {
+			if(fm.getArrSelection().length == 0) throw new K.NothingSelected();     // nothing to move
+			if(d.ll(4)) d.l(String.format("prepare move, panel=%s", this));
+			new DialogFragmentMoveDest().moveDest(this, fm.getCwdName());
+		}
+		catch(K.NothingSelected n) { d.lmwa(n.getMessage()); }
+	}
+
+	void doMove(String moveDest) {
+		if(d.ll(4)) d.l(String.format("do move, moveDest=%s", moveDest));
+		try {
+			fm.iterateMove(moveDest);
+			fm.enterDir(moveDest);
+			refreshDirList();
+			updateDirView();
+		}
+		catch(Exception e) { d.abendMsg("moving selection", e); }
+	}
+
+	// void prepTransferOverview() { if (K.transfProgress == null) K.transfProgress = new TransferOverview(d); }   // obsoleted by K.prepTransferOverview()
+
+	void refreshNotify() {
+		fm.refreshFileList();
+		notifyListView();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelLocalDir.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,80 @@
+package hh.dejsem.fm;
+
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.widget.RelativeLayout;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URLConnection;
+
+import hh.dejsem.K;
+import hh.dejsem.net.ExposeFile;
+import hh.dejsem.net.PushToServer;
+import hh.dejsem.net.SrvCmd;
+import hh.lib.D;
+
+/**
+ * panel with local directory list
+ */
+class PanelLocalDir extends PanelDirView {
+
+	PanelLocalDir(D d, RelativeLayout layout, String dirViewLabel, String newWorkDirName) {
+		super(d);
+		dirView = new DirListView(this, layout, dirViewLabel, K.LOC_FS);
+		fm = new FMLocal(d);
+		fm.enterDir(newWorkDirName);
+		setUpDirView();
+	}
+
+	@Override
+	void handOverServerCopy(NetTaskRef netTaskRef) {
+		try { new PushToServer(d, netTaskRef).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }
+		catch(Exception e) { d.abendMsg("copy to server", e); }
+	}
+
+	@Override
+	void handleClickedFile(String fn) {
+		String fp = fm.fullPath(fn);
+		File file = new File(fp);
+		URI fURI = file.toURI();
+		Uri u = new Uri.Builder()
+				.scheme(fURI.getScheme())
+				.encodedAuthority(fURI.getRawAuthority())
+				.encodedPath(fURI.getRawPath())
+				.query(fURI.getRawQuery())
+				.fragment(fURI.getRawFragment())
+				.build();
+		String mimeType = URLConnection.guessContentTypeFromName(fn);
+		if(mimeType == null) mimeType = "application/octet-stream";
+		d.l(4, "fn=" + fn + ", uri=" + u + ", type=" + mimeType);
+		Bundle b = new Bundle();
+		b.putParcelable(Intent.EXTRA_STREAM, u);
+		Intent i = new Intent(Intent.ACTION_VIEW).putExtras(b).setDataAndType(u, mimeType);
+		try { d.getContext().startActivity(i); }
+		catch(ActivityNotFoundException e) { d.getContext().startActivity(Intent.createChooser(i, "open file")); }
+	}
+
+	@Override
+	void expose(String selection) throws Exception {
+		int port = (Integer) SrvCmd.LONGTASK.exec(d, null);
+		FragFM.sweepOutNetCmd(d);
+		if(port == 0) throw new Exception("all server ports busy", null);
+		EntitySize es = fm.reckon(new String[] { selection });
+		Bundle parms = new Bundle();
+		parms.putInt(K.PORT_KEY, port);
+		parms.putString(K.FILES_FROM_KEY, selection);
+		K.prepTransferOverview(d);
+		FragFM.sweepOutNetCmd(d);
+
+		// tady BACHA, operace expose dostane ze serveru URL, který umístí na clipboard;
+		// celé by se to mělo proto nějak serializovat,
+		//  ale jen na samotný expose, s ostatními operacemi expose nekoliduje
+		NetTaskRef netTaskRef = new NetTaskRef(d, parms, es);
+		d.l(4, String.format("netTaskRef=%s", netTaskRef));
+		new ExposeFile(d, netTaskRef).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelLocalMoveDest.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,26 @@
+package hh.dejsem.fm;
+
+import android.view.ContextMenu;
+import android.view.View;
+import android.widget.RelativeLayout;
+
+import hh.dejsem.R;
+import hh.lib.D;
+
+/**
+ * panel to determine move destination in local fm
+ */
+class PanelLocalMoveDest extends PanelLocalDir {
+
+	PanelLocalMoveDest(D d, RelativeLayout layout, String newWorkDirName) {
+		super(d, layout, "move", newWorkDirName);
+	}
+
+	@Override
+	public void buildContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+		d.getAct().getMenuInflater().inflate(R.menu.move_dest, menu);
+	}
+
+	@Override
+	void handleSizeOf() { FragFM.localDirPanel.handleSizeOf(); }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelPeerDir.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,11 @@
+package hh.dejsem.fm;
+
+import android.widget.RelativeLayout;
+
+import hh.lib.D;
+
+class PanelPeerDir extends PanelLocalDir {
+	PanelPeerDir(D d, RelativeLayout layout, String newWorkDirName) {
+		super(d, layout, "local", newWorkDirName);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelServerDir.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,46 @@
+package hh.dejsem.fm;
+
+import android.os.AsyncTask;
+import android.widget.RelativeLayout;
+
+import hh.dejsem.K;
+import hh.dejsem.SendUriNG;
+import hh.dejsem.net.PullFromServer;
+import hh.dejsem.net.SrvCmd;
+import hh.lib.D;
+
+/**
+ * panel with remote directory list
+ */
+class PanelServerDir extends PanelDirView {
+
+	PanelServerDir(D d, RelativeLayout layout, String dirViewLabel, String newWorkDirName) {
+		super(d);
+		dirView = new DirListView(this, layout, dirViewLabel, K.SRV_FS);
+		fm = new FMServer(d);
+		fm.enterDir(newWorkDirName);
+		setUpDirView();
+	}
+
+	@Override
+	void handOverServerCopy(NetTaskRef netTaskRef) {
+		try { new PullFromServer(d, netTaskRef).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }
+		catch(Exception e) { d.abendMsg("copy from server", e); }
+	}
+
+	@Override
+	void expose(String selection) throws Exception {
+		String uriPath = (String)SrvCmd.EXPOSE.exec(d, selection);   // expose se nedávkuje, vyřizuje se jen 1.vybraný
+		FragFM.sweepOutNetCmd(d);
+		SendUriNG.decideSendUri(uriPath);
+	}
+
+	@Override
+	void hide(String[] selection) throws Exception {
+		for(String sel: selection) {
+			SrvCmd.HIDE.exec(d, sel);
+			if(d.ll(4)) d.l(sel + " hiden");
+		}
+		FragFM.sweepOutNetCmd(d);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelServerMoveDest.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,27 @@
+package hh.dejsem.fm;
+
+import android.view.ContextMenu;
+import android.view.View;
+import android.widget.RelativeLayout;
+
+import hh.dejsem.R;
+import hh.lib.D;
+
+/**
+ * panel to determine move destination in remote fm
+ */
+class PanelServerMoveDest extends PanelServerDir {
+
+	PanelServerMoveDest(D d, RelativeLayout layout, String newWorkDirName) {
+		super(d, layout, "move", newWorkDirName);
+	}
+
+	@Override
+	public void buildContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+		d.getAct().getMenuInflater().inflate(R.menu.move_dest, menu);
+		menu.setGroupVisible(R.id.home_dir, false);
+	}
+
+	@Override
+	void handleSizeOf() { FragFM.remoteDirPanel.handleSizeOf(); }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/ProgressMonitor.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,193 @@
+package hh.dejsem.fm;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.util.Stack;
+
+import hh.dejsem.Act;
+import hh.dejsem.K;
+import hh.dejsem.Main;
+import hh.lib.D;
+
+/**
+ * Aktivita sloužící k zobrazení detailů o průběhu déle travajících datových přenosů
+ * <p>
+ * ● aktivita se spouští z notifikace v Notification Bar
+ * <p>
+ * ● informace se zobrazují AlertDialogem jako list of progress bars
+ * <p>
+ * ● klik na cancel icon příslušný přenos bezpodmínečně ukončí
+ */
+public class ProgressMonitor
+		extends Act
+		implements
+			Runnable,
+			DialogInterface.OnClickListener,
+			DialogInterface.OnDismissListener,
+			DialogInterface.OnKeyListener,
+			View.OnClickListener {
+
+	Handler handler = new Handler();
+	ProgressListAdapter listAdapter = null;
+    final String fragmentTitle = "DEJSEM TRANSFER MONITOR";
+	Stack stack = new Stack();
+
+	@Override
+	public void onCreate(Bundle b) {
+		super.onCreate(b);
+		Main.eyeCatcher(d, this);
+		action(getIntent().getIntExtra(K.ACTION_KEY, 0));
+	}
+
+	@Override
+	public void onResume() {
+		super.onResume();
+		if(transferInProgress()) handler.postDelayed(this, K.transfProgress.refreshInterval);
+	}
+
+	@Override
+	public void onPause() {
+		super.onPause();
+		handler.removeCallbacks(this);
+	}
+
+	@Override
+	public void onDestroy() { 
+		super.onDestroy();
+		while(!stack.empty()) ((AlertDialog)stack.pop()).dismiss();
+	}
+
+	@Override
+	public void run() {
+		if(K.transfProgress.transfCnt() == 0) {
+			terminate();
+			return;
+		}
+		listAdapter.notifyDataSetChanged();
+		handler.postDelayed(this, K.transfProgress.refreshInterval);
+	}
+
+	@Override
+	public void onDismiss(DialogInterface dialog) {
+		d.l("+++ onDismiss()");
+		finish();
+	}
+
+	@Override
+	public boolean onKeyDown(int keyCode, KeyEvent event) {
+		if(d.ll(4)) d.l("onKeyDown, keyCode=" + keyCode);
+		if(keyCode == KeyEvent.KEYCODE_BACK) {
+			terminate();
+			return true;
+		}
+		return super.onKeyDown(keyCode, event);
+	}
+
+	@Override
+	public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+		if(d.ll(4)) d.l("onKey, keyCode=" + keyCode);
+		if(keyCode == KeyEvent.KEYCODE_BACK) {
+			if(event.getAction() == KeyEvent.ACTION_UP) terminate();
+			return true;
+		}
+		return false;
+	}
+
+	@Override
+	public void onClick(DialogInterface dialog, int which) {
+		if(d.ll(4)) d.l("DialogInterface.onClick()");
+		terminate();
+	}
+
+	@Override
+	public void onClick(View v) {   // click na cancel button v transfer listu
+		NetTaskRef netTaskRef = (NetTaskRef) v.getTag();
+		if(d.ll(4)) d.l("View.onClick(), cancel button, netTaskRef=" + netTaskRef);
+		netTaskRef.go = false;
+		if(K.transfProgress.transfCnt() == 1) terminate();     // poslední task, zhasnout transfer list
+	}
+
+	void action(int action) {
+		if(d.ll(4)) d.l("action=" + action);
+		if(action == K.TRANSFER_PIN_NOTIFICATION) {
+			if(transferInProgress()) {
+				d.l(4,"pin up transfer notification");
+				K.transfProgress.barNotif.pin();
+			}
+		}
+		else if(action == K.TRANSFER_DISPLAY_LIST) {
+			if(transferInProgress()) {
+				d.l(4,"transfer monitor activation...");
+				// getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND, WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+				K.transfProgress.barNotif.pin();
+				if(listAdapter == null) listAdapter = new ProgressListAdapter(d, this);
+
+				AlertDialog progressList = new AlertDialog.Builder(this)
+						.setAdapter(listAdapter,this)
+						.setTitle(fragmentTitle)
+						.setOnKeyListener(this)
+						.create();
+				progressList.setCanceledOnTouchOutside(false);
+				progressList.show();
+				stack.push(progressList);
+
+				handler.postDelayed(this, K.transfProgress.refreshInterval);
+				d.l(4, "transfer monitor activated");
+			}
+			else terminate();
+		}
+	}
+
+	void terminate() {
+		if(!stack.empty()) ((AlertDialog)stack.pop()).dismiss();
+		if(stack.empty()) finish();
+	}
+
+	boolean transferInProgress() {
+		if(K.transfProgress != null) return K.transfProgress.transfCnt() > 0;
+		else return false;
+	}
+
+	/**----------------------------------------------------------
+	 * progress list adapter
+	 * ----------------------------------------------------------*/
+	class ProgressListAdapter extends BaseAdapter {
+
+		D d;
+		View.OnClickListener cancelListener;
+
+		ProgressListAdapter(D d, View.OnClickListener cancelListener) {
+			this.d = d.klon(this);
+			this.cancelListener = cancelListener;
+			}
+
+		public int getCount() { return K.transfProgress == null ? 0 : K.transfProgress.transfCnt(); }
+
+		public Object getItem(int position) { return null; }
+
+		public long getItemId(int position) { return position; }
+
+		public int getViewTypeCount() { return 8; }
+
+		public View getView(int position, View listEntry, ViewGroup parent) {
+			if(d.ll(5)) d.l(String.format("getView, position=%d, view=%x", position, listEntry == null ? 0 : listEntry.hashCode()));
+			synchronized(K.transfProgress.statSet) {
+				if(position < K.transfProgress.statSet.size()) {
+					final NetTaskRef netTaskRef = K.transfProgress.statSet.get(position);
+					final TaskProgress taskProgress = netTaskRef.getProgress();
+					taskProgress.updateView(netTaskRef, cancelListener);
+					if(d.ll(5)) d.l(String.format("getView return, position=%d, view=%x", position, taskProgress.listRow.hashCode()));
+					return taskProgress.listRow;
+				}
+				else return null;
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/TaskProgress.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,73 @@
+package hh.dejsem.fm;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.text.format.Formatter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import hh.dejsem.R;
+import hh.lib.D;
+
+/**
+ * ----------------------------------------------------------
+ * holds and updates view on progress of one file transfer task
+ * ----------------------------------------------------------
+ */
+public class TaskProgress {
+	D d;
+	public EntitySize es;
+	long updTs;
+	int pct = 0;
+	String fn;
+	LinearLayout listRow;
+	TextView progressFiles, progressFn, progressNum;
+	ProgressBar progressBar;
+	ImageView cancelButton;
+
+	TaskProgress(D d) { this(d, new EntitySize().zero()); }
+
+	TaskProgress(D d, EntitySize target) {
+		this.d = d.klon(this);
+		/*es = new EntitySize(target);*/
+		es = target;
+		final LayoutInflater lInflater = LayoutInflater.from(d.getContext());
+		listRow = (LinearLayout)lInflater.inflate(R.layout.progress_entry, null);
+		progressFiles = ((TextView)(listRow.findViewById(R.id.progressFiles)));
+		progressFn = ((TextView)(listRow.findViewById(R.id.progressFn)));
+		progressNum = ((TextView)(listRow.findViewById(R.id.progressNum)));
+		progressBar = ((ProgressBar)(listRow.findViewById(R.id.progressBar)));
+		cancelButton = ((ImageView)(listRow.findViewById(R.id.cancel)));
+	}
+
+	void updateView(NetTaskRef netTaskRef, View.OnClickListener cancelListener) {
+		Bundle b = es.getTarget();
+		int i = b.getInt(EntitySize.TARGET_FNUM_KEY);
+		String files = String.format(" %d file%s", i, (i > 1 ? "s" : ""));
+		i = b.getInt(EntitySize.TARGET_DNUM_KEY);
+		if(i > 0) files += String.format(" in %d dir%s", i, (i > 1 ? "s" : ""));
+		files += ": ";
+		b = es.getStatus();
+		final Context c = d.getContext();
+		pct = b.getInt(EntitySize.PCT_KEY);
+		String formatted = String.format("%s of %s (%d%%) %s",
+				Formatter.formatFileSize(c, b.getLong(EntitySize.RUNNING_SIZE_KEY)),
+				Formatter.formatFileSize(c, b.getLong(EntitySize.TARGET_SIZE_KEY)),
+				pct,
+				(b.getBoolean(EntitySize.STALLED_KEY) ? " stalled" : ""));
+		fn = b.getString(EntitySize.FN_KEY);
+
+		progressFiles.setText(files);
+		progressFn.setText(fn);
+		progressNum.setText(formatted);
+		progressBar.setProgress(pct);
+		cancelButton.setTag(netTaskRef);
+		cancelButton.setOnClickListener(cancelListener);
+		updTs = System.currentTimeMillis();
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/TransferAlert.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,110 @@
+package hh.dejsem.fm;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.KeyEvent;
+
+import hh.dejsem.Act;
+import hh.dejsem.K;
+import hh.dejsem.Main;
+import hh.dejsem.SendUriNG;
+
+/**
+ * Activita k zobrazení zpráv z LongAsyncTask, který obecně nemá dostupné hlavní UI
+ */
+public class TransferAlert
+		extends Act
+		implements
+			DialogInterface.OnClickListener,
+			DialogInterface.OnDismissListener,
+			DialogInterface.OnKeyListener {
+
+	AlertDialog.Builder alertBuilder;
+	AlertDialog alert;
+
+	@Override
+	public void onCreate(Bundle b) {
+		super.onCreate(b);
+		Main.eyeCatcher(d, this);
+		d.logPrefix += "[" + getIntent().getIntExtra("IDX", 0) + "]";
+		alertBuilder = new AlertDialog.Builder(this).setOnKeyListener(this);
+		action(getIntent().getIntExtra(K.ACTION_KEY, 0));
+	}
+
+	@Override
+	public void onResume() {
+		super.onResume();
+		if(alert != null) alert.show();
+	}
+
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		if(alert != null) alert.dismiss();
+	}
+
+	@Override
+	public void onDismiss(DialogInterface d) { finish(); }
+
+	@Override
+	public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+		if(d.ll(4)) d.l(String.format("onKey, key code=%d, key action=%d", keyCode, event.getAction()));
+		if(keyCode == KeyEvent.KEYCODE_BACK) {
+			if(event.getAction() == KeyEvent.ACTION_UP) terminate();
+			return true;
+		}
+		return false;
+	}
+
+	@Override
+	public void onClick(DialogInterface dialog, int which) {
+		d.l(4,"onClick");
+		terminate();
+	}
+
+	void action(int action) {
+		if(d.ll(4)) d.l("action=" + action);
+		switch(action) {
+			case K.TRANSFER_WARNING:
+				displayMsg("WARNING");
+				break;
+			case K.TRANSFER_ABEND:
+				displayMsg("ABEND");
+				break;
+			case K.TRANSFER_DISPLAY_TEST:
+				displayMsg("ALERT TEST");
+				break;
+			case K.TRANSFER_SEND_URI:
+				decideUriMsg();
+				break;
+		}
+	}
+
+	void displayMsg(String title) {
+		alert = alertBuilder
+				.setTitle(String.format("%s %s", getPackageName(), title))
+				.setMessage(getIntent().getStringExtra(K.TXT_KEY))
+				.setPositiveButton("OK", this)
+				.create();
+		alert.setCanceledOnTouchOutside(false);
+	}
+
+	void decideUriMsg() {
+		SendUriNG sendUri = new SendUriNG(d, this, getIntent().getStringExtra(K.TXT_KEY));
+		alert = alertBuilder
+				.setTitle(String.format("%s: %s", getPackageName(), "expose to HTTP is complete"))
+				.setMessage(String.format("URL %s is now on clipboard. Should it be sent?", sendUri.uriStr))
+				.setPositiveButton("YES", sendUri)
+				.setNegativeButton("NO", this)
+				.create();
+		alert.setCanceledOnTouchOutside(false);
+	}
+
+	void terminate() {
+		d.l(4,"terminate");
+		alert.dismiss();
+		alert = null;
+		finish();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/TransferOverview.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,52 @@
+package hh.dejsem.fm;
+
+import android.os.Message;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+
+import hh.dejsem.K;
+import hh.dejsem.TitleBar;
+import hh.lib.D;
+
+/**
+ /**----------------------------------------------------------
+ * singleton udržující evidenci o probíhajících síťových přenosech
+ * -----------------------------------------------------------
+ */
+public class TransferOverview {
+
+	D d;
+	BarNotification barNotif = null;
+	public final ArrayList<NetTaskRef> statSet = new ArrayList<NetTaskRef>();
+	public final int refreshInterval = 1000;
+
+	public TransferOverview(D d) {
+		this.d = d.klon(this);
+		barNotif = new BarNotification(this.d, ProgressMonitor.class);
+	}
+
+	int transfCnt() { synchronized(statSet) { return statSet.size(); } }
+
+	public void registerTask(NetTaskRef netTaskRef) {
+		synchronized(statSet) {
+			barNotif.pin();
+			if(transfCnt() == 0) d.l(4, "bar notification established");
+			TitleBar.setPending();
+			try { K.title.msgr.send(Message.obtain(null, K.MSG_WATCH_PROGRESS)); } catch(RemoteException e) {}
+			statSet.add(netTaskRef);
+		}
+		if(d.ll(4)) d.l("background task on port " + netTaskRef.getPort() + " registered");
+	}
+
+	public void unRegisterTask(NetTaskRef netTaskRef) {
+		synchronized(statSet) {
+			statSet.remove(netTaskRef);
+			if(transfCnt() == 0) {
+				barNotif.cut();
+				TitleBar.unSetPending();
+			}
+		}
+		if(d.ll(4)) d.l("background task on port " + netTaskRef.getPort() + " unregistered");
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/UploadAct.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,66 @@
+package hh.dejsem.fm;
+
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import hh.dejsem.Act;
+import hh.dejsem.GetReady;
+import hh.dejsem.K;
+import hh.dejsem.Main;
+import hh.dejsem.R;
+import hh.dejsem.TitleBar;
+
+/**
+ * responds to intentions to send data to server
+ */
+public class UploadAct extends Act implements GetReady.ReadyListener {
+	static final String TITLETEXT = "upload";
+	
+	@Override 
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		d.logPrefix += String.format("[%x]", hashCode());
+
+		/*if(getClass().getPackage().getName().startsWith("hh.dejsem")) {   // +++
+			K.UDP_PORT = 4224;
+			d.l(String.format("========= application %s, %s =========",
+					getResources().getString(R.string.app_name), getClass().getSimpleName()));    // +++ log eye catcher
+		}*/
+		Main.eyeCatcher(d, this);
+
+		setContentView(R.layout.basic_layout);
+		TitleBar.assign(this, R.id.title_bar, TITLETEXT);
+
+		if(GetReady.isReady(d)) upload();
+		else {
+			TextView tv = new TextView(this);
+			tv.setText("waiting for password & SD card permissions");
+			((ViewGroup) findViewById(R.id.panel)).addView(tv);
+			GetReady.getReady(d, this);
+		}
+	}
+
+	@Override
+	public void onStart() {
+		super.onStart();
+		if(K.title != null) K.title.reassign(this, R.id.title_bar).setText(TITLETEXT);
+	}
+
+	@Override
+	public void onReady(boolean success) {
+		if(success) upload();
+		else finish();
+	}
+
+	void upload() {
+		UploadFrag upload = (UploadFrag)getSupportFragmentManager().findFragmentByTag(K.UPLOAD_TAG);
+		if (upload == null) {
+			upload = UploadFrag.instantiate(d, getIntent());
+		}
+		getSupportFragmentManager()
+				.beginTransaction()
+				.replace(R.id.panel, upload, K.UPLOAD_TAG)
+				.commitAllowingStateLoss();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/UploadFrag.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,244 @@
+package hh.dejsem.fm;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import java.io.File;
+import java.util.HashMap;
+
+import hh.dejsem.K;
+import hh.dejsem.R;
+import hh.dejsem.net.ExposeStream;
+import hh.dejsem.net.ExposeFile;
+import hh.dejsem.net.SrvCmd;
+import hh.lib.D;
+import hh.lib.DF;
+
+/**
+ * responds to intentions to send data to server
+ */
+public class UploadFrag extends DF implements View.OnClickListener {
+
+	String TITLETEXT = "share";
+
+	public static UploadFrag instantiate(D d, Intent intent) {
+		UploadFrag uf = new UploadFrag();
+		uf.d = d.klon(uf);
+		uf.intent = intent;
+		return uf;
+	}
+
+	static HashMap<Integer, String> flagNames = new HashMap<>();
+	static {
+		flagNames.put(Intent.FLAG_FROM_BACKGROUND, "FLAG_FROM_BACKGROUND");
+		flagNames.put(Intent.FLAG_DEBUG_LOG_RESOLUTION, "FLAG_DEBUG_LOG_RESOLUTION");
+		flagNames.put(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES, "FLAG_EXCLUDE_STOPPED_PACKAGES");
+		flagNames.put(Intent.FLAG_INCLUDE_STOPPED_PACKAGES, "FLAG_INCLUDE_STOPPED_PACKAGES");
+		flagNames.put(Intent.FLAG_ACTIVITY_MATCH_EXTERNAL, "FLAG_ACTIVITY_MATCH_EXTERNAL");
+		flagNames.put(Intent.FLAG_ACTIVITY_NO_HISTORY, "FLAG_ACTIVITY_NO_HISTORY");
+		flagNames.put(Intent.FLAG_ACTIVITY_SINGLE_TOP, "FLAG_ACTIVITY_SINGLE_TOP");
+		flagNames.put(Intent.FLAG_ACTIVITY_NEW_TASK, "FLAG_ACTIVITY_NEW_TASK");
+		flagNames.put(Intent.FLAG_ACTIVITY_MULTIPLE_TASK, "FLAG_ACTIVITY_MULTIPLE_TASK");
+		flagNames.put(Intent.FLAG_ACTIVITY_CLEAR_TOP, "FLAG_ACTIVITY_CLEAR_TOP");
+		flagNames.put(Intent.FLAG_ACTIVITY_FORWARD_RESULT, "FLAG_ACTIVITY_FORWARD_RESULT");
+		flagNames.put(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP, "FLAG_ACTIVITY_PREVIOUS_IS_TOP");
+		flagNames.put(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS, "FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS");
+		flagNames.put(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT, "FLAG_ACTIVITY_BROUGHT_TO_FRONT");
+		flagNames.put(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED, "FLAG_ACTIVITY_RESET_TASK_IF_NEEDED");
+		flagNames.put(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY, "FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY");
+		flagNames.put(Intent.FLAG_ACTIVITY_NEW_DOCUMENT, "FLAG_ACTIVITY_NEW_DOCUMENT");
+		flagNames.put(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET, "FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET");
+		flagNames.put(Intent.FLAG_ACTIVITY_NO_USER_ACTION, "FLAG_ACTIVITY_NO_USER_ACTION");
+		flagNames.put(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT, "FLAG_ACTIVITY_REORDER_TO_FRONT");
+		flagNames.put(Intent.FLAG_ACTIVITY_NO_ANIMATION, "FLAG_ACTIVITY_NO_ANIMATION");
+		flagNames.put(Intent.FLAG_ACTIVITY_CLEAR_TASK, "FLAG_ACTIVITY_CLEAR_TASK");
+		flagNames.put(Intent.FLAG_ACTIVITY_TASK_ON_HOME, "FLAG_ACTIVITY_TASK_ON_HOME");
+		flagNames.put(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS, "FLAG_ACTIVITY_RETAIN_IN_RECENTS");
+		flagNames.put(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT, "FLAG_ACTIVITY_LAUNCH_ADJACENT");
+		flagNames.put(Intent.FLAG_RECEIVER_REGISTERED_ONLY, "FLAG_RECEIVER_REGISTERED_ONLY");
+		flagNames.put(Intent.FLAG_RECEIVER_REPLACE_PENDING, "FLAG_RECEIVER_REPLACE_PENDING");
+		flagNames.put(Intent.FLAG_RECEIVER_FOREGROUND, "FLAG_RECEIVER_FOREGROUND");
+		flagNames.put(Intent.FLAG_RECEIVER_NO_ABORT, "FLAG_RECEIVER_NO_ABORT");
+		flagNames.put(Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS, "FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS");
+	}
+
+	Intent intent;
+	View view = null;
+	Button analyze, expose;
+	/*View.OnLongClickListener longClickToUpload = new View.OnLongClickListener() {
+		@Override 
+		public boolean onLongClick(View v) { return handleFileUpload(); }
+	};*/
+
+	/*@Override
+	public void onCreate(Bundle b) {
+		super.onCreate(b);
+		setRetainInstance(true);
+	}*/
+	
+	@Override 
+	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+		super.onCreateView(inflater, container, savedInstanceState);
+		setRetainInstance(true);
+		if(view == null) {
+			view = inflater.inflate(R.layout.upload_panel, null);
+			d.sc = (ScrollView) view.findViewById(R.id.sc);
+			d.tv = (TextView) view.findViewById(R.id.tv);
+			d.tv.setText("");
+			analyze = view.findViewById(R.id.analyze);
+			analyze.setOnClickListener(this);
+			if(intent.hasExtra(Intent.EXTRA_STREAM)) {
+				expose = view.findViewById(R.id.expose);
+				expose.setVisibility(View.VISIBLE);
+				expose.setOnClickListener(this);
+			}
+			else d.tv.append("\nshared object has no data stream");
+		}
+		return view;
+	}
+
+	/*@Override
+	public void onActivityCreated(Bundle b) {
+		super.onActivityCreated(b);
+	}*/
+
+	@Override
+	public void onClick(View v) {
+		if(v.getId() == R.id.analyze) analyze();
+		else if(v.getId() == R.id.expose) expose();
+	}
+
+	void analyze() {
+		analyze.setVisibility(View.GONE);
+		String flags = "";
+		for(int i=32; i>=0; i--) {
+			int flag = 1 << i;
+			if(flagNames.containsKey(flag) && (intent.getFlags() & flag) != 0)
+				flags += String.format("\n\t%08x, %s", flag, flagNames.get(flag));
+		}
+		String keys = "";
+		if(intent.getExtras() != null) for(String key: intent.getExtras().keySet()) keys += key + " ";
+		if(d.ll(4)) {
+			d.l("toUri()=" + intent.toUri(0));
+			d.l("getAction()=" + intent.getAction());
+			d.l("getData()=" + intent.getData());
+			d.l("getType()=" + intent.getType());
+			d.l("Extras keys=" + keys);
+		}
+		d.tv.append("\naction=" + intent.getAction());
+		d.tv.append(String.format("\nflags=%x", intent.getFlags()));
+		if(flags.length() > 0) d.tv.append(flags);
+		d.tv.append("\ndescribeContents()=" + intent.describeContents());
+		d.tv.append("\ngetAction()=" + intent.getAction());
+		d.tv.append("\ngetCategories()=" + intent.getCategories());
+		d.tv.append("\ngetComponent()=" + intent.getComponent());
+		d.tv.append("\ngetData()=" + intent.getDataString());
+		d.tv.append("\ngetDataString()=" + intent.getDataString());
+		d.tv.append("\ngetPackage()=" + intent.getPackage());
+		d.tv.append("\ngetScheme()=" + intent.getScheme());
+		d.tv.append("\ngetType()=" + intent.getType());
+		d.tv.append("\nhasFileDescriptors()=" + intent.hasFileDescriptors());
+		if(intent.getCategories() != null) {
+			d.tv.append("\ncategores:");
+			for(String s: intent.getCategories()) d.tv.append("\n  " + s); }
+		d.tv.append("\ngetExtras()=" + intent.getExtras());
+		if(intent.getExtras() != null) d.tv.append("\ndescribeContents()=" + intent.getExtras().describeContents());
+		d.tv.append("\nExtras keys=" + keys);
+		d.tv.append("\nhasExtra[EXTRA_TEXT]=" + intent.hasExtra(Intent.EXTRA_TEXT));
+		d.tv.append("\nEXTRA_TEXT=" + intent.getStringExtra(Intent.EXTRA_TEXT));
+		d.tv.append("\nhasExtra[EXTRA_STREAM]=" + intent.hasExtra(Intent.EXTRA_STREAM));
+		d.tv.append("\nEXTRA_STREAM=" + (Uri)intent.getParcelableExtra(Intent.EXTRA_STREAM));
+		if(intent.getExtras() != null) {
+			Bundle b = intent.getExtras();
+			if(b.containsKey(K.FN_KEY)) d.tv.append("\nfile=" + b.getString(K.FN_KEY));
+			Uri uri = b.getParcelable(Intent.EXTRA_STREAM);
+			if(uri != null) {
+				if(d.ll(4)) d.l("EXTRA_STREAM=" + uri);
+				d.tv.append("\nstream URI:");
+				d.tv.append("\n\ttoString()=" + uri.toString());
+				d.tv.append("\n\tisAbsolute()=" + uri.isAbsolute());
+				d.tv.append("\n\tisHierarchical()=" + uri.isHierarchical());
+				d.tv.append("\n\tgetScheme()=" + uri.getScheme());
+				d.tv.append("\n\tgetAuthority()=" + uri.getAuthority());
+				d.tv.append("\n\tgetHost()=" + uri.getHost());
+				d.tv.append("\n\tgetFragment()=" + uri.getFragment());
+				d.tv.append("\n\tgetPath()=" + uri.getPath());
+				}
+			}
+		}
+
+	void expose() {
+		expose.setVisibility(View.GONE);
+		Uri uri = intent.getExtras().getParcelable(Intent.EXTRA_STREAM);
+		if(uri.getScheme().equals("file")) handleFileUpload(uri);
+		else handleStreamUpload(uri);
+	}
+
+	boolean handleFileUpload(Uri uri) {
+		try {
+			d.l(4, "handleFileUpload, START");
+			int port = (Integer)SrvCmd.LONGTASK.exec(d, null);
+			K.cmdConnClose(d);
+			if(port == 0) throw new Exception("all server ports are busy", null);
+			Bundle taskParms = new Bundle();
+			taskParms.putInt(K.PORT_KEY, port);
+			String[] selection = new String[] { new File(uri.toString()).getPath() };
+			taskParms.putStringArray(K.FILES_FROM_KEY, selection);
+			taskParms.putString(K.FILE_TO_KEY, "");
+			EntitySize es = FileList.reckon(selection);
+			TaskProgress taskProgress = new TaskProgress(d, es);
+			K.prepTransferOverview(d);
+			FragFM.sweepOutNetCmd(d);
+			NetTaskRef netTaskRef = new NetTaskRef(d, taskParms, es);
+			d.l(4, String.format("netTaskRef=%s", netTaskRef));
+            new ExposeFile(d, netTaskRef).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+			d.l(4, "handleFileExpose, END, upload handed over to background");
+			return true;
+		}
+		catch(Exception e) { 
+			d.abendMsg(e);
+			return false;
+		}
+	}
+
+	boolean handleStreamUpload(Uri uri) {
+			d.l(4, "handleStreamUpload, START");
+			Bundle taskParms = new Bundle();
+			String mimeType = intent.getType();
+			taskParms.putString(K.MIME_TYPE_KEY, mimeType != null ? mimeType : "");
+			taskParms.putParcelable(K.STREAM_KEY, intent.getExtras().getParcelable(Intent.EXTRA_STREAM));
+		try {
+			ContentResolver cr = d.getContext().getContentResolver();
+			ContentProviderClient cc = cr.acquireContentProviderClient(uri);
+			if(cc == null) throw new Exception("data stream has no content provider");
+			Long size = cc.openAssetFile(uri, "r").getLength();
+			EntitySize es = new EntitySize(size, 1, 0);
+			TaskProgress taskProgress = new TaskProgress(d, es);
+			int port = (Integer)SrvCmd.LONGTASK.exec(d, null);
+			K.cmdConnClose(d);
+			if(port == 0) throw new Exception("all server ports are busy", null);
+			taskParms.putInt(K.PORT_KEY, port);
+			K.prepTransferOverview(d);
+			FragFM.sweepOutNetCmd(d);
+			NetTaskRef netTaskRef = new NetTaskRef(d, taskParms, es);
+			d.l(4, String.format("netTaskRef=%s", netTaskRef));
+            new ExposeStream(d, netTaskRef).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+			d.l(4, "handleStreamExpose, END, upload handed over to background");
+			return true;
+		}
+		catch(Exception e) {
+			d.abendMsg(e);
+			return false;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackA.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,129 @@
+package hh.dejsem.hack;
+
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import java.io.File;
+
+import hh.dejsem.Act;
+import hh.dejsem.K;
+import hh.dejsem.net.SrvCmd;
+
+/**
+ * The type Hack a.
+ */
+public class HackA extends Act {
+
+	final static String TITLETEXT = "hack";
+
+//	TitleIndiv title;
+	Uri uri;
+	View.OnLongClickListener longClickToUpload = new View.OnLongClickListener() {
+		@Override 
+		public boolean onLongClick(View v) { return handleUpload(); } 
+	};
+	
+	@Override 
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		d.tv = new TextView(this);
+		d.sc = new ScrollView(this);
+		d.sc.addView(d.tv);
+		setContentView(d.sc);
+	}
+
+	void analyze() {
+		d.tv.setText(d.logTag + "\n\n");
+		Intent i = getIntent();
+		d.tv.append("action=" + i.getAction() + "\n");
+		if(d.ll(4)) {
+			d.l("data=" + i.getData() + ", getType=" + i.getType());
+			d.l("toUri()=" + i.toUri(0));
+			d.l("getExtras()=" + i.getExtras());
+			d.l("Intent.EXTRA_STREAM=" + Intent.EXTRA_STREAM);
+		}
+		d.tv.append("describeContents()=" + i.describeContents() + "\n");
+		d.tv.append("getAction()=" + i.getAction() + "\n");
+		d.tv.append("getCategories()=" + i.getCategories() + "\n");
+		d.tv.append("getComponent()=" + i.getComponent() + "\n");
+		d.tv.append("getData()=" + i.getDataString() + "\n");
+		d.tv.append("getDataString()=" + i.getDataString() + "\n");
+		d.tv.append("getExtras()=" + i.getExtras() + "\n");
+		if(i.getExtras() != null) d.tv.append("describeContents()=" + i.getExtras().describeContents() + "\n");
+		d.tv.append("hasExtra[EXTRA_TEXT]=" + i.hasExtra(Intent.EXTRA_TEXT) + "\n");
+		d.tv.append("EXTRA_TEXT=" + i.getStringExtra(Intent.EXTRA_TEXT) + "\n");
+		d.tv.append("hasExtra[EXTRA_STREAM]=" + i.hasExtra(Intent.EXTRA_STREAM) + "\n");
+		d.tv.append("EXTRA_STREAM=" + (Uri)i.getParcelableExtra(Intent.EXTRA_STREAM) + "\n");
+		d.tv.append("getFlags()=" + i.getFlags() + "\n");
+		d.tv.append("getPackage()=" + i.getPackage() + "\n");
+		d.tv.append("getScheme()=" + i.getScheme() + "\n");
+		d.tv.append("getType()=" + i.getType() + "\n");
+		d.tv.append("hasFileDescriptors()=" + i.hasFileDescriptors() + "\n");
+		if(i.getCategories() != null) {
+			d.tv.append("categores:\n");
+			for(String s: i.getCategories()) d.tv.append("  " + s + "\n"); }
+		if(i.getExtras() != null) {
+			d.tv.append("Bundle:\n");
+			Bundle b = i.getExtras();
+			d.tv.append(i.getExtras().toString() + "\n");
+			if(b.containsKey(K.FN_KEY)) d.tv.append("file=" + b.getString(K.FN_KEY) + "\n");
+			uri = (Uri)b.getParcelable(Intent.EXTRA_STREAM);
+			if(uri != null) {
+				d.tv.append("URI:\n");
+				d.tv.append("isAbsolute()=" + uri.isAbsolute() + "\n");
+				d.tv.append("isHierarchical()=" + uri.isHierarchical() + "\n");
+				d.tv.append("getScheme()=" + uri.getScheme() + "\n");
+				d.tv.append("getAuthority()=" + uri.getAuthority() + "\n");
+				d.tv.append("getHost()=" + uri.getHost() + "\n");
+				d.tv.append("getFragment()=" + uri.getFragment() + "\n");
+				d.tv.append("getPath()=" + uri.getPath() + "\n");
+				d.tv.append("getScheme()=" + uri.getScheme() + "\n");
+
+				if(d.ll(4)) {
+					d.l(String.format("getParcelable(EXTRA_STREAM)=%s", uri.toString()));
+					d.l(String.format("uri=%s", uri));
+					d.l(String.format("uri.path=%s", uri.getPath()));
+					ContentResolver cr = getContentResolver();
+					d.l(String.format("canonicalized=%s", cr.canonicalize(uri)));
+					try {
+						java.net.URI juri = new java.net.URI(uri.getPath());
+						d.l(String.format("java.net.URI=%s", juri));
+//						File furi = new File(juri);
+//						d.l(String.format("uri file=%s", furi));
+					} catch(Exception e) { d.abendMsg(e); finish(); }
+				}
+			}
+		}
+	}
+
+	boolean handleUpload() {
+		try {
+			d.l(4, "handleUpload, START");
+			int port = (Integer) SrvCmd.LONGTASK.exec(d, null);
+			K.cmdConnClose(d);
+			if(port == 0) throw new Exception("all server ports busy", null);
+			String to = "exposed";
+			Bundle parms = new Bundle();
+			parms.putInt(K.PORT_KEY, port);
+			d.l(4, String.format("uri=%s", uri.toString()));
+//			InputStream fileInputStream=yourContext.getContentResolver().openInputStream(uri);
+			String[] selection = new String[] { new File(new java.net.URI(uri.getPath())).getPath() };
+			parms.putStringArray(K.FILES_FROM_KEY, selection);
+			parms.putString(K.FILE_TO_KEY, to);
+//			TaskProgress taskProgress = new TaskProgress(d, FileList.reckon(selection));
+	
+	//		fmsrv.iSvc.sendToService(D.COPY_TO_SRV, taskRef);	// execution under Service
+			d.l(4, "handleUpload, FINISH");
+			return true;
+		}
+		catch(Exception e) { 
+			d.abendMsg(e);
+			return false;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackAlertTest.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,42 @@
+package hh.dejsem.hack;
+
+import android.content.Context;
+import android.content.Intent;
+
+import java.io.IOException;
+
+import hh.dejsem.K;
+import hh.dejsem.fm.TransferAlert;
+import hh.lib.D;
+
+public class HackAlertTest implements Runnable {
+
+	D d;
+	Context c;
+
+	HackAlertTest(Context c, D d) {
+		this.d = d.klon(this);
+		this.c = c;
+	}
+
+	@Override
+	public void run() {
+		c.startActivity(new Intent(c, TransferAlert.class)
+				.putExtra(K.ACTION_KEY, K.TRANSFER_DISPLAY_TEST)
+				.putExtra(K.TXT_KEY, String.format("plavala husička do kopečka, za ní se koulela piva bečka"))
+		);
+		try { Thread.sleep(1000); } catch(Exception e) {}
+
+		c.startActivity(new Intent(c, TransferAlert.class)
+				.putExtra(K.ACTION_KEY, K.TRANSFER_DISPLAY_TEST)
+				.putExtra(K.TXT_KEY, K.testText)
+		);
+		try { Thread.sleep(1000); } catch(Exception e) {}
+
+		c.startActivity(new Intent(c, TransferAlert.class)
+				.putExtra(K.ACTION_KEY, K.TRANSFER_DISPLAY_TEST)
+				.putExtra(K.TXT_KEY, d.composeAbendMsg("zkouška ABENDu", new IOException("accept timeout")))
+		);
+		try { Thread.sleep(1000); } catch(Exception e) {}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackContextMenu.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,117 @@
+package hh.dejsem.hack;
+
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+
+import java.util.ArrayList;
+
+import hh.dejsem.Act;
+import hh.dejsem.R;
+
+public class HackContextMenu extends Act implements View.OnClickListener {
+
+	public enum PanelMenuItem {
+		//-------------------------------------------------------------
+		// intenzívnější zkouška kontextového menu/submenu
+		//-------------------------------------------------------------
+		M1() { void action(HackContextMenu target) throws Exception { target.handleMenuItem11(); } },
+		M2("zkouška 2"),
+		M3("zkouška 3") { void action(HackContextMenu target) throws Exception { target.handleMenuItem33(); } };
+
+		final String title;
+		void action(HackContextMenu target) throws Exception {};
+
+		PanelMenuItem() { title = name(); }
+		PanelMenuItem(String n) { this.title = n; }
+	}
+	ArrayList<PanelMenuItem> items = new ArrayList<PanelMenuItem>();
+	Button b1, b2, b3, b4;
+
+	@Override
+	public void onCreate(Bundle b) {
+		super.onCreate(b);
+		setContentView(R.layout.hack);
+		for(PanelMenuItem p: PanelMenuItem.values()) items.add(p.ordinal(), p);
+//		panelMenu = new HackPanelMenu(d);
+		b1 = (Button)findViewById(R.id.bHack_1);
+		registerForContextMenu(b1);
+		b1.setOnClickListener(this);
+
+		b2 = (Button)findViewById(R.id.bHack_2);
+		registerForContextMenu(b2);
+		b2.setOnClickListener(this);
+
+		b3 = (Button)findViewById(R.id.bHack_3);
+		registerForContextMenu(b3);
+		b3.setOnClickListener(this);
+
+		b4 = (Button)findViewById(R.id.bHack_4);
+		b4.setOnClickListener(this);
+	}
+
+	@Override
+	public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+		d.l("HackContextMenu.onCreateContextMenu");
+//		panelMenu.createContextMenu(menu, v, menuInfo);
+		switch(v.getId()) {
+			case R.id.bHack_1:
+				menu.add(Menu.NONE, PanelMenuItem.M1.ordinal(), Menu.NONE, PanelMenuItem.M1.title);
+				Menu smenu = menu.addSubMenu(Menu.NONE, 9999, Menu.NONE, "submenu S1");
+				smenu.add(Menu.NONE, PanelMenuItem.M2.ordinal(), Menu.NONE, PanelMenuItem.M2.title);
+				smenu.add(Menu.NONE, PanelMenuItem.M3.ordinal(), Menu.NONE, PanelMenuItem.M3.title);
+				menu.add(Menu.NONE, 22, Menu.CATEGORY_CONTAINER + 1, "expl. zkouška 2");
+				menu.add(Menu.NONE, 33, Menu.CATEGORY_CONTAINER + 2, "expl. zkouška 3");
+				break;
+			case R.id.bHack_2:
+				menu.add(Menu.NONE, 22, Menu.CATEGORY_CONTAINER + 1, "expl. zkouška 2");
+				menu.add(Menu.NONE, PanelMenuItem.M3.ordinal(), Menu.NONE, PanelMenuItem.M3.title);
+				menu.add(Menu.NONE, 33, Menu.CATEGORY_CONTAINER + 2, "expl. zkouška 3");
+				break;
+			case R.id.bHack_3:
+				menu.add(Menu.NONE, PanelMenuItem.M1.ordinal(), Menu.NONE, PanelMenuItem.M1.title);
+				menu.add(Menu.NONE, 22, Menu.CATEGORY_CONTAINER + 1, "expl. zkouška 2");
+				menu.add(Menu.NONE, PanelMenuItem.M1.ordinal(), Menu.NONE, PanelMenuItem.M1.title);
+				break;
+		}
+	}
+
+	@Override
+	public void onClick(View v) {
+		d.l("open context menu");
+		switch(v.getId()) {
+			case R.id.bHack_1:
+				openContextMenu(b1);
+				break;
+			case R.id.bHack_2:
+				openContextMenu(b2);
+				break;
+			case R.id.bHack_3:
+				openContextMenu(b3);
+				break;
+			case R.id.bHack_4:
+				handleMenuItem44();
+				break;
+		}
+	}
+
+	@Override
+	public boolean onContextItemSelected(MenuItem item) {
+		d.l(String.format("handleContextItemSelected, item id=%d", item.getItemId()));
+		if(item.getItemId() < items.size())
+			try { items.get(item.getItemId()).action(this); }
+			catch(Exception e) {}
+			finally { return true; }
+//		if(panelMenu.handleContextItemSelected(item, this)) return true;
+		return super.onContextItemSelected(item);
+	}
+
+	public void handleMenuItem11() { d.l("handleMenuItem11"); }
+
+	public void handleMenuItem33() { d.l("handleMenuItem33"); }
+
+	public void handleMenuItem44() { d.l("handleMenuItem44"); }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackDF.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,54 @@
+package hh.dejsem.hack;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+
+import java.util.GregorianCalendar;
+
+import hh.lib.D;
+
+public class HackDF extends DialogFragment {
+
+	static HackDF instantiate(D d) {
+		HackDF ndf = new HackDF();
+		ndf.d = new D(ndf);
+		ndf.d.setFragment(ndf);
+		ndf.d.setAct(d.getAct());
+		ndf.d.l("instantiate");
+		return ndf;
+	}
+
+	D d;
+
+	/*@Override
+	public void onAttach(FragmentActivity c) {
+		super.onAttach(c);
+		if(d == null) {
+			d = new D(this);
+			d.setAct(c);
+		}
+		d.l(2, String.format("onAttach"));
+	}*/
+
+	@Override
+	public Dialog onCreateDialog(Bundle savedInstanceState) {
+		d.l(2, String.format("onCreateDialog"));
+		final GregorianCalendar cal = new GregorianCalendar();
+		final String s = String.format("● %d.%02d.%02d %02d:%02d:%02d.%03d UTC+%d",
+				cal.get(cal.YEAR), cal.get(cal.MONTH) + 1, cal.get(cal.DAY_OF_MONTH),
+				cal.get(cal.HOUR_OF_DAY), cal.get(cal.MINUTE), cal.get(cal.SECOND), cal.get(cal.MILLISECOND),
+				((cal.get(cal.ZONE_OFFSET) + cal.get(cal.DST_OFFSET))/(3600*1000)));
+		return new AlertDialog.Builder(this.getActivity())
+				.setTitle("zkouška převracení")
+				.setMessage(s)
+				.create();
+	}
+
+	@Override
+	public void onSaveInstanceState(Bundle b) {
+		//		super.onSaveInstanceState(b);
+		d.l(2, "onSaveInstanceState");
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackDfTst.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,19 @@
+package hh.dejsem.hack;
+
+import android.content.DialogInterface;
+
+import hh.lib.D;
+import hh.lib.NoticeDialogFragment;
+
+public class HackDfTst implements DialogInterface.OnClickListener {
+
+	D d;
+
+	@Override
+	public void onClick(DialogInterface i, int what) { d.l("+++ callback called"); }
+
+	void display(D d) {
+		this.d = d.klon(this);
+		new NoticeDialogFragment().display(d, "ZÁHLAVÍ", "msg 0", null);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackDfUse.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,78 @@
+package hh.dejsem.hack;
+
+import android.content.DialogInterface;
+import android.os.Handler;
+import android.view.View;
+import android.widget.ImageView;
+
+import java.util.GregorianCalendar;
+
+import hh.dejsem.K;
+import hh.dejsem.R;
+import hh.dejsem.hack.HackViewDialog;
+import hh.lib.D;
+
+public class HackDfUse implements HackViewDialog.ViewDialogView, DialogInterface.OnClickListener, View.OnClickListener, Runnable {
+
+	D d;
+	HackViewDialog hackDF;
+	Handler hackHandler = new Handler();
+	ImageView hackView = null;
+	boolean hackSw = false;
+	final String hackTag = "zkus";
+
+	HackDfUse(D d) { this.d = d.klon(this); }
+
+	void onActivityCreate() {
+		if(K.base.containsKey(hackTag)) {
+			final HackViewDialog df = (HackViewDialog) K.base.get(hackTag);
+			d.l("+++ notice dialog text getView=" + df.getView.hashCode());
+			if(hackDF != null && hackDF.isDetached()) hackHandler.postDelayed(this, 1000);
+		}
+	}
+
+	void dialogFragmentTest() {
+		d.l("+++ hack, getView=" + this.hashCode());
+		hackHandler.removeCallbacks(this);
+		if(hackDF != null) hackDF.dismiss();
+		hackDF = new HackViewDialog();
+		hackDF.display(d, hackTag, this, this);
+		hackHandler.postDelayed(this, 1000);
+	}
+
+	void hackSetDfView() {
+		hackSw = !hackSw;
+		hackView.setImageResource(hackSw ? R.raw.menu : R.raw.up);
+	}
+
+	@Override
+	public View getView() {
+		hackView = new ImageView(d.getContext());
+		hackSetDfView();
+		return hackView;
+	}
+
+	@Override
+	public void run() {
+		if(hackDF.isDetached()) return;
+		hackSetDfView();
+		hackView.invalidate();
+		hackHandler.postDelayed(this, 1000);
+	}
+
+	@Override
+	public void onClick(DialogInterface i, int which) {
+		d.l(String.format("+++ DialogInterface OnClick, this=%x, which=%d", hashCode(), which));
+	}
+
+	@Override
+	public void onClick(View v) {
+		final GregorianCalendar cal = new GregorianCalendar();
+		final String s = String.format("● %d.%02d.%02d %02d:%02d:%02d.%03d UTC+%d",
+				cal.get(cal.YEAR), cal.get(cal.MONTH) + 1, cal.get(cal.DAY_OF_MONTH),
+				cal.get(cal.HOUR_OF_DAY), cal.get(cal.MINUTE), cal.get(cal.SECOND), cal.get(cal.MILLISECOND),
+				((cal.get(cal.ZONE_OFFSET) + cal.get(cal.DST_OFFSET)) / (3600 * 1000)));
+		new HackViewDialog().display(d, "zkouška převracení", "zkouška převracení", s,null);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackDgrams.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,160 @@
+package hh.dejsem.hack;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import hh.dejsem.K;
+import hh.dejsem.net.NetCrypt;
+import hh.dejsem.net.NetUDP;
+import hh.lib.D;
+
+public class HackDgrams extends Thread {
+	final static int BCAST = 12;
+	final static int SEND = 13;
+	final static int REC = 14;
+	final static int CLOSE = 15;
+	final static int CHECK = 16;
+	final static int ECHO = 17;
+	final static String NULL_HOST = "0.0.0.0";
+	static volatile Set<DatagramSocket> sockets = new HashSet<>(100);
+	static volatile NetCrypt c = null;
+
+	D d;
+	NetUDP netUDP;
+	int dgramAction;
+	int port = K.UDP_PORT;
+	DatagramSocket socket;
+	String msg = "";
+	String host = NULL_HOST;
+	InetAddress ia = null;
+
+	HackDgrams(D d, int dgramAction, String msg, InetAddress ia) {
+		this(d, dgramAction, msg);
+		this.ia = ia;
+	}
+
+	HackDgrams(D d, int dgramAction, String msg, String host) throws UnknownHostException {
+		this(d, dgramAction, msg);
+		this.host = host;
+		ia = InetAddress.getByName(host);
+	}
+
+	HackDgrams(D d, int dgramAction, String msg) {
+		this(d, dgramAction);
+		this.msg = msg;
+	}
+
+	HackDgrams(D d, int dgramAction) {
+		this(d);
+		this.dgramAction = dgramAction;
+	}
+
+	HackDgrams(D d) {
+		this.d = d.klon(getClass().getSimpleName() + "." + hashCode());
+		synchronized(this) { if(c == null) c = new NetCrypt(d,null, "zatmívačka", 0); }
+	}
+
+	@Override
+	public void run() {
+//		sendToUI( 1, "zkouška spojení");
+//		d.appPrefix("" + hashCode());
+		d.l("+++ action=" + dgramAction);
+		if(dgramAction == CHECK) encDec();
+		//else if(dgramAction == REC) { netUDP = new NetUDP(d); netUDP.keepReceivingPeerIp(); } // zakryto kvůli neošetřené Exc
+		else if(dgramAction == SEND) sendDgram("//" + msg);
+		else if(dgramAction == BCAST) sendBroadcast("//" + msg);
+		else if(dgramAction == ECHO) echoDgrams();
+		else if(dgramAction == CLOSE) close();
+	}
+
+	DatagramPacket allocRecDgram() throws Exception {
+		socket = new DatagramSocket(new InetSocketAddress(InetAddress.getByName("0.0.0.0"), K.UDP_PORT));
+//		sockets.add(socket);
+		socket.setBroadcast(true);
+		byte[] recvBuf = new byte[15000];
+		DatagramPacket packet = new DatagramPacket(recvBuf, recvBuf.length);
+		d.l("receiving UDP allocated");
+		return packet;
+	}
+
+	String receiveUDPData(DatagramPacket packet) throws Exception {
+		socket.receive(packet);
+		int len = packet.getLength();
+		byte[] enc = Arrays.copyOf(packet.getData(), len);
+		String data = "";
+		try { data = c.decrypt(enc); }
+		catch(Exception e) { return null; }
+		if(!data.startsWith("//")) return null;
+		data = data.replaceFirst("^//", "");
+		return data;
+	}
+
+	void echoDgrams() {
+		DatagramPacket packet = null;
+		try {
+			packet = allocRecDgram();
+			d.l("receiving encrypted UDP packets...");
+			while(netUDP.go) {
+				String data = receiveUDPData(packet);
+				ia = packet.getAddress();
+				String ip = "unknown";
+				if(ia != null) ip = ia.getHostAddress();
+				if(data == null) continue;
+				String s = String.format("encrypted UDP dgram [%s] received from %s", data, ip);
+				d.l(s);
+
+				if(ia != null && data.indexOf("echo") < 0) {
+					d.l(String.format("sending echo to %s...", ip));
+					try { Thread.sleep(1000, 0); } catch(InterruptedException e) {}
+					msg = String.format("%s: echo: %s", NetUDP.localAddr.getHostAddress(), data);
+					sendDgram("//" + msg);
+				}
+			}
+		}
+		catch(SocketException e) { d.l("socket closed"); }
+		catch(Exception e) {
+			d.l(String.format("datagram exc errno=%d, emsg=[%s]", d.errno(e), e.getMessage()));
+			d.abendMsg("Oops", e);
+		}
+	}
+
+	void sendBroadcast(String msg) {
+		ia = NetUDP.broadcastAddr;
+		sendDgram(msg);
+	}
+
+	void sendDgram(String data) {
+		try {
+			DatagramSocket socket = new DatagramSocket();
+			socket.setBroadcast(true);
+			byte[] sendData = c.encrypt(data);
+			if(ia == null) ia = InetAddress.getByName(host);
+			DatagramPacket packet = new DatagramPacket(sendData, sendData.length, ia, K.UDP_PORT);
+			socket.send(packet);
+			d.l(String.format("UDP dgram [%s] sent to %s", msg, ia.getHostAddress()));
+		} catch (Exception e) { d.abendMsg("send broadcast UDP: ", e); }
+	}
+
+	void sendToUI(int what, String data) {
+		try { d.l(data); }
+		catch(Exception e) { d.abendMsg("messenger problem", e); }
+	}
+
+	void close() {
+//		d.l("+++ closing, sockets num=" + sockets.size());
+		/*for(DatagramSocket socket: sockets) {
+			socket.close();
+			sockets.remove(socket);
+		}*/
+		socket.close();
+	}
+
+	void encDec() { try { d.l(c.decrypt(c.encrypt("encrypt/decrypt test"))); } catch(Exception e) {} }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackDialogFragment.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,36 @@
+package hh.dejsem.hack;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.os.Bundle;
+
+import hh.lib.D;
+
+public class HackDialogFragment extends DialogFragment {
+	/*----------------------------------------------------------
+	* test dialog
+	* ----------------------------------------------------------*/
+	D d = null;
+	String title, message;
+    /*----------------------------------------------------------*/
+
+	void testDialog(D d, String title, String message) {
+		this.d = d.klon(this);
+		this.title = title;
+		this.message = message;
+	}
+
+	@Override
+	public Dialog onCreateDialog(Bundle savedInstanceState) {
+		if(d == null) { dismiss(); return null; }
+		AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+		builder
+				.setCancelable(true)
+				.setTitle(title)
+				.setItems(new String[] {"bla bla", "ble ble", "bleeeeeeeeee"}, null)
+				.setPositiveButton("OK", null);
+		AlertDialog dialog = builder.create();
+		return dialog;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackFS.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,104 @@
+package hh.dejsem.hack;
+
+import android.support.v4.provider.DocumentFile;
+
+import java.io.File;
+
+import hh.dejsem.K;
+import hh.dejsem.fm.FragFM;
+import hh.dejsem.fm.FSLocal;
+import hh.lib.D;
+
+public class HackFS {
+	D d;
+	FSLocal fs;
+
+	public HackFS(D d) {
+		this.d = d.klon(this);
+		fs = new FSLocal(d);
+	}
+
+	/**
+	 * Zkouška chování caseless filesystému při kolizi jmen v situacích vytváření file a directory
+	 * při použití alternativně DocumentFile a File.
+	 */
+	public void caselessFS() {
+		String cwd = "/storage/extSdCard/HH/odpad";
+		d.l(4, String.format("caselessFS() on SD card, cwd path=%s, SD card root path=%s", cwd, K.SD_CARD_MOUNT_POINT));
+		try {
+			fs.doCreateDir(cwd);
+			operateOnDocFiles(fs.getSdCardDocFile(cwd), cwd);
+		}
+		catch(Exception e) {
+			d.l(String.format("working directory %s not found or isn't a directory", cwd));
+		}
+
+		cwd = "/storage/emulated/0/_HH/odpad";
+		d.l(4, String.format("caselessFS() on doc files in builtin storage, cwd path=%s", cwd));
+		if(new File(cwd).mkdirs() || new File(cwd).isDirectory())
+			operateOnDocFiles(DocumentFile.fromFile(new File(cwd)), cwd);
+			//operateOnFiles(cwd);
+		else d.l(String.format("working directory %s not found or isn't a directory", cwd));
+	}
+
+	void operateOnDocFiles (DocumentFile parentDf, String parentPath) { // parentPath je absolutní
+		//DocumentFile parentDf = DocumentFile.fromFile(cwdF);
+		for(String fn : new String[]{"hH", "Hh", "hh", "HH"}) {
+			File f = new File(parentPath, fn);
+			d.l(String.format("path %s exists=%b, is directory=%b, found=%b", fn, f.exists(), f.isDirectory(), parentDf.findFile(fn) != null));
+			createDocfDir(parentDf, fn);
+			createDocfFile(parentDf, fn);
+		}
+		FragFM.refreshNotify(true);
+	}
+
+	void createDocfFile(DocumentFile parentDf, String fn) {  // fn bez cesty
+		DocumentFile df = parentDf.findFile(fn);
+		if(df != null && df.isFile()) d.l(String.format("file %s is ready", fn));
+		else if(df == null) {
+			df = parentDf.createFile("application/octet-stream", fn);
+			if(df != null) {
+				if(df.getName().equals(fn)) d.l(String.format("file %s created", fn));
+				else {
+					df.delete();
+					d.l(String.format("file %s can't be created, name already used", fn));
+				}
+			}
+			else d.l(String.format("file %s not created for unknown reason", fn));
+		}
+		else d.l(String.format("file  %s can't be created, name already in use", fn));
+	}
+
+	void createDocfDir(DocumentFile parentDf, String fn) {  // fn bez cesty
+		DocumentFile df = parentDf.findFile(fn);
+		if(df != null && df.isDirectory()) d.l(String.format("dir  %s is ready", fn));
+		else if(df == null) {
+			df = parentDf.createDirectory(fn);
+			if(df != null) {
+				if(df.getName().equals(fn)) d.l(String.format("dir  %s created", fn));
+				else {
+					df.delete();
+					d.l(String.format("dir  %s can't be created, name already in use", fn));
+				}
+			}
+			else d.l(String.format("dir  %s not created for unknown reason", fn));
+		}
+		else d.l(String.format("dir  %s can't be created, name already in use", fn));
+	}
+
+	void operateOnFiles (String parentPath) {
+		for(String fn : new String[]{"hh", "hH", "Hh", "HH"}) {
+			File f = new File(parentPath, fn);
+			d.l(String.format("path %s found=%b, is directory=%b", fn, f.exists(), f.isDirectory()));
+			try {
+				if(f.createNewFile()) d.l(String.format("file %s created", fn));
+				else d.l(String.format("file %s can't be created, name already used", fn));
+			} catch(Exception e) {
+				d.lmwa(e.getMessage());
+			}
+
+			if(f.mkdir()) d.l(String.format("dir  %s created", fn));
+			else d.l(String.format("dir  %s can't be created, name already used", fn));
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackFile.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,29 @@
+package hh.dejsem.hack;
+
+import android.os.Environment;
+
+import java.io.File;
+
+import hh.lib.D;
+
+public class HackFile {
+
+	D d;
+
+	HackFile(D d) { this.d = d.klon(this); }
+
+	void fileStorageTest() {
+		d.l(String.format("+++ getDataDirectory()=%s", Environment.getDataDirectory().getPath()));
+		d.l(String.format("+++ getDownloadCacheDirectory()=%s", Environment.getDownloadCacheDirectory().getPath()));
+		d.l(String.format("+++ getExternalStorageDirectory()=%s", Environment.getExternalStorageDirectory().getPath()));
+		d.l(String.format("+++ getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS)=%s", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath()));
+		d.l(String.format("+++ getExternalStorageState()=%s", Environment.getExternalStorageState(new File("/storage/extSdCard/HH"))));
+		d.l(String.format("+++ getRootDirectory()=%s", Environment.getRootDirectory()));
+		d.l(String.format("+++ isExternalStorageEmulated()=%b", Environment.isExternalStorageEmulated()));
+		d.l(String.format("+++ isExternalStorageRemovable()=%b", Environment.isExternalStorageRemovable()));
+
+		File f= new File(Environment.getExternalStorageDirectory(), "_HH/tmp/f");
+		d.l(String.format("set timestamp=%b", f.setLastModified(1000*1000*1000*1000L)));
+		for(String fn: (new File(Environment.getExternalStorageDirectory(), "_HH/tmp").list())) d.l("+++ " + fn);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackNotifDialogAct.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,36 @@
+package hh.dejsem.hack;
+
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import hh.dejsem.Act;
+import hh.lib.NoticeDialogFragment;
+
+public class HackNotifDialogAct extends Act implements DialogInterface.OnClickListener {
+
+	static int cntr = 0;
+
+	@Override
+	public void onCreate(Bundle b) {
+		super.onCreate(b);
+		new Vlákno(cntr++, this).start();
+		new Vlákno(9000 + cntr++, this).start();
+	}
+
+	@Override
+	public void onClick(DialogInterface dialog, int which) {
+		d.l("finishing...");
+		finish(); }
+
+	class Vlákno extends Thread {
+		int cntr;
+		DialogInterface.OnClickListener lstnr;
+		Vlákno(int cntr, DialogInterface.OnClickListener lstnr) {
+			this.cntr = cntr;
+			this.lstnr = lstnr;
+		}
+		public void run() {
+			new NoticeDialogFragment().display(d, "HACK["+(cntr)+"]", "candy is dandy", lstnr);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackNotifListAct.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,131 @@
+package hh.dejsem.hack;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import hh.dejsem.Act;
+import hh.dejsem.R;
+import hh.lib.D;
+
+public class HackNotifListAct extends Act implements DialogInterface.OnKeyListener, DialogInterface.OnClickListener, View.OnClickListener {
+	static int cnt = 0;
+	static ArrayList<LinearLayout> statSet = null;
+
+	HackListAdapter la = null;
+	TextView progressFiles, progressFn, progressNum;
+	ProgressBar progressBar;
+	ImageView cancelButt;
+	int toRemove = -1;
+
+	@Override
+	public void onCreate(Bundle b) {
+		super.onCreate(b);
+		statSet = new ArrayList<LinearLayout>();
+		statSet.add(fillLv(1));
+		statSet.add(fillLv(2));
+		statSet.add(fillLv(3));
+		la = new HackListAdapter(d);
+		new AlertDialog.Builder(this)
+				.setAdapter(la, this)
+				.setTitle("test notif activity")
+				.setOnKeyListener(this)
+				.show();
+	}
+
+	@Override
+	public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+		d.l(String.format("+++ onKey, keyCode=%d, KeyEvent.KEYCODE_BACK=%d, dialog interface=%s", keyCode, KeyEvent.KEYCODE_BACK, dialog));
+		if(keyCode == KeyEvent.KEYCODE_BACK) {
+			d.l("+++ key down");
+			finishActivity(dialog);
+			return true;
+		}
+		return super.onKeyDown(keyCode, event);
+	}
+
+	@Override
+	public void onClick(DialogInterface dialog, int which) {
+		d.l("+++ onClick, which=" + which);
+		if(which == DialogInterface.BUTTON_POSITIVE && toRemove > -1) {
+			HackNotifListAct.statSet.remove(toRemove);
+			toRemove = -1;
+			if(statSet.size() == 0) finishActivity(dialog);
+			else la.notifyDataSetChanged();
+		}
+	}
+
+	@Override
+	public void onClick(View v) {
+		d.l("+++ onClick, view tag=" + v.getTag());
+		toRemove = ((int)(v.getTag()) - 1);
+		new AlertDialog.Builder(this)
+				.setMessage("cancel ?")
+				.setNegativeButton("cancel", null)
+				.setPositiveButton("OK", this)
+				.setOnKeyListener(this)
+				.show();
+	}
+
+	void finishActivity(DialogInterface dialog) {
+		if(dialog != null) dialog.dismiss();
+		finish();
+	}
+
+	LinearLayout fillLv(int position) {
+		final LayoutInflater lInflater = LayoutInflater.from(d.getContext());
+		LinearLayout lv = (LinearLayout)lInflater.inflate(R.layout.progress_entry, null);
+		progressFiles = ((TextView)(lv.findViewById(R.id.progressFiles)));
+		progressFiles.setText(String.valueOf(position*3));
+		progressFn = ((TextView)(lv.findViewById(R.id.progressFn)));
+		progressFn.setText("test file " + position);
+		progressNum = ((TextView)(lv.findViewById(R.id.progressNum)));
+		progressNum.setText(String.valueOf(position));
+		progressBar = ((ProgressBar)(lv.findViewById(R.id.progressBar)));
+		progressBar.setProgress(position*20);
+		cancelButt = ((ImageView)(lv.findViewById(R.id.cancel)));
+		cancelButt.setTag(position);
+		cancelButt.setClickable(true);
+		cancelButt.setOnClickListener((View.OnClickListener)(d.getAct()));
+		return lv;
+	}
+
+	class HackListAdapter extends BaseAdapter {
+		/**----------------------------------------------------------
+		 * progress list adapter
+		 * ----------------------------------------------------------*/
+		D d;
+		/* ----------------------------------------------------------*/
+
+		HackListAdapter(D d) { this.d = d.klon(this); }
+
+		public int getCount() { return statSet.size(); }
+
+		public Object getItem(int position) { return null; }
+
+		public long getItemId(int position) { return position; }
+
+		public int getViewTypeCount() { return 8; }
+
+		public View getView(int position, View listEntry, ViewGroup parent) {
+			d.l(String.format("getView, position=%d, view=%x", position, listEntry == null ? 0 : listEntry.hashCode()));
+			if(position < statSet.size()) {
+				LinearLayout lv = HackNotifListAct.statSet.get(position);
+				if(d.ll(5)) d.l(String.format("getView return, position=%d, listEntry=%x, listRow=%x", position, listEntry.hashCode(), lv.hashCode()));
+				return lv;
+			}
+			else return null;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackPanelMenu.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,58 @@
+package hh.dejsem.hack;
+
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+
+import java.util.ArrayList;
+
+import hh.lib.D;
+
+public class HackPanelMenu {
+
+	enum PanelMenuItem {
+		M1() { void action(HackContextMenu target) throws Exception { target.handleMenuItem11(); } },
+		M2("zkouška 2"),
+		M3("zkouška 3") { void action(HackContextMenu target) throws Exception { target.handleMenuItem33(); } };
+
+		final int i;
+		final String title;
+		void action(HackContextMenu target) throws Exception {};
+
+		PanelMenuItem() {
+			i = ordinal();
+			title = name();
+		}
+		PanelMenuItem(String n) {
+			i = ordinal();
+			this.title = n;
+		}
+	}
+
+	D d;
+	ArrayList<PanelMenuItem> items = new ArrayList<PanelMenuItem>();
+
+	HackPanelMenu(D d) {
+		this.d = d.klon(this);
+		for(PanelMenuItem p: PanelMenuItem.values()) items.add(p.ordinal(), p);
+	}
+
+	void createContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+		d.l("createContextMenu");
+		menu.add(Menu.NONE, PanelMenuItem.M1.ordinal(), Menu.NONE, PanelMenuItem.M1.title);
+		menu.add(Menu.NONE, PanelMenuItem.M2.ordinal(), Menu.NONE, PanelMenuItem.M2.title);
+		menu.add(Menu.NONE, PanelMenuItem.M3.ordinal(), Menu.NONE, PanelMenuItem.M3.title);
+		menu.add(Menu.NONE, 22, Menu.CATEGORY_CONTAINER + 1, "expl. zkouška 2");
+		menu.add(Menu.NONE, 33, Menu.CATEGORY_CONTAINER + 2, "expl. zkouška 3");
+	}
+
+	boolean handleContextItemSelected(MenuItem item, HackContextMenu target) {
+		d.l("handleContextItemSelected, item=" + items.get(item.getItemId()));
+		if(item.getItemId() < items.size())
+			try { items.get(item.getItemId()).action(target); }
+			catch(Exception e) {}
+			finally { return true; }
+		return false;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackUDP.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,84 @@
+package hh.dejsem.hack;
+
+import android.content.Context;
+import android.net.DhcpInfo;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.StrictMode;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+
+import hh.dejsem.Act;
+import hh.dejsem.net.NetUDP;
+
+public class HackUDP extends Act {
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		if(NetUDP.localAddr == null) {
+			//new NetUDP(d);
+			d.l(String.format("LAN broadcast=%s, myip=%s, hostname=%s",
+					NetUDP.broadcastAddr.getHostAddress(), NetUDP.localAddr.getHostAddress(), getLocalHostName()));
+		}
+
+//		dgramReceive();
+		dgramBcast("helemese nemelese");
+	}
+
+    String getLocalHostName() {
+		StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
+		StrictMode.setThreadPolicy(policy);
+		String hostname;
+	    try  { hostname = InetAddress.getLocalHost().getHostName(); }
+	    catch(UnknownHostException e) { hostname = null; }
+	    return hostname;
+	}
+
+	InetAddress getLocalIp() {
+		try {
+			Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
+//			if(en.hasMoreElements()) {
+				for(; en.hasMoreElements(); ) {
+					NetworkInterface i = en.nextElement();
+					for(Enumeration<InetAddress> enumIpAddr = i.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
+						InetAddress ia = enumIpAddr.nextElement();
+						if( !ia.isLoopbackAddress() &&
+							!ia.toString().substring(0, 0).getBytes().equals((byte) -128) &&
+							Inet4Address.class.isInstance(ia))
+							return ia;
+					}
+				}
+//			}
+		}
+		catch(SocketException e) { d.abendMsg(e); }
+		return null;
+	}
+
+	InetAddress getBroadcastAddress() {
+		InetAddress broadcastAddr = null;
+		try {
+			DhcpInfo dhcp = ((WifiManager)getApplicationContext().getSystemService(Context.WIFI_SERVICE)).getDhcpInfo();
+			if(dhcp != null && dhcp.ipAddress != 0) broadcastAddr = int2ia((dhcp.ipAddress & dhcp.netmask) | ~dhcp.netmask);
+		}
+		catch(Exception e) { d.abendMsg("get broadcast address", e); }
+		return broadcastAddr;
+	}
+
+	void dgramReceive() { new HackDgrams(d, HackDgrams.REC).start(); }
+
+	void dgramBcast(String msg) { new HackDgrams(d, HackDgrams.SEND, msg, NetUDP.broadcastAddr).start(); }
+
+	void dgramClose() { new HackDgrams(d, HackDgrams.CLOSE).start(); }
+
+	InetAddress int2ia(int ia) throws UnknownHostException {
+		byte[] quad = new byte[4];
+		for(int k = 0; k < 4; k++) quad[k] = (byte)((ia >> k * 8) & 0xFF);
+		return InetAddress.getByAddress(quad);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackViewDialog.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,123 @@
+package hh.dejsem.hack;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.support.v4.app.DialogFragment;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.View;
+
+import hh.dejsem.K;
+import hh.lib.D;
+
+public class HackViewDialog extends DialogFragment {
+
+	public static interface ViewDialogView { View getView(); }
+
+	static final String DIALOG_TAG_KEY = "TAG";
+	static final String DIALOG_TYPE_KEY = "TYPE";
+	static final int DIALOG_TYPE_VIEW = 0;
+	static final int DIALOG_TYPE_TEXT = 1;
+	static final String DIALOG_TITLE_KEY = "TITLE";
+	static final String DIALOG_TEXT_KEY = "TEXT";
+	static final String TAG = "notice dialog";
+
+	D d;
+	int dialogType = DIALOG_TYPE_TEXT;
+	public ViewDialogView getView = null;
+	String title = null;
+	String msg = null;
+	DialogInterface.OnClickListener lstnr;
+	String tag;
+	boolean restart = false;
+	int counter = 0;
+	AlertDialog dialog;
+
+	@Override
+	public void onSaveInstanceState(Bundle b) {
+		super.onSaveInstanceState(b);
+		restart = true;
+		b.putString(DIALOG_TAG_KEY, tag);
+		K.base.put(tag, this);
+			/*if(dialogType == DIALOG_TYPE_TEXT) {
+				b.putString(DIALOG_TITLE_KEY, title);
+				b.putString(DIALOG_TEXT_KEY, msg);
+			}*/
+//			d.l(2, "onSaveInstanceState");
+	}
+
+	@Override
+	public Dialog onCreateDialog(Bundle b) {
+		if(b != null) restore(b.getString(DIALOG_TAG_KEY));
+		restart = false;
+		counter += 1;
+		d.l("+++ onCreateDialog, counter=" + counter);
+
+			/*if(b != null) {
+				dialogType = b.getInt(DIALOG_TYPE_KEY, DIALOG_TYPE_TEXT);
+				if(dialogType == DIALOG_TYPE_TEXT) {
+					title = b.getString(DIALOG_TITLE_KEY);
+					msg = b.getString(DIALOG_TEXT_KEY);
+				}
+			}*/
+
+		if(true) {
+			final AlertDialog.Builder builder = new AlertDialog.Builder(this.getActivity());
+			d.l("+++ onCreateDialog, setting view=" + getView.hashCode());
+			if(dialogType == DIALOG_TYPE_VIEW && getView != null) builder.setView(getView.getView());
+			else if(dialogType == DIALOG_TYPE_TEXT) builder.setTitle(title).setMessage(msg);
+			d.l("+++ onCreateDialog, view set");
+			builder.setCancelable(true).setPositiveButton("OK", lstnr);
+			dialog = builder.create();
+			return dialog;
+		}
+		else {
+			final AlertDialog ad = (AlertDialog)getDialog();
+			ad.setView(getView.getView());
+			ad.setButton(DialogInterface.BUTTON_POSITIVE, "OK", lstnr);
+			return ad;
+		}
+	}
+
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+	}
+
+	public void display(D d, String tag, ViewDialogView getView, DialogInterface.OnClickListener lstnr) {
+		this.getView = getView;
+		dialogType = DIALOG_TYPE_VIEW;
+		this.display(d, tag, lstnr);
+	}
+
+	public void display(D d, String tag, String title, String msg, DialogInterface.OnClickListener lstnr) {
+		this.title = title;
+		this.msg = msg;
+		dialogType = DIALOG_TYPE_TEXT;
+		this.display(d, tag, lstnr);
+	}
+
+	public void display(D d, String tag, DialogInterface.OnClickListener lstnr) {
+		this.d = d.klon((Object)this);
+		this.d.setFragment(this);
+		this.d.l("+++ display");
+		this.tag = tag;
+		this.lstnr = lstnr;
+		show(d.getFmgr(), tag);
+	}
+
+	private void restore(String tag) {
+		final HackViewDialog df = (HackViewDialog) K.base.get(tag);
+		this.d = df.d;
+		this.d.setFragment(this);
+		d.l("+++ restore");
+		this.dialogType = df.dialogType;
+		this.getView = df.getView;
+		this.title = df.title;
+		this.msg = df.msg;
+		this.lstnr = df.lstnr;
+		this.tag = df.tag;
+		this.restart = df.restart;
+		this.counter = df.counter;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/lastmile/LastMileMeterFrag.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,80 @@
+package hh.dejsem.lastmile;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import hh.dejsem.K;
+import hh.dejsem.R;
+import hh.dejsem.net.NetNode;
+import hh.lib.D;
+import hh.lib.DF;
+
+/**
+ * last mile throughput meter Activity
+ */
+public class LastMileMeterFrag extends DF implements View.OnClickListener {
+
+	final static String TITLETEXT = "last mile";
+
+	public static LastMileMeterFrag instantiate(D d) {
+		LastMileMeterFrag lf = new LastMileMeterFrag();
+		lf.d = d.klon(lf);
+		return lf;
+	}
+
+	LastMileMeterRun netTask = null;
+
+	@Override
+	public void onCreate(Bundle b) {
+		super.onCreate(b);
+	}
+
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+		super.onCreateView(inflater, container, savedInstanceState);
+
+		View lastMile = inflater.inflate(R.layout.last_mile, null);
+		lastMile.findViewById(R.id.intrr).setOnClickListener(this);
+		d.tv = (TextView)lastMile.findViewById(R.id.tv);
+		d.sc = (ScrollView)lastMile.findViewById(R.id.sc);
+		return lastMile;
+	}
+
+	@Override
+	public void onResume() {
+		super.onResume();
+		K.title.setText(TITLETEXT).update();
+		try {
+			if (K.netNode == null) K.netNode = new NetNode(d);
+			run();
+		}
+		catch(Exception e) { d.abendMsg(e); }
+	}
+
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		if(netTask != null) netTask.cancel(false);
+	}
+
+	@Override
+	public void onClick(View v) {
+		if(netTask != null) {
+			if (netTask.inProgress) {
+				netTask.cancel(false);
+				netTask.inProgress = false;
+			} else run();
+		}
+	}
+
+	void run() {
+		if(netTask == null || !netTask.inProgress) {
+			netTask = new LastMileMeterRun(d);
+			netTask.execute();
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/lastmile/LastMileMeterRun.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,132 @@
+package hh.dejsem.lastmile;
+
+import android.os.AsyncTask;
+
+import java.io.BufferedInputStream;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.URL;
+import java.util.Date;
+
+import hh.dejsem.K;
+import hh.dejsem.Prefs;
+import hh.dejsem.net.NetData;
+import hh.dejsem.net.NetNode;
+import hh.dejsem.net.NetScIO;
+import hh.lib.D;
+
+public class LastMileMeterRun extends AsyncTask<Void, String, Void> {
+	/**----------------------------------------------------------
+	 * network operations of last mile throughput meter
+	 * ----------------------------------------------------------*/
+	D d;
+	Exception exception;
+	NetScIO nonSslIO = null;
+	long t00, t0, td, n, nn;
+	public boolean inProgress = true;
+
+	public LastMileMeterRun(D d) {
+		this.d = d.klon(this);
+	}
+
+	@Override
+	protected Void doInBackground(Void... params) {
+		if(d.ll(4)) d.l("doInBackground starting...");
+		exception = null;
+		try {
+			if(K.netNode == null) K.netNode = new NetNode(d);
+			meter();
+		}
+		catch(Exception e) { exception = e; }
+		if(d.ll(4)) d.l("doInBackground finishing, exc=" + exception);
+		return null;
+	}
+
+	@Override
+	protected void onPostExecute(Void res) {
+		if(d.ll(4)) d.l("onPostExecute, exc=" + exception);
+		inProgress = false;
+		if(exception != null) d.abendMsg(exception);
+	}
+
+	protected void onCancelled() { inProgress = false; }
+
+	@Override
+	protected final void onProgressUpdate(String... s) { d.ltv(s == null ? null : s[0]); }
+
+	void meter() {
+		HttpURLConnection urlConn = null;
+		n = 0;
+		nn = 0;
+		try {
+			String srv = Prefs.host;
+			publishProgress("open connection to " + srv);
+			urlConn = (HttpURLConnection)new URL("http://" + srv + "/hh.1M").openConnection();
+			BufferedInputStream in = new BufferedInputStream(urlConn.getInputStream());
+			publishProgress("DOWNLOAD stream of 1MB");
+			byte[] buf = new byte[8192];
+			t00 = new Date().getTime();
+			t0 = t00;
+			int r;
+			while(!isCancelled() && (r = in.read(buf)) > -1) {
+				n += r;
+				td = new Date().getTime() - t0;
+				if(td > 1000 || n > 127*1024) report();
+			}
+			td = new Date().getTime() - t00;
+			publishProgress(String.format("finish, % 5.3f secs. elaspsed, %s", 0.001 * td, rate(nn, td)));
+
+			if(!isCancelled()) {
+				publishProgress("UPLOAD stream of 1MB");
+				nonSslIO = meterUpConn();
+				t00 = new Date().getTime();
+				t0 = t00;
+				nn = 0;
+				n = 0;
+				for(int i=0; i < 64; i++) {
+					if(isCancelled()) { nonSslIO.closeSc(); break; }
+					n += meterUpSend(nonSslIO, 16*1024);
+					td = new Date().getTime() - t0;
+					if(td > 1000 || n > 127*1024) report();
+				}
+				td = new Date().getTime() - t00;
+				publishProgress(String.format("finish, % 5.3f secs. elaspsed, %s", 0.001 * td, rate(nn, td)));
+			}
+		}
+		catch(Exception e) { this.exception = e; }
+		finally {
+			if(urlConn != null) urlConn.disconnect();
+			if(nonSslIO != null) try { nonSslIO.closeSc(); } catch(Exception e) {}
+		}
+	}
+
+	NetScIO meterUpConn() throws Exception {
+		NetScIO netIO = new NetScIO(d);
+		int port = K.BASE_PORT_NUM;
+		if(d.ll(3)) d.l("start meter up, host=" + Prefs.host + ", port=" + port);
+		netIO.connect(new InetSocketAddress(Prefs.host, port));
+		return netIO;
+	}
+
+	int meterUpSend(NetScIO netIO, int l) throws Exception {		
+		NetData data = new NetData(d, (l < 10 ? 10 : l));
+		netIO.put(data.buf);
+		netIO.out.flush();
+		if(d.ll(5)) d.l(String.format("%d bytes uploaded", l));
+		data.buf.rewind();
+		data.buf.limit(10);
+		while(data.buf.position() < 10) netIO.get(data.buf);
+		data.unloadToByte();
+		if(new String(data.nb, 0, 2).equals("=>")) return Integer.valueOf(new String(data.nb, 2, 8));
+		else throw new Exception("bad number got");
+	}
+
+	void report() throws Exception {
+		publishProgress(rate(n, td));
+		t0 = new Date().getTime();
+		nn += n;
+		n = 0;
+	}
+
+	String rate(long n, long td) { return String.format("% 3.3f MB/s", 1000.0 * n / (1024 * 1024 * td)); }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/ExposeFile.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,33 @@
+package hh.dejsem.net;
+
+import hh.dejsem.K;
+import hh.dejsem.SendUriNG;
+import hh.dejsem.fm.NetTaskRef;
+import hh.lib.D;
+
+/**
+ * expose to HTTP on server
+ * ● copy file asynchronously to dejsem server
+ * ● get path segment of URL from server and share it
+ */
+public class ExposeFile extends ServerCopy {
+
+	String uriPath;
+
+	public ExposeFile(D d, NetTaskRef netTaskRef) throws Exception  {
+		super(d, netTaskRef);
+	}
+
+	@Override
+	protected Void doInBackground(Void... params) {
+		super.doInBackground();
+//		String fn = args.getString(K.FILES_FROM_KEY);
+		if(netTaskRef.go)
+			try { uriPath = netConn.pushFileExpose(); }
+			catch(Exception e) { if(!K.End.class.isInstance(e)) abend("", e); }
+		return null;
+	}
+
+	@Override
+	void completePostExecute() { SendUriNG.decideSendUri(uriPath); }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/ExposeStream.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,34 @@
+package hh.dejsem.net;
+
+import hh.dejsem.K;
+import hh.dejsem.SendUriNG;
+import hh.dejsem.fm.NetTaskRef;
+import hh.lib.D;
+
+/**
+ * expose to HTTP on server (copy asynchronously)
+ */
+public class ExposeStream extends LongAsyncTask {
+
+	NetConnSrv netConn;
+	String uriPath;
+
+	public ExposeStream(D d, NetTaskRef netTaskRef) throws Exception  {
+		super(d, netTaskRef);
+		int port = args.getInt(K.PORT_KEY, 0);
+		netConn = new NetConnSrv(this.d, String.format("[%d]", port), netTaskRef);
+	}
+
+	@Override
+	protected Void doInBackground(Void... params) {
+		super.doInBackground();
+		if(netTaskRef.go)
+			try { uriPath = netConn.pushStreamExpose(); }  // expose data and get exposed path
+			catch(Exception e) { if(!K.End.class.isInstance(e)) abend("EXPOSE stream to http on server", e); }
+			finally { try { netConn.connClose(); } catch(Exception e) {} }
+		return null;
+	}
+
+	@Override
+	void completePostExecute() { SendUriNG.decideSendUri(uriPath); }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/FileIO.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,63 @@
+package hh.dejsem.net;
+
+import android.net.Uri;
+import android.support.v4.provider.DocumentFile;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+import hh.lib.D;
+
+/**
+ * file I/O interface
+ */
+public class FileIO implements GetPut {
+	D d;
+	FileChannel fc = null;
+	// ----------------------------------------------------------
+
+	public FileIO(D d, DocumentFile parentDf, File f, String mode) throws Exception {    // pro nový file na SD card
+		this.d = d.klon(this);
+		if(!mode.equals("r")) {
+			Uri dfUri = parentDf.createFile("application/octet-stream", f.getName()).getUri();
+			fc = ((FileOutputStream)(d.getAct().getContentResolver().openOutputStream(dfUri))).getChannel();
+			if(this.d.ll(4)) this.d.l("created, file path=" + f.getAbsolutePath());
+		}
+		else fc = new RandomAccessFile(f, mode).getChannel();
+	}
+
+	public FileIO(D d, File f, String mode) throws Exception {
+		this.d = d.klon(this);
+		fc = new RandomAccessFile(f, mode).getChannel();
+		if(d.ll(4) && mode.equals("rw")) this.d.l("created, file path=" + f.getAbsolutePath());
+	}
+
+	public boolean get(ByteBuffer buf) throws Exception {
+		if(d.ll(5)) d.l("get from file, buf=" + D.bufStat(buf));
+		try { while(buf.hasRemaining()) if(fc.read(buf) < 0) break; }
+		catch(Exception x) { close(); throw new Exception("file channel read", x); };
+		if(d.ll(5)) d.l("" + buf.position()+ " read");
+		return buf.position() > 0;
+	}
+
+	public int put(ByteBuffer buf) throws Exception {
+		int n = 0;
+		if(d.ll(5)) d.l("output to file, buf=" + D.bufStat(buf));
+		while(buf.hasRemaining()) {
+			n = 0;
+			try { n = fc.write(buf); }
+			catch(Exception x) { close(); throw new Exception("file channel write", x); }
+			if(d.ll(5)) d.l("" + n + " written"); }
+		buf.clear();
+		return n;
+	}
+
+	public void close() {
+		try { fc.close(); }
+		catch(IOException e) { d.l("close file channel" + e.getMessage()); }
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/GetPeerHostThread.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,16 @@
+package hh.dejsem.net;
+
+import hh.lib.D;
+
+public class GetPeerHostThread extends Thread {
+	D d;
+	public Object result;
+	public Exception exception = null;
+
+	GetPeerHostThread(D d) { this.d = d.klon(this); }
+
+	public void run() {
+		try { result = new NetUDP(d).keepReceivingPeerIp(); }
+		catch (Exception e) { this.exception = e; }
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/GetPut.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,8 @@
+package hh.dejsem.net;
+
+import java.nio.ByteBuffer;
+
+public interface GetPut {
+	boolean get(ByteBuffer buf) throws Exception;
+	int put(ByteBuffer buf) throws Exception;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/InChIO.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,34 @@
+package hh.dejsem.net;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+
+import hh.lib.D;
+
+/**
+ * channel I/O interface
+ */
+public class InChIO /*implements GetPut*/ {
+	D d;
+	ReadableByteChannel ch = null;
+	// ----------------------------------------------------------
+
+	public InChIO(D d, ReadableByteChannel ch) throws Exception {
+		this.d = d.klon(this);
+		this.ch = ch;
+	}
+
+	public boolean get(ByteBuffer buf) throws Exception {
+		if(d.ll(5)) d.l("get from channel, buf=" + D.bufStat(buf));
+		try { while(buf.hasRemaining()) if(ch.read(buf) < 0) break; }
+		catch(Exception x) { close(); throw new Exception("channel read", x); };
+		if(d.ll(5)) d.l("" + buf.position()+ " read");
+		return buf.position() > 0;
+	}
+
+	public void close() {
+		try { ch.close(); }
+		catch(IOException e) { d.l("close channel" + e.getMessage()); }
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/LongAsyncTask.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,89 @@
+package hh.dejsem.net;
+
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.RemoteException;
+
+import hh.dejsem.K;
+import hh.dejsem.fm.NetTaskRef;
+import hh.dejsem.fm.TransferAlert;
+import hh.lib.D;
+
+/**
+ * asynchronous task for long job
+ */
+public class LongAsyncTask extends AsyncTask<Void, Void, Void> {
+	D d;
+	NetTaskRef netTaskRef;
+	Bundle args;
+
+	LongAsyncTask(D d, NetTaskRef netTaskRef) throws Exception {
+		this.d = d.klon(this);
+		this.netTaskRef = netTaskRef;
+		args = netTaskRef.getArgs();
+	}
+
+	@Override
+	protected void onPreExecute() {
+		if(d.ll(4)) d.l("onPreExecute");
+		K.transfProgress.registerTask(netTaskRef);
+	}
+
+	@Override
+	protected Void doInBackground(Void... params) {
+		if(d.ll(4)) d.l("executing asynchronously...");
+		return null;
+	}
+
+	@Override
+	protected void onPostExecute(Void v) {
+		if(d.ll(4)) d.l("onPostExecute");
+		K.transfProgress.unRegisterTask(netTaskRef);
+		completePostExecute();
+	}
+
+	@Override
+	protected void onProgressUpdate(Void... v) { if(d.ll(4)) d.l("onProgressUpdate"); }
+
+	void completePostExecute() {};
+
+	void notifyRefresh() {
+		try {
+			if(netTaskRef.d.actMsgr != null) netTaskRef.d.actMsgr.send(Message.obtain(null, K.MSG_REFRESH_LOC));
+			if(netTaskRef.failedTimeStamp)     // bug in setLastModified()
+				warning("at least one timestamp failed to be set to its original value due to device dependent bug in system");
+		}
+		catch (RemoteException x) { abend("error when notifying for refresh", x); }
+	}
+
+	void notifyRefreshFileList(int what) {
+		try { if(netTaskRef.d.actMsgr != null) netTaskRef.d.actMsgr.send(Message.obtain(null, what)); }
+		catch (RemoteException x) { abend("error when notifying for refresh", x); }
+	}
+
+	void warning(String txt) {
+		K.app.startActivity(new Intent(K.app, TransferAlert.class)
+				.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+				.putExtra(K.ACTION_KEY, K.TRANSFER_WARNING)
+				.putExtra(K.TXT_KEY, txt)
+		);
+	}
+
+	/**
+	 * Ošetření ABENDu v prostředí nitě běžící na pozadí - spouští se aktivita s pop-up window.
+	 *
+	 * @param symptom the symptom
+	 * @param e       the e
+	 */
+	void abend(String symptom, Exception e) {
+		d.lvab(symptom, e);
+		K.transfProgress.unRegisterTask(netTaskRef);
+		K.app.startActivity(new Intent(K.app, TransferAlert.class)
+				.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)     /*Intent.FLAG_ACTIVITY_SINGLE_TOP)*/
+				.putExtra(K.ACTION_KEY, K.TRANSFER_ABEND)
+				.putExtra(K.TXT_KEY, d.composeAbendMsg(symptom, e))
+		);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetChIO.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,26 @@
+package hh.dejsem.net;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+
+import hh.lib.D;
+
+public class NetChIO {
+	/**----------------------------------------------------------
+	 * nonSSL non-blocking network IO
+	 * ----------------------------------------------------------*/
+	D d;
+	ServerSocket ssc;
+	Socket sc = null;
+	InputStream in = null;
+	OutputStream out = null;
+	ReadableByteChannel ic;
+	WritableByteChannel oc;
+	/* ----------------------------------------------------------*/
+
+	NetChIO(D d) { this.d = d.klon(this); }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetChOps.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,51 @@
+package hh.dejsem.net;
+
+import java.nio.ByteBuffer;
+
+import hh.lib.D;
+
+/**
+ * helpers for some specific operations on IO channel (java.nio)
+ */
+public class NetChOps {
+	static String tag = "NetChOps";
+
+	public static long getNum(D d, GetPut io) throws Exception {
+		ByteBuffer buf = ByteBuffer.allocate(12);
+		while(buf.remaining() > 0) if(!io.get(buf)) break;
+		if(buf.remaining() > 0) return -2;
+		if(d.ll(5)) d.log(tag + ".getNum", "read ByteBuffer=" +  D.bufStat(buf));
+		buf.flip();
+		byte[] b = new byte[12];
+		buf.get(b);
+		if(d.ll(5)) d.log(tag + ".getNum", String.format("num read=%d", Long.valueOf(new String(b))));
+		return Long.valueOf(new String(b));
+	}
+
+	public static void putNum(D d, long n, GetPut io) throws Exception {
+		if(d.ll(5)) d.log(tag + ".putNum", String.format("num to put=%012d", n));
+		io.put(ByteBuffer.wrap(String.format("%012d", n).getBytes()));
+	}
+
+	public static String getStr(D d, GetPut io) throws Exception {
+		int len = (int)getNum(d, io);
+		if(d.ll(5)) d.log(tag + ".getStr", "length of string to get=" + len);
+		if(len == -2) return null;		// EOD in input data
+		if(len == 0) return "";
+		ByteBuffer buf = ByteBuffer.allocate(len);
+		while(buf.remaining() > 0) { if(!io.get(buf)) break; }
+		buf.flip();
+		byte[] b = new byte[len];
+		buf.get(b);
+		String got = new String(b, 0, len);
+		if(d.ll(5)) d.log(tag + ".getStr", "string got=" + got);
+		return got;
+	}
+
+	public static void putStr(D d, String fn, GetPut io) throws Exception {
+		if(d.ll(5)) d.log(tag + ".putStr", "string to put=" + fn);
+		byte[] b = fn.getBytes();
+		putNum(d, b.length, io);
+		io.put(ByteBuffer.wrap(fn.getBytes()));
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetConn.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,186 @@
+package hh.dejsem.net;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+import java.io.File;
+import java.net.InetSocketAddress;
+
+import hh.dejsem.K;
+import hh.dejsem.Prefs;
+import hh.dejsem.fm.EntitySize;
+import hh.dejsem.fm.FSLocal;
+import hh.dejsem.fm.FSServer;
+import hh.lib.D;
+
+/**
+ * - base class of data operations over network SSL connection
+ * - all methods are called from subprocess of main and may perform net opers
+ */
+public abstract class NetConn {
+	D d;
+	int port = -1;
+	NetData netData;
+	public NetSslScIO netIO = null;
+	FSLocal localFS;
+	public FSServer remoteFS;
+	FileIO fio;
+	FileInfo fileInfo;
+	File file = null;
+	// ----------------------------------------------------------
+
+	static ConnectivityManager cm = null;
+
+	public static boolean netAvailable(D d) {
+		boolean avail = false;
+		if(cm == null) cm = (ConnectivityManager)d.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+		NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
+		if(activeNetworkInfo != null) avail = activeNetworkInfo.getState() == NetworkInfo.State.CONNECTED;
+		if(!avail) d.lmwa("network not available");
+		return avail;
+	}
+
+	public static boolean lanAvailable(D d) {
+		if(!netAvailable(d)) return false;
+		if(cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI) return true;
+		d.lmwa("LAN access not available");
+		return false;
+	}
+
+	NetConn(D d) throws Exception {
+		this.d = d.klon(this);
+		fileInfo = new FileInfo(d);
+		netIO = new NetSslScIO(d);
+		localFS = new FSLocal(this.d);
+		remoteFS = new FSServer(this.d);
+	}
+
+	void connectSock(InetSocketAddress hostIp) throws Exception {
+		String host = hostIp.getHostName();
+		int port = hostIp.getPort();
+		if(d.ll(3)) d.l(String.format("%sSSL, connecting to %s:%d...", (Prefs.ssl ? "" : "non"), host, port));
+		try { netIO.connect(hostIp); } catch(Exception e) { abend(e); }
+
+		boolean accepted = false;
+		NetData confirm = new NetData(d, 8);
+		while(confirm.buf.remaining() > 0) netIO.get(confirm.buf);
+		confirm.unloadToString();
+		if(d.ll(3)) d.l(confirm.data + " received as confirm");
+
+		if(confirm.data.equals("ACCEPTED")) accepted = true;
+		if(!accepted) abend(false, "connection rejected by server", null);
+		if(d.ll(3)) d.l("connected to " + host + ":" + port);
+	}
+
+	void accountRec(EntitySize es, int delta, String fn) {
+		if(d.ll(5)) d.l(String.format("EntitySize counter=%s, fn=%s, incremented by %d, target size=%d, running size=%d, pct=%d",
+				es.hashCode(), fn, delta, es.getTargetSize(), es.getRunningSize(), es.getPct()));
+		es.incrR(delta, fn); }
+
+	void accountFile(EntitySize es, long d) { es.incrF(); }
+
+	void accountDir(EntitySize es, String dn) { es.incrD(dn); }
+
+	public void endOfData() throws Exception { if(netIO.out != null) putNum(0); }
+
+	String getCmd() throws Exception {
+		NetData cmd = new NetData(d, 8);
+		netIO.get(cmd.buf);
+		cmd.unloadToString();
+		return cmd.data;
+	}
+
+	void putAttrs(EntitySize targetSize) throws Exception { new FileInfo(d, targetSize).put(); }
+
+	void putAttrs(File f, String fp) throws Exception { new FileInfo(d, f, fp).put(); }
+
+	void putCmd(String act) throws Exception {
+		netIO.put(new NetData(d, act).buf);
+		if(d.ll(4)) d.l(act + " cmd send");
+	}
+
+	public void batchEnd() throws Exception { if(netIO.out != null) putCmd(K.LongTaskAction.BATCHEND.action); }
+
+	public String getStr() throws Exception { return NetChOps.getStr(d, netIO); }
+
+	void putStr(String s) throws Exception { NetChOps.putStr(d, s, netIO); }
+
+	public long getNum() throws Exception { return NetChOps.getNum(d, netIO); }
+
+	void putNum(long n) throws Exception { NetChOps.putNum(d, n, netIO); }
+
+	void abend(Exception x) throws Exception {  // by default hlasitý ABEND
+		abend(false, x);
+	}
+
+	void abend(boolean quiet, Exception x) throws Exception {
+		synchronized(K.app) {
+			K.cmdConnClose(d);
+			connClose();
+			if(quiet) throw new K.End(x);
+			else throw x;
+		}
+	}
+
+	void abend(boolean quiet, String msg, Exception x) throws Exception {   // ABEND s doprovodnou informací
+		synchronized(K.app) {
+			K.cmdConnClose(d);
+			connClose();
+			final String m = getClass().getName() + "[" + msg + "]";
+			if(quiet) throw new K.End(m, x);
+			else throw new Exception(m, x);
+		}
+	}
+
+	abstract void connClose();
+
+	/**
+	 * holds information about file and communicates it over data stream
+	 */
+	class FileInfo {
+		D d;
+		String fn;
+		long size;
+		long timestamp;
+
+		FileInfo(D d) { this.d = d.klon(this); }
+
+		FileInfo(D d, File realFile, File relFile) { this(d, realFile, relFile.getPath()); }
+
+		FileInfo(D d, File file, String fn) {
+			this.d = d.klon(this);
+			this.fn = fn;
+			size = file.isFile() ? file.length() : -1;
+			timestamp = (long)file.lastModified()/1000;
+		}
+
+		FileInfo(D d, EntitySize es) {
+			this.d = d.klon(this);
+			this.fn = "dummy fn for batch size";
+			size = es.getTargetSize();
+			timestamp = 0L;
+		}
+
+		boolean get() throws Exception { return get(netIO); }
+
+		boolean get(GetPut io) throws Exception {
+			fn = NetChOps.getStr(d, io);
+			if(fn == null || fn.length() == 0) return false;
+			else {
+				fn = fn.replaceAll(K.FN_RESERVED_CHARS_REGEX, ".");
+				size = NetChOps.getNum(d, io);
+				timestamp = NetChOps.getNum(d, io);
+				if(d.ll(4)) d.l("fname=" + fn + ", size=" + size + ", timestamp=" + timestamp);
+				return true; }
+		}
+
+		void put() throws Exception { put(netIO); }
+
+		void put(GetPut io) throws Exception {
+			NetChOps.putStr(d, fn, io);
+			NetChOps.putNum(d, size, io);
+			NetChOps.putNum(d, timestamp, io);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetConnPeer.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,210 @@
+package hh.dejsem.net;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Handler;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+
+import hh.dejsem.K;
+import hh.dejsem.fm.EntitySize;
+import hh.dejsem.fm.NetTaskRef;
+import hh.lib.D;
+
+/**
+ * network SSL connection to peer
+ * ● Net opers must not be performed from constructors as they are called from main process
+ * ● all other methods are called from subprocess and may perform net opers
+ */
+public class NetConnPeer extends NetConn {
+	String locHost = null;
+	boolean peerServerSide = true;
+	public InetSocketAddress peerIp;
+	SendPeerHostThread sendPeerHostThread;
+	NetTaskRef netTaskRef;
+	// ----------------------------------------------------------
+	
+	public NetConnPeer(D d, NetTaskRef netTaskRef, boolean listen) throws Exception {
+		super(d);
+		this.netTaskRef = netTaskRef;
+		peerServerSide = listen;
+	}
+
+	/**
+	 * Broadcast via UDP addr:port as server side of peer connection.
+	 *
+	 * @param abendHandler the abend handler
+	 * @throws SocketException      the socket exception
+	 * @throws UnknownHostException the unknown host exception
+	 */
+	void sendPeerHost_UDP(Handler abendHandler) throws SocketException, UnknownHostException {
+		if(d.ll(3)) d.l("advertising peer server side via UDP broadcast");
+		String ipPortAdvert = String.format("%012d%s%012d%s%012d", K.UDP_ADVERT_KEY.length(), K.UDP_ADVERT_KEY, locHost.length(), locHost, port);
+		sendPeerHostThread = new SendPeerHostThread(d, abendHandler, ipPortAdvert);
+		sendPeerHostThread.start();
+	}
+
+	public void pushBatchSize(EntitySize targetSize) throws Exception {
+		peerIp = new NetUDP(d).keepReceivingPeerIp();
+		connectPeerAsClient();
+		d.l(3, String.format("sending batchsize[%d]", targetSize.getTargetSize()));
+		putAttrs(targetSize);
+	}
+
+	public void pushPeer(String from) throws Exception {
+		// from=full_path_file, to=relative_path_entry
+		connectPeerAsClient();
+		File ffrom = new File(from);
+		File fto = new File(ffrom.getName());
+		pushFileToPeer(ffrom, fto);
+		if(d.ll(3)) d.l("partial PUSHPEER finished");
+	}
+
+	void pushFileToPeer(File from, File to) throws Exception {
+		if(d.ll(3)) d.l("pushing to peer, from=" + from.getPath() + ", to=" + to.getPath());
+		/*final EntitySize es = netTaskRef.progress.transfSize;*/
+		final EntitySize es = netTaskRef.getProgress().es;
+		if(netTaskRef.go) putAttrs(from, to.getPath());
+		if(from.isDirectory()) {
+			accountDir(es, from.getPath());    // account dir for transfer progress display
+			for(String fn: from.list()) if(netTaskRef.go) pushFileToPeer(new File(from, fn), new File(to, fn));
+			if(netTaskRef.go) putAttrs(from, to.getPath());
+		}
+		else {
+			netData = new NetData(d);
+			fio = new FileIO(d, from, "r");
+			while(netTaskRef.go && fio.get(netData.buf)) {
+				netData.buf.flip();
+				accountRec(es, netIO.put(netData.buf), from.getPath());    // send buf and account bytes for transfer progress display
+				netData.buf.clear(); }
+			accountFile(es, from.length());    // account file for transfer progress display
+		}
+	}
+
+	public long pullBatchSize() throws Exception {
+		// accept connection from peer and get size of batch which peer will send
+		fileInfo.get();
+		return fileInfo.size;
+	}
+
+	public void pullDataFromPeer(String to) throws Exception {
+		// to=full_path_dir
+		final EntitySize es = netTaskRef.getProgress().es;
+		netData = new NetData(d);
+		while(netTaskRef.go && fileInfo.get()) {
+			if(d.ll(3)) d.l("pulling from peer, fn=" + fileInfo.fn + ", to=" + to);
+			file = new File(to, fileInfo.fn);
+
+			if(fileInfo.size < 0) {		// directory
+				localFS.doCreateDir(to, fileInfo.fn);    // to - absolutní, fn - relativní k to
+				if(!file.isDirectory()) abend(false, "unable to create server dir " + to, null);
+				accountDir(es, file.getPath());         // account dir for transfer progress display
+			}
+
+			else {                      // file
+				String relParent = file.getParent().replace(to, "");    // parent dir relative to "to"
+				if(relParent.startsWith("/")) relParent = relParent.substring(1);
+				fio = new FileIO(d, localFS.doCreateDir(to, relParent), file, "rw");
+
+				long rest = fileInfo.size;
+				while(netTaskRef.go && rest > 0) {
+					netData.buf.clear();
+					if(rest < netData.buf.limit()) netData.buf.limit((int)rest);
+					if(!netIO.get(netData.buf)) break;
+					int transferred = netData.buf.position();
+					rest -= transferred;
+					netData.buf.flip();
+					fio.put(netData.buf);
+					accountRec(es, transferred, file.getPath());    // account bytes for transfer progress display
+				}
+				fio.close();
+				accountFile(es, file.length());    // account file for transfer progress display
+			}
+			if(!file.setLastModified(fileInfo.timestamp * 1000)) netTaskRef.failedTimeStamp = true;
+		}
+		if(d.ll(2)) d.l("PULLPEER finished");
+	}
+
+	void getLocHost() throws Exception {
+		// get local ipv4 addr to bind to
+		locHost = null;
+		if(isLAN())
+			try {
+				for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
+				    NetworkInterface intf = en.nextElement();
+				    for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
+				        InetAddress inetAddress = enumIpAddr.nextElement();
+				        if(inetAddress.isLoopbackAddress()) continue;
+				        if(inetAddress.getClass().getSimpleName().equals("Inet6Address")) continue;
+				        else { locHost = inetAddress.getHostAddress().toString(); break; } } }
+			} catch (SocketException e) { abend(false, "network interfaces info", e); }
+		if(d.ll(3)) d.l("local ip=" + locHost);
+	}
+
+	public void bindSockToPeer() throws Exception {
+		connClose();
+		if(locHost == null) getLocHost();
+
+		for(port = K.netNode.portBase + 1; port < K.netNode.portBase + 3; port++) {
+			if(d.ll(3)) d.l("binding to " + locHost + ":" + port);
+			try {
+				netIO.bind(locHost, port);
+				//K.cmdConn.declarePeerHost(locHost, port); // atavismus - přešlo se na UDP
+				break;
+			} catch (Exception e) {
+				if(e.getMessage().contains("Address already in use")) continue;
+				else throw new Exception("bind SSL server socket", e);
+			}
+		}
+		if(!netIO.isBound()) throw new Exception("all ports to peers are busy");
+		else d.logPrefix += "[" + port + "]";
+	}
+
+	void acceptConnFromPeer(Handler abendHandler) throws Exception {
+		if(isPeerConnected()) return;
+		bindSockToPeer();
+		sendPeerHost_UDP(abendHandler);
+		if(d.ll(3)) d.l("accepting peer conn on " + locHost + ":" + port);
+		try {
+			netIO.accept();
+			netIO.put(new NetData(d, "ACCEPTED").buf);
+			if(d.ll(4)) d.l("connection acceptance conformed");
+		}
+		catch(Exception e) {
+			boolean quiet = false;
+			if(!netTaskRef.go) quiet = true;    // task byl explicitně zastaven
+			abend(quiet, "peer server accept", e); }
+		finally { sendPeerHostThread.stopAdv();  }   // stop server host:port advertising
+	}
+
+	public boolean isPeerConnected() throws Exception {
+		if(!isLAN()) abend(false, "no LAN connection", null);
+		return netIO.isConnected();
+	}
+
+	boolean isLAN() {
+		// determine LAN connectivity for peer-to-peer operations
+		NetworkInfo ni = ((ConnectivityManager)d.getContext().getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
+		return ni != null && ni.isConnected();
+	}
+
+	void connectPeerAsClient() throws Exception {
+		if(isPeerConnected()) return;
+		connectSock(peerIp);
+	}
+
+	public void connClose() {
+		try {
+			netIO.closeSc();
+			netIO.closeSsc(); }
+		catch(Exception e) { d.abendMsg("closing", e); } }
+
+	void bindUSB() {}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetConnSrv.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,405 @@
+package hh.dejsem.net;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.net.Uri;
+
+import java.io.File;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+
+import hh.dejsem.K;
+import hh.dejsem.Prefs;
+import hh.dejsem.clipboard.HistList;
+import hh.dejsem.fm.EntitySize;
+import hh.dejsem.fm.NetTaskRef;
+import hh.lib.D;
+
+/**
+ * operations on network SSL connection to server
+ * <p>
+ * ● třída je instanciována z instancí LongAsyncTask, které dědí AsyncTask
+ *      t.j. kromě konstruktorů jsou všechny metody prováděny ve zvláštních vláknech, takže mohou provádět síťové operace
+ * </br>
+ * ● instance LongAsyncTask a potažmo NetConnSrv se tvoří pro každou přenosovou dávku,
+ *      t.j. jako akce kontextového menu download/upload to server
+ * </br>
+ * ● přenos dávky lze násilně ukončit v dialogu ProgressMonitor spouštěném z notifikace na Notification Bar
+ */
+public class NetConnSrv extends NetConn {
+
+	NetTaskRef netTaskRef;
+
+	public NetConnSrv(D d) throws Exception { 									// cmd connection to server
+		super(d);
+		d.logPrefix += "[cmds]";
+		port = K.netNode.portBase;
+	}
+
+	public NetConnSrv(D d, String logPrefix, NetTaskRef netTaskRef) throws Exception { 		// data or cmd connection to server
+		super(d);
+		d.logPrefix += logPrefix;
+		this.netTaskRef = netTaskRef;
+		port = netTaskRef.getArgs().getInt(K.PORT_KEY, 0);
+		if(d.ll(3)) d.l(String.format("netConnSrv=%s, port=%d", this, port));
+	}
+
+	void connectServer() throws Exception {
+		// connect to the server on either CMD or DATA connection
+		if(netIO.isConnected()) return;
+		try { connectSock(new InetSocketAddress(Prefs.host, port)); }
+		catch (Exception e) { abend(false, "connecting to server", e); }
+	}
+	
+	String pullClip(String fn) throws Exception {
+		if(d.ll(3)) d.l("pulling clipboard, fn=" + fn);
+		connectServer();
+		putCmd(SrvCmd.PULLCLIP.cmd);
+		putStr(fn);	
+		String data = getStr();
+		if(d.ll(3)) d.l("PULLCLIP finished");
+		connClose();
+		return data;
+	}
+	
+	void pushClip(NetData payload) throws Exception {
+		if(d.ll(3)) d.l("pushing clipboard...");
+		connectServer();
+		putCmd(SrvCmd.PUSHCLIP.cmd);
+		if(payload.buf.remaining() == 0) {
+			if(d.ll(4)) d.l("empty buffer");
+			connClose(); return; }
+		netIO.put(payload.buf);
+		if(d.ll(3)) d.l("PUSHCLIP finished");
+		connClose();
+	}
+
+	HistList pullHistList() throws Exception {
+		d.l("+++ pullHistList()");
+		HistList history = new HistList();
+		history.entries = HistList.allocEntries();
+		connectServer();
+		if(d.ll(3)) d.l("pulling clipboard history");
+		putCmd(SrvCmd.PULLHIST.cmd);
+		while(true) {
+			HistList.HistEntry he = new HistList.HistEntry();
+			he.name = getStr();
+			if(he.name != null && he.name.length() > 0) {
+				he.text = getStr();
+				he.text = he.text.replaceAll("\n", "|");
+				he.size = getNum();
+				he.date = getNum();
+				if(d.ll(5)) d.l(
+					"name=" + he.name + ", size=" + he.size + ", timestamp=" + he.date +
+					", text=" + (he.text == null ? null : (he.text.length() < 20 ? he.text : he.text.substring(0, 20))));
+				history.entries.put(he.name, he);
+			}
+			else break;
+		}		
+		connClose();
+		if(d.ll(3)) d.l("PULLHIST finished");
+		return history;
+	}
+
+	public void pushSrv() throws Exception, K.EntryNotFound {
+		// from=full_path_entry, to=full_path_entry
+		String to = netTaskRef.getArgs().getString(K.FILE_TO_KEY);
+		String fn = null;
+		try {
+			connectServer();
+			for(String from: netTaskRef.getArgs().getStringArray(K.FILES_FROM_KEY)) {
+				fn = from;
+				File f = new File(from);
+				File dir = new File(to);
+				if(!f.exists()) abend(false, from + " doesn't exist", null);
+				if(d.ll(3)) d.l("pushing file...");
+				putCmd(K.LongTaskAction.PUSHFILE.action);
+				if(netTaskRef.go)
+					pushFileToSrv(f, dir);      // recursivelly push file/dir object
+				endOfData();                    // end of recurse
+				if(d.ll(3))
+					d.l(String.format("PUSHFILE finished, entry %s pushed to server %s", f.getPath(), dir.getPath()));
+				if(!netTaskRef.go) break;
+			}
+			batchEnd();
+			getNum();                           // wait until server receives all data
+		}
+		catch(Exception e) { throw new Exception(String.format("COPYing %s to server", fn), e); }
+		finally { try { connClose(); } catch(Exception e) {} }
+	}
+
+	void pushFileToSrv(File from, File to) throws Exception, K.EntryNotFound {
+		File target = new File(to, from.getName());
+		EntitySize es = netTaskRef.getProgress().es;
+		if(from.isDirectory()) {
+			accountDir(es, from.getPath());    // account dir for transfer progress display
+			if(from.listFiles() != null)
+				for(File f: from.listFiles())
+					if(netTaskRef.go) pushFileToSrv(f, target);     // recurse
+			new FileInfo(d, from, target).put();	// put directory info once more to setup its orig timestamp
+		}
+		else {
+			new FileInfo(d, from, target).put();
+			fio = new FileIO(d, from, "r");
+			netData = new NetData(d);
+			while(netTaskRef.go && fio.get(netData.buf)) {
+				netData.buf.flip();
+				accountRec(es, netIO.put(netData.buf), from.getPath());    // account bytes for transfer progress display
+				netData.buf.clear(); }
+			accountFile(es, from.length());    // account file for transfer progress display
+		}
+	}
+
+	/**
+	 * Expose file located on server to HTTP.
+	 *
+	 * @param path the path to file on server
+	 * @return the path segment of resulting URL
+	 * @throws Exception the exception
+	 */
+	String expose(String path) throws Exception {
+		if(d.ll(3)) d.l("exposing " + path);
+		connectServer();
+		putCmd(SrvCmd.EXPOSE.cmd);
+		putStr(path);
+		return getStr();    // get from server path segment of URL
+	}
+
+	/**
+	 * Upload to server and expose on web-server.
+	 *
+	 * @throws Exception    the exception
+	 */
+	public String pushFileExpose() throws Exception {
+		// from=full_path_entry
+		String from = netTaskRef.getArgs().getString(K.FILES_FROM_KEY);
+		try {
+			File f = new File(from);
+			if(!f.exists()) abend(false, from + " doesn't exist", null);
+			if(d.ll(3)) d.l("exposing file...");
+			connectServer();
+			putCmd(K.LongTaskAction.PUSHFIEX.action);
+			if(netTaskRef.go) pushFileToSrv(f, new File(""));      // recursivelly push file/dir object
+			endOfData();        // end of recurse
+			if(d.ll(3)) d.l("PUSHFIEX finished, receiving URI path");
+			return getStr();    // return path segment of resulting URL
+		}
+		catch(Exception e) { throw new Exception(String.format("EXPOSing file %s on http-server", from), e); }
+		finally { try { connClose(); } catch(Exception e) {} }
+	}
+
+	/**
+	 * Upload to server and expose on web-server.
+	 *
+	 * @throws Exception     the exception
+	 */
+	public String pushStreamExpose() throws Exception {
+		if(d.ll(3)) d.l("exposing stream...");
+		Uri uri = (Uri)(netTaskRef.getArgs().getParcelable(K.STREAM_KEY));
+		ContentResolver cr = d.getContext().getContentResolver();
+		InputStream is = cr.openInputStream(uri);
+		ReadableByteChannel ic = Channels.newChannel(is);
+		ContentProviderClient cc = cr.acquireContentProviderClient(uri);
+		Long size = cc.openAssetFile(uri, "r").getLength();
+		String mimeType = cc.getType(uri);
+		if(mimeType == null) mimeType = "";
+		connectServer();
+		putCmd(K.LongTaskAction.PUSHSTEX.action);
+		NetChOps.putStr(d, mimeType, netIO);
+		NetChOps.putNum(d, size, netIO);
+		netData = new NetData(d);
+		InChIO chio = new InChIO(d, ic);
+		EntitySize es = netTaskRef.getProgress().es;
+		while(netTaskRef.go && chio.get(netData.buf)) {
+			netData.buf.flip();
+			accountRec(es, netIO.put(netData.buf), uri.getPath());    // account for transfer progress display
+			netData.buf.clear(); }
+		accountFile(es, size);  // account for transfer progress display
+		if(d.ll(3)) d.l("PUSHSTEX finished, receiving URI path");
+		return getStr();        // return path segment of exposed URL
+	}
+
+	public void pullSrv() throws Exception {
+		// from=full_path_entry, to=full_path_target_directory
+		String to = netTaskRef.getArgs().getString(K.FILE_TO_KEY);
+		if(to == null) return;
+		if(d.ll(3)) d.l(String.format("pulling into %s/ ...", to));
+		EntitySize es = netTaskRef.getProgress().es;
+		connectServer();
+		String fn = null;
+		try {
+			for(String from: netTaskRef.getArgs().getStringArray(K.FILES_FROM_KEY)) {
+				fn = from;
+				putCmd(K.LongTaskAction.PULLFILE.action);
+				putStr(from);
+				netData = new NetData(d);
+				boolean found = false;
+				while(netTaskRef.go && fileInfo.get()) {
+					found = true;
+					file = new File(to, fileInfo.fn);
+					if(d.ll(4)) d.l(String.format(""));
+
+					if(fileInfo.size < 0) {     // object is directory
+						localFS.doCreateDir(to, fileInfo.fn);   // to - absolutní, fn - relativní k to
+						if(!file.isDirectory())
+							abend(false, "unable to create server dir " + to, null);
+						accountDir(es, file.getPath());            // account dir for transfer progress display
+					}
+
+					else {                      // object is file
+						if(file.exists())
+							file.delete();      // FileIO bere file jako random access, tak proto delete
+						fio = new FileIO(d, localFS.doCreateDir(file.getParent(), null), file, "rw");
+						long rest = fileInfo.size;
+						while(netTaskRef.go && rest > 0) {
+							netData.buf.clear();
+							if(rest < netData.buf.limit()) netData.buf.limit((int)rest);
+							if(!netIO.get(netData.buf)) break;
+							int transferred = netData.buf.position();
+							rest -= transferred;
+							netData.buf.flip();
+							fio.put(netData.buf);
+							accountRec(es, transferred, file.getPath());    // account bytes for transfer progress display
+						}
+						fio.close();
+						accountFile(es, file.length());    // account file for transfer progress display
+					}
+					// there is a bug in setLastModified() - success is device dependent - see https://code.google.com/p/android/issues/detail?id=18624
+					if(!file.setLastModified(fileInfo.timestamp * 1000))
+						netTaskRef.failedTimeStamp = true;
+					if(d.ll(4)) d.l(file.getPath() + " pulled, dir=" + (fileInfo.size == -1));
+				}
+				if(!found) {
+					if(d.ll(3)) d.l("PULLFILE: entry " + from + " not found");
+					throw new K.EntryNotFound();
+				}
+				if(d.ll(3)) d.l("PULLFILE finished");
+				if(!netTaskRef.go) break;   // net task cancelled
+			}
+			batchEnd();
+		}
+		catch(Exception e) { throw new Exception(String.format("COPY %s from server", fn), e); }
+		finally { try { connClose(); } catch(Exception e) {} }
+	}
+
+	void pullListSrv(String from, String to) throws Exception { 
+		// from=full_path_dir, to=full_path_cache_file
+		connectServer();
+		if(d.ll(3)) d.l("pulling file list, cwdFl=" + from);
+		putCmd(SrvCmd.PULLLIST.cmd);
+		NetChOps.putStr(d, from, netIO);
+		try {
+			FileIO fio = new FileIO(d, new File(to), "rw");
+			NetChOps.putStr(d, from, fio);							// cache cwdFl name
+			while(fileInfo.get(netIO)) fileInfo.put(fio);	// cache cwdFl list
+			fio.close(); }
+		catch(Exception e) { abend(false, "put server dir list to cache", e); }
+		if(d.ll(3)) d.l("PULLLIST finished");
+	}
+
+	int longtask() throws Exception {
+		connectServer();
+		putCmd(SrvCmd.LONGTASK.cmd);
+		int port = (int)getNum();
+		K.cmdConnClose(d);
+		if(d.ll(3)) d.l("long running task scheduled on " + port + " port");
+		return port;
+	}
+
+	void move(String from, String to) throws Exception, K.EntryNotFound {
+		// from=full_path_entry, to=full_path_entry
+		if(d.ll(3)) d.l("moving " + from + " to " + to);
+		connectServer();
+		putCmd(SrvCmd.MOVE.cmd);
+		putStr(from);
+		putStr(to);
+		if(d.ll(3)) d.l("MOVE finished");
+	}
+
+	void hide(String path) throws Exception {
+		if(d.ll(3)) d.l("hiding " + path);
+		connectServer();
+		putCmd(SrvCmd.HIDE.cmd);
+		putStr(path);
+	}
+
+	/*String exposeUp(String path) throws Exception {     // ???
+		if(d.ll(3)) d.l("exposing " + path);
+		connectServer();
+		putCmd(SrvCmd.EXPOSEUP.cmd);
+		putStr(path);
+		return getStr();
+	}*/
+
+	void createDir(String dirName) throws Exception { 
+		// dirName=simple name
+		if(d.ll(3)) d.l("creating directory " + dirName);
+		connectServer();
+		putCmd(SrvCmd.CREATEDIR.cmd);
+		putStr(dirName);
+		if(d.ll(3)) d.l("CREATEDIR finished");
+	}
+
+	void delete(String from) throws Exception { 
+		// from=full_path_file
+		if(d.ll(3)) d.l("deleting " + from);
+		connectServer();
+		putCmd(SrvCmd.DELETE.cmd);
+		putStr(from);
+		if(d.ll(3)) d.l("DELETE finished");
+	}
+
+	long getFreeSpace() throws Exception {
+		if(d.ll(3)) d.l("asking for free space");
+		connectServer();
+		putCmd(SrvCmd.FREE.cmd);
+		long free = getNum();
+		if(d.ll(3)) d.l("FREE finished, got=" + free);
+		return free;
+	}
+
+	EntitySize reckon(String fp) throws Exception { 
+		// from=full_path_file
+		if(d.ll(3)) d.l("reckoning " + fp);
+		connectServer();
+		putCmd(SrvCmd.RECKON.cmd);
+		putStr(fp);
+		return new EntitySize(getNum(), (int)getNum(), (int)getNum());
+		/*EntitySize ds = new EntitySize(getNum(), (int)getNum(), (int)getNum());
+		*//*ds.size = getNum();
+		ds.fnum = (int)getNum();
+		ds.dnum = (int)getNum();*//*
+		return ds;*/
+	}
+
+	InetSocketAddress getPeerHost() throws Exception {
+		// get addr of peer declaring itself as server side of peer connection
+		String host = null;
+		int port = 0;
+		try {
+			if(d.ll(3)) d.l("getting peer server");
+			connectServer();
+			putCmd(SrvCmd.GETPEER.cmd);
+			host = getStr();
+			port = (int)getNum();
+			if(d.ll(3)) d.l("got peer server=" + host + ", port=" + port);
+			K.cmdConnClose(d); }
+		catch (Exception e) { abend(false, "get peer host", e); }
+		if(host == null) abend(new K.NoPeerListening());
+		return new InetSocketAddress(host, port);
+	}
+
+	void declarePeerHost(String host, int port) throws Exception {
+		// declare itself as server side of peer connection
+		if(d.ll(3)) d.l("declaring peer server on " + host + ":" + port);
+		connectServer();
+		putCmd(SrvCmd.SETPEER.cmd);
+		putStr(host);
+		putNum(port);
+		connClose();
+	}
+
+	public void connClose() { try { netIO.closeSc(); } catch(Exception e) { d.abendMsg("closing", e); } }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetCrypt.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,50 @@
+package hh.dejsem.net;
+
+import java.security.Key;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+
+import hh.dejsem.K;
+import hh.lib.D;
+
+public class NetCrypt {
+
+	D d;
+	String alg = K.CRYPT_ALG;
+	int keylen = K.CRYPT_KEYLEN;
+	Key k;
+	Cipher encoder, decoder;
+
+	public NetCrypt(D d, String alg, String passwd, int keylen) {
+		try {
+			this.d = d.klon(this);
+			if(alg != null) this.alg = alg;
+			if(keylen > 0) this.keylen = keylen;
+			k = new SecretKeySpec(rawKey(passwd, this.keylen), this.alg);
+			encoder = Cipher.getInstance(this.alg);
+			encoder.init(Cipher.ENCRYPT_MODE, k);
+			decoder = Cipher.getInstance(this.alg);
+			decoder.init(Cipher.DECRYPT_MODE, k);
+		} catch(Exception e) { d.abendMsg(e); }
+	}
+
+	private byte[] rawKey(String passwd, int len) {
+		String key = "";
+		while (key.length() < len) key += passwd;
+		return  Arrays.copyOf(key.getBytes(), len);
+	}
+
+	public synchronized String decrypt(byte[] enc) throws Exception {
+		return new String(decoder.doFinal(enc)).trim();
+	}
+
+	public synchronized byte[] encrypt(String msg) throws Exception {
+		byte[] bMsg = msg.getBytes();
+		int len = bMsg.length;
+		byte[] dec = new byte[len + 8 - len%8];
+		for(int i=0; i<len; i++) dec[i] = bMsg[i];
+		for(int i=len; i<dec.length; i++) dec[i] = ' ';
+		return encoder.doFinal(dec); }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetData.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,78 @@
+package hh.dejsem.net;
+
+import java.nio.ByteBuffer;
+
+import hh.dejsem.K;
+import hh.lib.D;
+
+/**
+ * network data sent and received
+ */
+public class NetData {
+	D d;
+	public ByteBuffer buf;
+	public byte[] nb;
+	public String data;
+	int n;
+	/* ----------------------------------------------------------*/
+
+	public NetData(D d, byte[] b) {
+		this.d = d.klon("DATA");
+		load(b);
+	}
+
+	public NetData(D d, String s) {
+		this.d = d.klon(this);
+		load(s);
+	}
+
+	public NetData(D d) { this(d, K.buffSize); }
+
+	public NetData(D d, int size) {
+		this.d = d.klon(this);
+		buf = ByteBuffer.allocate(size);
+		nb = new byte[size];
+		data = "";
+	}
+
+	void load(byte[] b) {
+		if(n < 0) n = 0;
+		buf = ByteBuffer.wrap(b, 0, n);
+		if(d.ll(5)) d.l("loaded from byte data[" + n + "]: buf=" + D.bufStat(buf));
+	}
+
+	void load(String s) {
+		if(d.ll(5)) d.l("loading data from string[" + s.length() + "]...");
+		buf = ByteBuffer.wrap(s.getBytes());
+		nb = s.getBytes();
+	}
+
+	void append() {
+		if(d.ll(5)) d.l("appending buffer " + D.bufStat(buf) + " + " + n);
+		if(buf.remaining() < n) inflate();
+		buf.put(nb, 0, n);
+	}
+
+	void inflate() {
+		if(d.ll(4)) d.l("doubling buffer " + D.bufStat(buf));
+		ByteBuffer b = ByteBuffer.allocate(2 * buf.capacity());
+		buf.rewind();
+		b.put(buf);
+		buf = b;
+	}
+
+	public void unloadToString() {
+		if(d.ll(5)) d.l("unloading buffer[" + D.bufStat(buf) + "]...");
+		byte[] b = new byte[buf.position()];
+		buf.flip();
+		buf.get(b);
+		data = new String(b);
+	}
+
+	public void unloadToByte() {
+		if(d.ll(5)) d.l("unloading buffer[" + D.bufStat(buf) + "]...");
+		nb = new byte[buf.position()];
+		buf.flip();
+		buf.get(nb);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetIO.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,15 @@
+package hh.dejsem.net;
+
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+
+public interface NetIO {
+	void bind(String host, int port) throws Exception;
+	boolean isBound();
+	void accept() throws Exception;
+	void connect(InetSocketAddress hostIp) throws Exception;
+	boolean get(ByteBuffer buf) throws Exception;
+	int put(ByteBuffer buf) throws Exception;
+	void closeSc() throws Exception;
+	void closeSsc() throws Exception;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetInfoFrag.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,112 @@
+package hh.dejsem.net;
+
+import android.content.Context;
+import android.net.DhcpInfo;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.support.v4.app.FragmentManager;
+import android.text.Html;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+
+import hh.dejsem.K;
+import hh.dejsem.R;
+import hh.dejsem.lastmile.LastMileMeterFrag;
+import hh.lib.D;
+import hh.lib.DF;
+
+/**
+ * Fragment for info about current network connectivity
+ */
+public class NetInfoFrag extends DF implements View.OnClickListener {
+
+	static final String TITLETEXT = "net info";
+
+	public static NetInfoFrag instantiate(D d) {
+		NetInfoFrag nf = new NetInfoFrag();
+		nf.d = d.klon(nf);
+		return nf;
+	}
+
+	TextView myip;
+
+	@Override
+	public void onCreate(Bundle b) { super.onCreate(b); }
+
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		View netInfoView = inflater.inflate(R.layout.net_info, null);
+		myip = (TextView)netInfoView.findViewById(R.id.myip);
+		d.tv = (TextView)netInfoView.findViewById(R.id.tv);
+		d.sc = (ScrollView)netInfoView.findViewById(R.id.sc);
+		netInfoView.findViewById(R.id.throughput).setOnClickListener(this);
+		printNetworkElements();
+		return netInfoView;
+	}
+
+	@Override
+	public void onResume() {
+		super.onResume();
+		K.title.setText(TITLETEXT).update();
+	}
+
+	@Override
+	public void onClick(View v) {
+		final FragmentManager fm = getActivity().getSupportFragmentManager();
+		LastMileMeterFrag lastMile = (LastMileMeterFrag)fm.findFragmentByTag(K.LAST_MILE_TAG);
+		if (lastMile == null) lastMile = LastMileMeterFrag.instantiate(d);
+		fm.beginTransaction()
+				.replace(R.id.panel, lastMile, K.LAST_MILE_TAG)
+				.addToBackStack(null)
+				.commitAllowingStateLoss();
+	}
+
+	void printNetworkElements() {
+		try {
+			Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
+			if(en.hasMoreElements()) {
+				for(;en.hasMoreElements();) {
+					NetworkInterface i = en.nextElement();
+					d.ttv(String.format("● %s: isLoopback=%b, isVirtual=%b, isPointToPoint=%b, isUp=%b",
+							i.getDisplayName(), i.isLoopback(), i.isVirtual(), i.isPointToPoint(), i.isUp()));
+					for(InterfaceAddress ifa : i.getInterfaceAddresses())
+						d.ttv(String.format("● %s: iaddr=%s", i.getDisplayName(), ifa.getAddress().getHostAddress()));
+					for(Enumeration<InetAddress> enumIpAddr = i.getInetAddresses(); enumIpAddr.hasMoreElements();) {
+						InetAddress ia = enumIpAddr.nextElement();
+						if( !ia.isLoopbackAddress() &&
+							!ia.toString().substring(0, 0).getBytes().equals((byte)0xFF) &&
+							Inet4Address.class.isInstance(ia)
+							) myip.setText(Html.fromHtml(String.format("myip=<font color=\"#%06x\">%s</font>",
+								getResources().getColor(R.color.dir_name_color) - 0xff000000, ia.getHostAddress())), TextView.BufferType.SPANNABLE);
+					}
+					d.ttv("");
+				}
+				DhcpInfo dhcp = ((WifiManager)getActivity().getApplicationContext().getSystemService(Context.WIFI_SERVICE)).getDhcpInfo();
+				if(dhcp != null) {
+					d.ttv(String.format("● dhcp.ipAddress=%s", int2ia(dhcp.ipAddress).getHostAddress()));
+					d.ttv(String.format("● dhcp.netmask=%s", int2ia(dhcp.netmask).getHostAddress()));
+					d.ttv(String.format("● dhcp.broadcast=%s", int2ia((dhcp.ipAddress & dhcp.netmask) | ~dhcp.netmask).getHostAddress()));
+				}
+				d.ttv("");
+            }
+			else d.ttv("no network elements present");
+		} catch(Exception e) { d.abendMsg(e); }
+	}
+
+	InetAddress int2ia(int ia) throws UnknownHostException {
+		byte[] quad = new byte[4];
+		for(int k = 0; k < 4; k++) quad[k] = (byte)((ia >> k * 8) & 0xFF);
+		return InetAddress.getByAddress(quad);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetNode.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,144 @@
+package hh.dejsem.net;
+
+import android.content.res.Resources;
+
+import java.io.InputStream;
+import java.net.Socket;
+import java.security.KeyStore;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509ExtendedKeyManager;
+import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
+
+import hh.dejsem.K;
+import hh.dejsem.Prefs;
+import hh.dejsem.R;
+import hh.lib.D;
+
+/**
+ * network SSL node
+ */
+public class NetNode {
+	D d;
+	public int portBase;
+	public SSLContext ctx;
+	public SSLSocketFactory sf;
+	public SSLServerSocketFactory ssf;
+	/* ----------------------------------------------------------*/
+
+	public NetNode(D d) throws Exception {
+		this.d = d.klon(this);
+		d.logPrefix += "[" + (Prefs.ssl ? "" : "non") + "SSL]";
+		portBase = K.BASE_PORT_NUM + 10 * Integer.valueOf(d.getContext().getResources().getString(R.string.channel));
+		prepareSsl();
+		if(d.ll(3)) d.l("initalized");
+	}
+
+	private class X509KM extends X509ExtendedKeyManager {
+		X509KeyManager standardKM;
+
+	    X509KM(KeyManager standardKM) { this.standardKM = (X509KeyManager)standardKM; }
+	   
+		@Override 
+	    public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
+			String valentine = standardKM.chooseClientAlias(keyType, issuers, socket);
+			if(d.ll(4)) d.l("KM: chooseClientAlias: " + valentine);
+			return valentine; }
+	   
+		@Override 
+	    public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
+			if(d.ll(4)) d.l("KM: chooseServerAlias");
+			return standardKM.chooseServerAlias(keyType, issuers, socket); }
+	   
+		@Override 
+	    public X509Certificate[] getCertificateChain(String alias) {
+			if(d.ll(4)) d.l("KM: getCertificateChain: alias=" + alias);
+			return standardKM.getCertificateChain(alias); }
+
+		@Override
+	    public String[] getClientAliases(String keyType, Principal[] issuers) {
+			if(d.ll(4)) d.l("KM: getClientAliases: key type=" + keyType);
+			return standardKM.getClientAliases(keyType, issuers); }
+
+		@Override 
+	    public PrivateKey getPrivateKey(String alias) {
+			if(d.ll(4)) d.l("KM: getPrivateKey: alias=" + alias);
+			return standardKM.getPrivateKey(alias); }
+
+		@Override
+	    public String[] getServerAliases (String keyType, Principal[] issuers) {
+			if(d.ll(4)) d.l("KM: getServerAliases");
+			return standardKM.getServerAliases(keyType, issuers); }
+	}
+
+	private class X509TM implements X509TrustManager {
+		X509TrustManager standardTM;
+		X509Certificate trusted = null;
+		X509TM(X509TrustManager standardTM) { this.standardTM = standardTM; }
+	   
+		@Override 
+	    public X509Certificate[] getAcceptedIssuers() {
+			X509Certificate[] accepted = standardTM.getAcceptedIssuers();
+			if(d.ll(4)) d.l("TM: getAcceptedIssuers");
+			return accepted; }
+
+		@Override 
+	    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+			if(d.ll(4)) d.l("TM: checkClientTrusted");
+			standardTM.checkClientTrusted(chain, authType); }
+
+		@Override 
+	    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+			String cns = "";
+			for(X509Certificate crt: chain) cns += (crt.getSubjectDN() + " ");
+			if(d.ll(4)) d.l("TM: checkServerTrusted: server chain=" + cns);
+			for(X509Certificate c: chain) if(d.ll(4)) d.l("\tDN=" + c.getSubjectDN()); }
+	}
+
+	void prepareSsl() throws Exception {
+		if(K.word.length() == 0) throw new Exception("KeyStore integrity check failed.");   // tuhle exc generuje bcast při neplatném hesle
+		KeyManager[] standardKMs;
+		TrustManager[] standardTMs;
+
+		if(Prefs.sslDebug) System.setProperty("javax.net.debug", "ssl,handshake");
+		final char[] passphrase = K.word.toCharArray();
+		Resources r = d.getContext().getResources();
+		String ksName = String.format("bks%02d", Integer.valueOf(r.getString(R.string.channel)));
+		int ksId = r.getIdentifier(ksName, "raw", d.getContext().getPackageName());
+		ctx = SSLContext.getInstance("TLSv1");
+
+		KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
+		final KeyStore ks = KeyStore.getInstance("bks");
+		InputStream kfs = r.openRawResource(ksId);
+		ks.load(kfs, passphrase);
+		kmf.init(ks, passphrase);
+		standardKMs = kmf.getKeyManagers();
+		X509KM x509km = new X509KM((X509KeyManager)standardKMs[0]);
+
+		TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
+		final KeyStore ts = KeyStore.getInstance("bks");
+		InputStream tfs = r.openRawResource(ksId);
+		ts.load(tfs, passphrase);
+		tmf.init(ts);
+		standardTMs = tmf.getTrustManagers();
+		X509TM x509tm = new X509TM((X509TrustManager)standardTMs[0]);
+
+		ctx.init(standardKMs, standardTMs, null);
+
+		sf = ctx.getSocketFactory();
+		ssf = ctx.getServerSocketFactory();
+	}
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetScIO.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,84 @@
+package hh.dejsem.net;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+
+import hh.dejsem.K;
+import hh.lib.D;
+
+public class NetScIO implements NetIO, GetPut {
+	/**----------------------------------------------------------
+	 * nonSSL blocking network IO
+	 * ----------------------------------------------------------*/
+	D d;
+	ServerSocket ssc;
+	Socket sc = null;
+	InputStream in = null;
+	public OutputStream out = null;
+	ReadableByteChannel ic;
+	WritableByteChannel oc;
+	/* ----------------------------------------------------------*/
+
+	public NetScIO(D d) { this.d = d.klon(this); }
+
+	public void bind(String host, int port) throws Exception {}
+
+	public boolean isBound() { return ssc.isBound(); }
+
+	public void accept() throws Exception {}
+
+	public void connect(InetSocketAddress hostIp) throws Exception {
+		sc = new Socket();
+		sc.connect(hostIp, K.socketTO);		// oddělit connect TO a read TO
+		sc.setSoTimeout(K.socketTO);
+		in = sc.getInputStream();
+		ic = Channels.newChannel(in);
+		out = sc.getOutputStream();
+		oc = Channels.newChannel(out);
+	}
+
+	public boolean get(ByteBuffer buf) throws Exception {		// false on EOD
+		if(d.ll(5)) d.l("get from net, buf=" + D.bufStat(buf) + " ...");
+		try { while(buf.hasRemaining()) {
+				int n;
+				n = ic.read(buf);
+				if(d.ll(5)) d.l(String.format("%d bytes partially read", n));
+				if(n < 0) break;
+			}
+		}
+		catch(Exception x) {
+			closeSc();
+			throw new Exception("net channel read", x); };
+		if(d.ll(5)) d.l("" + buf.position()+ " read");
+		return buf.position() > 0;
+	}
+
+	public int put(ByteBuffer buf) throws Exception {
+		int n = 0;
+		if(d.ll(5)) d.l("output to net, buf=" + D.bufStat(buf) + " ...");
+		while(buf.hasRemaining()) {
+			n = 0;
+			try { n = oc.write(buf); }
+			catch(Exception x) {
+				closeSc();
+				throw new Exception("net channel write", x); }
+			if(d.ll(5)) d.l("" + n + " written"); }
+		buf.clear();
+		return n;
+	}
+
+	public void closeSc() throws Exception { if(sc != null) sc.close(); }
+
+	public void closeSsc() throws Exception {
+		if(ssc != null) {
+			ssc.close();
+			ssc = null; }
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetSslChIO.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,258 @@
+package hh.dejsem.net;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLSession;
+
+import hh.dejsem.K;
+import hh.dejsem.Prefs;
+import hh.lib.D;
+
+public abstract class NetSslChIO implements NetIO, GetPut {
+	/**----------------------------------------------------------
+	 * base class for SSL engine operations
+	 * ----------------------------------------------------------*/
+	public D d;
+	/*long timeOut = 10*1000L;*/
+	SSLEngine engine;
+	SSLSession session;
+	SSLEngineResult result;		// results from last engine operation
+	SSLEngineResult.Status rStatus;				// engine result progress
+	SSLEngineResult.HandshakeStatus hsStatus;	// engine handshake progress
+	int bytesProduced;			// data bytes produced by engine
+	boolean peerMayBeGone = false;
+	ServerSocket ss;
+	ServerSocketChannel ssc;
+	public SocketChannel sc;
+	public Selector selector = null;
+	public SelectionKey sk;
+	public ByteBuffer sslOut;			// SSL outbound
+	ByteBuffer sslIn;			// SSL inbound
+	ByteBuffer out;				// data outbound
+	ByteBuffer in;				// data inbound
+
+	public NetSslChIO(D d) throws Exception {
+//		super(d);
+		this.d = d.klon(this);
+		if(Prefs.sslDebug) { System.setProperty("javax.net.debug", "all"); }
+	}
+
+	public void bind(String host, int port) throws Exception {
+		ssc = ServerSocketChannel.open();
+		ss = ssc.socket();
+		ss.bind(new InetSocketAddress(host, port), 1024);
+	}
+
+	public boolean isBound() { return ss.isBound(); }
+
+	public void accept() throws Exception {
+		createSSLEngine();
+		engine.setUseClientMode(false);
+		engine.setNeedClientAuth(true);
+		selector = Selector.open();
+		ssc.configureBlocking(false);
+		int selection = 0;
+		sk = ssc.register(selector, SelectionKey.OP_ACCEPT);
+
+		selection = selector.select(K.acceptTO);
+
+		selector.selectedKeys().remove(sk);
+		if(d.getAct().isFinishing()) return;
+		if(selection == 0) throw new SocketTimeoutException("on accept");
+		sc = ssc.accept();
+		configureSocket();
+	}
+
+	public void connect(InetSocketAddress inetAdr) throws Exception {
+		createSSLEngine();
+		engine.setUseClientMode(true);
+		sc = SocketChannel.open();
+		sc.socket().setSoTimeout(K.socketTO);
+		sc.configureBlocking(true);
+		sc.socket().connect(inetAdr, K.socketTO);
+		configureSocket();
+		wrapAndPut(ByteBuffer.allocate(0), in);		// handshaking
+	}
+
+	public boolean isConnected() { return sc != null && sc.isConnected(); }
+
+	public boolean get(ByteBuffer buf) throws Exception {		// false on EOD
+		if(d.ll(5)) d.l("get from net, buf=" + D.bufStat(buf) + " ...");
+		getAndUnwrap(buf, out);
+		if(d.ll(5)) d.l("got from net, buf=" + D.bufStat(buf) + " ...");
+		return buf.position() > 0;
+	}
+
+	public int put(ByteBuffer buf) throws Exception {
+		if(d.ll(5)) d.l("put to net, buf=" + D.bufStat(buf));
+		while(buf.hasRemaining()) {
+			wrapAndPut(buf, in); }
+		int n = buf.position();
+		buf.clear();
+		return n;
+	}
+
+	public void closeSc() throws Exception { terminateClient(in); }
+
+	public void closeSsc() throws Exception { terminateServer(in); }
+
+	abstract void wrapAndPut(ByteBuffer out, ByteBuffer in) throws Exception;
+
+	public void wrap(ByteBuffer out) throws Exception {
+		if(d.ll(5)) d.l(String.format("%-16s %-32s %-32s", "before wrap",
+				"sslOut=" + D.bufStat(sslOut), "dataOut=" + D.bufStat(out)));
+		result = engine.wrap(out, sslOut);
+		chkStat("wrap: ");
+		if(d.ll(5)) d.l(String.format("%-16s %-32s %-32s %-32s", "after wrap",
+				"sslOut=" + D.bufStat(sslOut), "dataOut=" + D.bufStat(out),
+				"close=" + engine.isOutboundDone() + "/" + engine.isInboundDone()));
+	}
+
+	public void putSslOut() throws Exception {
+		sslOut.flip();
+		int len = sslOut.remaining();
+		while(sslOut.remaining() > 0)
+			try { sc.write(sslOut); }
+			catch(Exception e) {
+				if(isClosed() && e.getMessage().equals("Broken pipe")) break;		// peer is gone after close
+				else throw new Exception(e); }
+		if(d.ll(4)) d.l(String.format("%-16s %-32s", "written=" + len, "sslOut=" + D.bufStat(sslOut)));
+		sslOut.compact();
+	}
+
+	public void getAndUnwrap(ByteBuffer in, ByteBuffer out) throws Exception {
+		if(d.ll(5)) d.l("reading...");
+		getSslIn();
+		if(sslIn.position() == 0) return;
+		do { unwrap(in); } while(needUnwrap());
+		if(needWrap()) wrapAndPut(out, in);
+	}
+
+	void unwrap(ByteBuffer in) throws Exception {
+		do {
+			sslIn.flip();
+			if(d.ll(4)) d.l(String.format("%-16s %-32s %-32s", "before unwrap", "sslIn=" + D.bufStat(sslIn), "dataIn=" + D.bufStat(in)));
+			result = engine.unwrap(sslIn, in);
+			chkStat("unwrap: ");
+			sslIn.compact();
+			if(d.ll(4)) d.l(String.format("%-16s %-32s %-32s %-32s", "after unwrap", "sslIn=" + D.bufStat(sslIn), "dataIn=" + D.bufStat(in),
+					"close=" + engine.isOutboundDone() + "/" + engine.isInboundDone()));
+		} while(sslIn.position() > 0 && rStatus == SSLEngineResult.Status.OK);
+	}
+
+	void getSslIn() throws Exception {
+		int len;
+		try {
+			if((len = sc.read(sslIn)) < 0 && !peerMayBeGone) {
+				if(d.ll(5)) d.l("getSslIn, len read=" + len + ", peerMayBeGone=" + peerMayBeGone);
+				throw new IOException("Connection reset by peer");
+			}
+		}
+		catch(Exception e) {
+			if(e.getMessage().equals("Connection reset by peer") && peerMayBeGone) len = 0;
+			else throw e; }
+		if(d.ll(4)) d.l(String.format("%-16s %-32s", "read=" + len, "sslIn=" + D.bufStat(sslIn)));
+	}
+
+	void createSSLEngine() throws Exception {
+		engine = K.netNode.ctx.createSSLEngine();
+		session = engine.getSession();
+		getBuffers();
+	}
+
+	abstract void configureSocket() throws Exception;
+
+	void getBuffers() {
+		int netBufferMax = session.getPacketBufferSize();
+		sslOut = ByteBuffer.allocateDirect(netBufferMax);
+		sslIn = ByteBuffer.allocateDirect(netBufferMax);
+		out = createNetBuffer();
+		in = createNetBuffer();
+	}
+
+	ByteBuffer createDataBuffer() {
+		int appBufferMax = session.getApplicationBufferSize();
+		return ByteBuffer.allocate(appBufferMax);
+	}
+
+	ByteBuffer createNetBuffer() {
+		int netBufferMax = session.getPacketBufferSize();
+		return ByteBuffer.allocate(netBufferMax);
+	}
+
+	public boolean needWrap() { return rOK() && hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP; }
+
+	public boolean needUnwrap() { return rOK() && hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP; }
+
+	boolean rOK() { return (rStatus == SSLEngineResult.Status.OK || rStatus == SSLEngineResult.Status.CLOSED); }
+
+	boolean isClosed() { return (engine.isOutboundDone() && engine.isInboundDone()); }
+
+	boolean isNotHandshaking() {
+		return hsStatus == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING || hsStatus == SSLEngineResult.HandshakeStatus.FINISHED; }
+
+	boolean isHandshaking() { return !isNotHandshaking(); }
+
+	void closeIn() throws Exception {
+		engine.closeInbound();
+		if(d.ll(4)) d.l(String.format("%-82s %-32s",
+				"SSL engine's inbound closed", "close=" + engine.isOutboundDone() + "/" + engine.isInboundDone())); }
+
+	public void closeOut() {
+		if(d.ll(4)) d.l(String.format("%-82s %-32s",
+				"SSL engine before close", "close=" + engine.isOutboundDone() + "/" + engine.isInboundDone()));
+		engine.closeOutbound();
+		if(d.ll(4)) d.l(String.format("%-82s %-32s",
+				"SSL engine's outbound closed", "close=" + engine.isOutboundDone() + "/" + engine.isInboundDone())); }
+
+	public void closeSocket() throws Exception {
+		if(sc != null) sc.socket().close();
+		if(ss != null) ss.close();
+	}
+
+	void terminateClient(ByteBuffer dataIn) throws Exception {
+		closeOut();
+		if(isConnected()) wrapAndPut(ByteBuffer.allocate(0), dataIn);
+		closeSocket();
+	}
+
+	abstract void terminateServer(ByteBuffer dataIn) throws Exception;
+
+	private void chkStat(String tag) throws Exception {
+		rStatus = result.getStatus();
+		if(rStatus == SSLEngineResult.Status.CLOSED) peerMayBeGone = true;
+		hsStatus = engine.getHandshakeStatus();
+		logRes(tag);
+		if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) {
+			Runnable runnable;
+			while ((runnable = engine.getDelegatedTask()) != null) {
+				if(d.ll(3)) d.l("\trunning delegated task...");
+				runnable.run();
+			}
+			rStatus = result.getStatus();
+			hsStatus = engine.getHandshakeStatus();
+			if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK)
+				throw new Exception("handshake shouldn't need additional tasks");
+			logRes(" ");
+		}
+	}
+
+	void logRes(String str) {
+		if(!d.ll(3)) return;
+		String s = String.format("%-8s", str) +
+				rStatus + "/" + hsStatus + ", " +
+				result.bytesConsumed() + "/" + result.bytesProduced() + " bytes";
+		if (hsStatus == SSLEngineResult.HandshakeStatus.FINISHED) s += "\t...ready for application data";
+		d.l(s);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetSslChbIO.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,36 @@
+package hh.dejsem.net;
+
+import java.nio.ByteBuffer;
+
+import hh.dejsem.K;
+import hh.lib.D;
+
+public class NetSslChbIO extends NetSslChIO {
+	/**----------------------------------------------------------
+	 * SSL blocking network IO (SSL engine)
+	 * ----------------------------------------------------------*/
+
+	NetSslChbIO(D d) throws Exception { super(d); }
+
+	@Override
+	void wrapAndPut(ByteBuffer out, ByteBuffer in) throws Exception {
+		do {
+			wrap(out);
+			if(sslOut.position() == 0) break;
+			putSslOut();
+		} while(needWrap());
+		if(needUnwrap()) getAndUnwrap(in, out);
+	}
+
+	void terminateServer(ByteBuffer dataIn) throws Exception {
+		closeOut();
+		getAndUnwrap(dataIn, ByteBuffer.allocate(0));
+		closeSocket();
+	}
+
+	@Override
+	void configureSocket() throws Exception {
+		sc.socket().setSoTimeout((int) K.socketTO);
+		sc.configureBlocking(true);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetSslChnIO.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,60 @@
+package hh.dejsem.net;
+
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+
+import hh.dejsem.K;
+import hh.lib.D;
+
+public class NetSslChnIO extends NetSslChIO {
+	/**----------------------------------------------------------
+	 * SSL non-blocking network IO (SSL engine)
+	 * ----------------------------------------------------------*/
+
+	NetSslChnIO(D d) throws Exception { super(d); }
+
+	@Override
+	void wrapAndPut(ByteBuffer out, ByteBuffer in) throws Exception {
+		do {
+			wrap(out);
+			if(sslOut.position() == 0) break;
+			putSslOut();
+		} while(needWrap());
+		if(needUnwrap()) selectAndUnwrap(in, out);
+	}
+
+	void selectAndUnwrap(ByteBuffer in, ByteBuffer out) throws Exception {
+		if(d.ll(5)) d.l("select reading...");
+		select(SelectionKey.OP_READ);
+		getAndUnwrap(in, out);
+	}
+
+	void selectAndWrap(ByteBuffer out, ByteBuffer in) throws Exception {
+		if(d.ll(5)) d.l("select writing...");
+		select(SelectionKey.OP_WRITE);
+		wrapAndPut(out, in);
+	}
+
+	void select(int flag) throws Exception {
+		register(flag);
+		int selection = selector.select(K.socketTO);
+		selector.selectedKeys().remove(sk);
+		if(selection == 0) throw new Exception("timeout on net oper");
+	}
+
+	void terminateServer(ByteBuffer dataIn) throws Exception {
+		closeOut();
+		selectAndUnwrap(dataIn, ByteBuffer.allocate(0));
+		closeSocket();
+	}
+
+	@Override
+	void configureSocket() throws Exception {
+		sc.configureBlocking(false);
+		if(selector == null) selector = Selector.open();
+		register(SelectionKey.OP_READ);
+	}
+
+	void register(int flag) throws Exception { sk = sc.register(selector, flag); }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetSslScIO.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,100 @@
+package hh.dejsem.net;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLSocket;
+
+import hh.dejsem.K;
+import hh.lib.D;
+
+/**
+ * SSL blocking network IO (SSL socket)
+ */
+public class NetSslScIO implements NetIO, GetPut {
+	D d;
+	SSLServerSocket sslssc;
+	SSLSocket sslsc = null;
+	Socket sc = null;
+	InputStream in = null;
+	public OutputStream out = null;
+	ReadableByteChannel ic;
+	WritableByteChannel oc;
+	// ----------------------------------------------------------
+
+	public NetSslScIO(D d) {
+		this.d = d.klon(this);
+	}
+
+	public void bind(String host, int port) throws Exception {
+		sslssc = (SSLServerSocket) K.netNode.ssf.createServerSocket(port);
+	}
+
+	public boolean isBound() { return sslssc != null; }
+
+	public void accept() throws Exception {
+		sslssc.setSoTimeout(K.acceptTO);
+		sslsc = (SSLSocket)sslssc.accept();
+		getChannels();
+	}
+
+	public void connect(InetSocketAddress hostIp) throws Exception {
+		sslsc = (SSLSocket) K.netNode.sf.createSocket();
+		sslsc.setSoTimeout(K.socketTO);
+		sslsc.connect(hostIp);
+		sslsc.startHandshake();
+		getChannels();
+	}
+
+	public void getChannels() throws Exception {
+		in = sslsc.getInputStream();
+		ic = Channels.newChannel(in);
+		out = sslsc.getOutputStream();
+		oc = Channels.newChannel(out);
+	}
+
+	public boolean isConnected() { return sslsc != null && sslsc.isConnected() && !sslsc.isClosed(); }
+
+	public boolean get(ByteBuffer buf) throws Exception {		// false on EOD
+		try {
+			if(d.ll(5)) d.l(String.format("get from net, buf=%s ...", D.bufStat(buf)));
+			while(buf.hasRemaining()) { if(ic.read(buf) < 0) break; }
+			if(d.ll(5)) d.l(String.format("%d bytes read", buf.position()));
+			return buf.position() > 0;
+		}
+		catch(Exception x) {
+			closeSc();
+			throw new Exception(String.format("%s[get]: net channel read", d.logPrefix), x);
+		}
+	}
+
+	public int put(ByteBuffer buf) throws Exception {
+		int n = 0;
+		if(d.ll(5)) d.l("output to net, buf=" + D.bufStat(buf));
+		while(buf.hasRemaining()) {
+			n = 0;
+			try { n = oc.write(buf); }
+			catch(Exception x) {
+				closeSc();
+				throw new Exception("net channel write", x); }
+			if(d.ll(5)) d.l("" + n + " bytes written"); }
+		n = buf.position();
+		buf.clear();
+		return n;
+	}
+
+	public void closeSc() throws Exception { if(sslsc != null) { sslsc.close(); } }
+
+	public void closeSsc() throws Exception {
+		if(sslssc != null) {
+			sslssc.close();
+			sslssc = null; }
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetThread.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,32 @@
+package hh.dejsem.net;
+
+import android.os.AsyncTask;
+
+import hh.dejsem.K;
+import hh.lib.D;
+
+class NetThread extends AsyncTask<SrvCmd, String, Void> {
+	D d;
+	SrvCmd action;
+	Exception exception;
+
+	NetThread(D d) { this.d = d.klon(this); }
+
+	NetThread(D d, SrvCmd action) {
+		this(d);
+		this.action = action;
+		if(d.ll(4)) this.d.l("initFromContext, action=" + action);
+	}
+
+	@Override
+	protected Void doInBackground(SrvCmd... actions) {
+		if(d.ll(4)) this.d.l("doInBackground starting...");
+		exception = null;
+		try {
+			if(K.netNode == null) K.netNode = new NetNode(d);
+			if(K.cmdConn == null) K.cmdConn = new NetConnSrv(d);
+		}
+		catch(Exception e) {}
+		finally { return null; }
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetUDP.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,158 @@
+package hh.dejsem.net;
+
+import android.content.Context;
+import android.net.DhcpInfo;
+import android.net.wifi.WifiManager;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Enumeration;
+
+import hh.dejsem.K;
+import hh.lib.D;
+
+public class NetUDP {
+
+	final static String NULL_HOST = "0.0.0.0";
+
+	public static InetAddress localAddr = null;
+	public static InetAddress broadcastAddr = null;
+	static NetCrypt c = null;
+
+	D d;
+	public boolean go = true;
+	DatagramSocket socket = null;
+
+	public NetUDP(D d) throws SocketException, UnknownHostException {
+		this.d = d.klon(this);
+		synchronized(K.app) {
+			if(localAddr == null) {
+				localAddr = getLocalIp();
+				broadcastAddr = getBroadcastAddress();
+				if(c == null) c = new NetCrypt(d, null, "heslo", 0);
+			}
+		}
+	}
+
+	InetAddress getLocalIp() throws SocketException {
+		Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
+		for(; en.hasMoreElements(); ) {
+			NetworkInterface i = en.nextElement();
+			for(Enumeration<InetAddress> enumIpAddr = i.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
+				InetAddress ia = enumIpAddr.nextElement();
+				if( !ia.isLoopbackAddress() &&
+					!ia.toString().substring(0, 0).getBytes().equals((byte) -128) &&
+					Inet4Address.class.isInstance(ia)) return ia;
+			}
+		}
+		return null;
+	}
+
+	InetAddress getBroadcastAddress() throws UnknownHostException {
+		InetAddress broadcastAddr = null;
+		DhcpInfo dhcp = ((WifiManager)d.getAct().getApplicationContext().getSystemService(Context.WIFI_SERVICE)).getDhcpInfo();
+		if(dhcp != null && dhcp.ipAddress != 0) broadcastAddr = int2ia((dhcp.ipAddress & dhcp.netmask) | ~dhcp.netmask);
+		return broadcastAddr;
+	}
+
+	void setSocket() throws SocketException, UnknownHostException {
+		socket = new DatagramSocket(new InetSocketAddress(InetAddress.getByName(NULL_HOST), K.UDP_PORT));
+		socket.setBroadcast(true);
+	}
+
+	void closeSocket() { if(socket != null) socket.close(); }
+
+	InetAddress int2ia(int ia) throws UnknownHostException {
+		byte[] quad = new byte[4];
+		for(int k = 0; k < 4; k++) quad[k] = (byte)((ia >> k * 8) & 0xFF);
+		return InetAddress.getByAddress(quad);
+    }
+
+	DatagramPacket allocRecDgram() {
+		byte[] recvBuf = new byte[15000];
+		DatagramPacket packet = new DatagramPacket(recvBuf, recvBuf.length);
+		return packet;
+	}
+
+	String receiveUDPData(DatagramSocket socket, DatagramPacket packet) throws IOException {
+		socket.receive(packet);
+		int len = packet.getLength();
+		byte[] enc = Arrays.copyOf(packet.getData(), len);
+		String data = "";
+		try { data = c.decrypt(enc); }
+		catch(Exception e) { return null; }
+		return data;
+	}
+
+	/**
+	 * get InetSocketAddress of peer declaring itself as server side of peer connection
+	 * <br>● peer broadcasts ipaddr:port in UDP datagrams
+	 *
+	 * @return the inet socket address
+	 * @throws IOException the io exception
+	 */
+	public InetSocketAddress keepReceivingPeerIp() throws IOException {
+		if(d.ll(3)) d.l("getting peer ipaddr:port via UDP broadcast");
+		String data = null;
+		String key = "";
+		String peerIp = "";
+		final int numLen = 12;
+		int strLen = 0;
+		int beg = 0;
+		try {
+			setSocket();
+			DatagramPacket packet = allocRecDgram();
+			go = true;
+			while(go && peerIp.equals("") || peerIp.equals(localAddr.getHostAddress())) {
+				key = "";
+				while(go && !key.equals(K.UDP_ADVERT_KEY)) {
+					data = receiveUDPData(socket, packet);
+					beg = 0;
+					strLen = Integer.valueOf(data.substring(beg, beg + numLen));
+					beg += numLen;
+					key = data.substring(beg, beg + strLen);
+				}
+				beg += strLen;
+				strLen = Integer.valueOf(data.substring(beg, beg + 12));
+				beg += 12;
+				peerIp = data.substring(beg, beg + strLen);
+			}
+			if(go) {
+				beg += strLen;
+				int port = Integer.valueOf(data.substring(beg, beg + numLen));
+				return new InetSocketAddress(peerIp, port);
+			}
+		}
+		catch(IOException e) {
+			d.l(String.format("datagram exception: errno=%d, emsg=[%s]", d.errno(e), e.getMessage()));
+			throw e;
+		}
+		finally { closeSocket(); }
+		return null;
+	}
+
+	public void keepSendingPeerIp(String ipPort) throws Exception {
+		try {
+			byte[] sendData = c.encrypt(ipPort);
+			setSocket();
+			DatagramPacket packet = new DatagramPacket(sendData, sendData.length, broadcastAddr, K.UDP_PORT);
+			while(go) {
+				socket.send(packet);
+				try { Thread.sleep(1000, 0); } catch(InterruptedException e) {}
+			}
+		}
+		catch(Exception e) {
+			d.l(String.format("datagram exception: errno=%d, emsg=[%s]", d.errno(e), e.getMessage()));
+			throw e;
+		}
+		finally { closeSocket(); }
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/PullFromPeer.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,59 @@
+package hh.dejsem.net;
+
+import android.os.Handler;
+import android.os.Message;
+
+import java.io.IOException;
+import java.net.SocketException;
+
+import hh.dejsem.K;
+import hh.dejsem.fm.NetTaskRef;
+import hh.lib.D;
+
+/**
+ * copy from peer asynchronously in background
+ */
+public class PullFromPeer extends LongAsyncTask implements Handler.Callback {
+
+	NetConnPeer netConn;
+	Handler abendHandler = new Handler(this);   // handles requests for ABEND from thread
+
+	/**
+	 * Instantiates a new Pull from peer.
+	 *
+	 * @param d          the d
+	 * @param netTaskRef the net task ref
+	 * @throws Exception the exception
+	 */
+	public PullFromPeer(D d, NetTaskRef netTaskRef) throws Exception {
+		super(d, netTaskRef);
+		if(K.netNode == null) K.netNode = new NetNode(d);
+		netConn = new NetConnPeer(this.d, netTaskRef, true);
+		//SrvCmd.SETPEER.exec(d, netConn);	// bindSockToPeer() must be called here from main process because of serial cmdConn use // atavismus
+	}
+
+	@Override
+	protected Void doInBackground(Void... params) {
+		super.doInBackground(params);
+		String to = "";
+		try {
+			to = args.getString(K.FILE_TO_KEY);
+			try { netConn.acceptConnFromPeer(abendHandler); }
+			catch (SocketException e) { throw new IOException("accepting conn from peer", e); }
+			if(netTaskRef.go) netTaskRef.getProgress().es.setTargetSize(netConn.pullBatchSize());
+			if(netTaskRef.go) netConn.pullDataFromPeer(to); }
+		catch(Exception e) { if(!K.End.class.isInstance(e)) abend(String.format("copy from peer to dir %s", to), e); }
+		finally { try { netConn.connClose(); } catch(Exception e) {} }
+		return null;
+	}
+
+	@Override
+	void completePostExecute() { notifyRefresh(); }
+
+	@Override
+	public boolean handleMessage(Message msg) { // msg contains message from exception
+		abend("copy from peer", new Exception((String)(msg.obj)));
+		try { netConn.connClose(); } catch(Exception e) {}
+		return true;
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/PullFromServer.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,25 @@
+package hh.dejsem.net;
+
+import hh.dejsem.K;
+import hh.dejsem.fm.NetTaskRef;
+import hh.lib.D;
+
+/**
+ * copy from server asynchronously
+ */
+public class PullFromServer extends ServerCopy {
+
+	public PullFromServer(D d, NetTaskRef netTaskRef) throws Exception  { super(d, netTaskRef); }
+
+	@Override
+	protected Void doInBackground(Void... params) {
+		super.doInBackground();
+		try { if(netTaskRef.go) netConn.pullSrv(); }
+		catch(Exception e) { if(!(K.EntryNotFound.class.isInstance(e) || K.End.class.isInstance(e))) abend(null, e); }
+		finally { try { netConn.connClose(); } catch(Exception e) {} }
+		return null;
+	}
+
+	@Override
+	void completePostExecute() { notifyRefresh(); }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/PushToPeer.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,56 @@
+package hh.dejsem.net;
+
+import hh.dejsem.K;
+import hh.dejsem.fm.NetTaskRef;
+import hh.lib.D;
+
+/**
+ * copy to peer asynchronously
+ */
+public class PushToPeer extends LongAsyncTask {
+	NetConnPeer dataConn;
+
+	public PushToPeer(D d, NetTaskRef netTaskRef) throws Exception {
+		super(d, netTaskRef);
+		if (K.netNode == null) K.netNode = new NetNode(d);
+		dataConn = new NetConnPeer(d, netTaskRef, false);
+		// getPeerHost() must be called here from main process in order to use cmdConn serially
+		// netConn.peerIp = (InetSocketAddress)SrvCmd.GETPEER.exec(d, null);   // atavismus, PEER se teď získá přes UDP
+	}
+
+	@Override
+	protected Void doInBackground(Void... params) {
+		super.doInBackground(params);
+		try {
+			// netConn.peerIp = netConn.getPeerHost_UDP();   // přesunuto do NetConnPeer.pushBatchSize  // atavismus
+			if(netTaskRef.go) dataConn.pushBatchSize(netTaskRef.getProgress().es);
+		}
+		catch (Exception e) {
+			if(!K.End.class.isInstance(e)) abend("peer PUSH", e);
+			try { dataConn.connClose(); } catch(Exception x) {}
+			return null;
+		}
+
+		String fn = "";
+		try {
+			for(String from: args.getStringArray(K.FILES_FROM_KEY))
+				if(netTaskRef.go) {
+					fn = from;
+					dataConn.pushPeer(from);
+				}
+			if(netTaskRef.go) dataConn.endOfData();
+		}
+		catch (Exception e) {
+			if(!K.End.class.isInstance(e)) {
+				String peer = "";
+				if(dataConn.peerIp != null) peer = dataConn.peerIp.getHostName();
+				abend(String.format("COPY %s to peer %s", fn, peer), e);
+			}
+		}
+		finally { try { dataConn.connClose(); } catch(Exception e) {} }
+		return null;
+	}
+
+	@Override
+	void completePostExecute() { d.l(3, "PUSHPEER batch finished"); }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/PushToServer.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,26 @@
+package hh.dejsem.net;
+
+import hh.dejsem.K;
+import hh.dejsem.fm.NetTaskRef;
+import hh.lib.D;
+
+/**
+ * copy to server asynchronously
+ */
+public class PushToServer extends ServerCopy {
+
+	public PushToServer(D d, NetTaskRef netTaskRef) throws Exception  {
+		super(d, netTaskRef);
+	}
+
+	@Override
+	protected Void doInBackground(Void... params) {
+		super.doInBackground();
+		try { if(netTaskRef.go) netConn.pushSrv(); }
+		catch(Exception e) { if(!K.End.class.isInstance(e)) abend(null, e); }
+		return null;
+	}
+
+	@Override
+	void completePostExecute() { notifyRefreshFileList(K.MSG_REFRESH_SRV); }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/SendPeerHostThread.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,36 @@
+package hh.dejsem.net;
+
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+
+import java.net.SocketException;
+import java.net.UnknownHostException;
+
+import hh.lib.D;
+
+public class SendPeerHostThread extends Thread {
+	D d;
+	Handler abendHandler;
+	String ipPort = "";
+	Exception exception = null;
+	NetUDP netUDP;
+
+	public SendPeerHostThread(D d, Handler abendHandler, String ipPort) throws SocketException, UnknownHostException {
+		this.d = d.klon(this);
+		this.abendHandler = abendHandler;
+		this.ipPort = ipPort;
+		netUDP = new NetUDP(d);
+	}
+
+	public void run() {
+		try { netUDP.keepSendingPeerIp(ipPort); }
+		catch(Exception e) {
+			try { new Messenger(abendHandler).send(Message.obtain(null, 0, e.getMessage())); }
+			catch(RemoteException r) {}
+		}
+	}
+
+	public void stopAdv() { netUDP.go = false; }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/ServerCopy.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,18 @@
+package hh.dejsem.net;
+
+import hh.dejsem.K;
+import hh.dejsem.fm.NetTaskRef;
+import hh.lib.D;
+
+/**
+ * copy to/from server asynchronously
+ */
+class ServerCopy extends LongAsyncTask {
+
+	NetConnSrv netConn;
+
+	ServerCopy(D d, NetTaskRef netTaskRef) throws Exception  {
+		super(d, netTaskRef);
+		int port = args.getInt(K.PORT_KEY, 0);
+		netConn = new NetConnSrv(this.d, String.format("[%d]", port), netTaskRef); }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/SrvCmd.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,183 @@
+package hh.dejsem.net;
+
+import hh.dejsem.K;
+import hh.lib.D;
+
+public enum SrvCmd {
+	/**
+	 * synchronous actions on cmd connection to server executed in separate thread
+	 * <p>
+	 * ● actions are initiated via execute().get(), i.e. blocking
+	 * <p>
+	 * ● actions should be short time, immediate
+	 * <p>
+	 * ● after action (or batch of actions) cmd connection needs to be closed
+	 * <p>
+	 * ● connection close is up to caller (to be able to do actions in batch)
+	 * <p>
+	 * ● SETPEER operates first on peer connection (binds to port) and then sends ip:port to server
+	 */
+
+	CLOSE("CLOSE___") {         // close connection on command port
+		@Override
+		public Object action(Object... parms) throws Exception {
+			K.cmdConn.connClose();
+			return null;
+		}
+	},
+
+	CREATEDIR("CREATDIR") {     // create directory
+		@Override
+		public Object action(Object... parms) throws Exception {
+			K.cmdConn.createDir((String) parms[0]);
+			return null;
+		}
+	},
+
+	DELETE("DELETE__") {        // recursively delete object
+		@Override
+		public Object action(Object... parms) throws Exception {
+			K.cmdConn.delete((String) parms[0]);
+			return null;
+		}
+	},
+
+	EXPOSE("EXPOSE__") {
+		@Override
+		public Object action(Object... parms) throws Exception {
+			return K.cmdConn.expose((String) parms[0]);
+		}
+	},
+
+	FREE("FREE____") {
+		@Override
+		public Object action(Object... parms) throws Exception {
+			return K.cmdConn.getFreeSpace();
+		}
+	},
+
+	GETPEER("GETPEER_") {
+		@Override
+		public Object action(Object... parms) throws Exception {
+			return K.cmdConn.getPeerHost();
+		}
+	},
+
+	HIDE("HIDE____") {
+		@Override
+		public Object action(Object... parms) throws Exception {
+			K.cmdConn.hide((String) parms[0]);
+			return null;
+		}
+	},
+
+	MOVE("MOVE____") {
+		@Override
+		public Object action(Object... parms) throws Exception {
+			K.cmdConn.move((String) parms[0], (String) parms[1]);
+			return null;
+		}
+	},
+
+	LONGTASK {
+		@Override
+		public Object action(Object... parms) throws Exception {
+			return K.cmdConn.longtask();
+		}
+	},
+
+	PULLCLIP {
+		@Override
+		public Object action(Object... parms) throws Exception {
+			return K.cmdConn.pullClip((String) parms[0]);
+		}
+	},
+
+	PULLHIST {
+		@Override
+		public Object action(Object... parms) throws Exception {
+			return K.cmdConn.pullHistList();
+//			return null;
+		}
+	},
+
+	PULLLIST {
+		@Override
+		public Object action(Object... parms) throws Exception {
+			K.cmdConn.pullListSrv((String) parms[0], (String) parms[1]);
+			return null;
+		}
+	},
+
+	PUSHCLIP {
+		@Override
+		public Object action(Object... parms) throws Exception {
+			K.cmdConn.pushClip((NetData)parms[0]);
+			return null;
+		}
+	},
+	RECKON("RECKON__") {
+		@Override
+		public Object action(Object... parms) throws Exception {
+			return K.cmdConn.reckon((String) parms[0]);
+		}
+	},
+
+	SETPEER("SETPEER_") {   // atavismus z doby před použitím UDP
+		@Override
+		public Object action(Object... parms) throws Exception {
+			((NetConnPeer)parms[0]).bindSockToPeer();
+			return null;
+		}
+	};
+
+	D d;
+	final String cmd;
+
+	SrvCmd() {
+		this.cmd = name();
+	}
+
+	SrvCmd(String cmd) {
+		this.cmd = cmd;
+	}
+
+	public abstract Object action(Object... args) throws Exception;
+
+	public Object exec(D d, Object parm) throws Exception {
+		return exec(d, new Object[]{parm});
+	}
+
+	public Object exec(D d, Object[] parms) throws Exception {
+		this.d = d.klon(this);
+		SrvCmdThread t = new SrvCmdThread(d, this, parms);
+		t.start();
+		t.join();
+		if (t.exception == null) return t.result;
+		else throw t.exception;
+	}
+
+	static class SrvCmdThread extends Thread {
+		D d;
+		public SrvCmd action;
+		public Object[] args;
+		public Object result;
+		public Exception exception = null;
+
+		public SrvCmdThread(D d, SrvCmd action, Object[] args) {
+			this.d = d.klon(this);
+			this.action = action;
+			this.args = args;
+		}
+
+		public void run() {
+			try {
+				if (K.netNode == null) K.netNode = new NetNode(d);
+				if (K.cmdConn == null) K.cmdConn = new NetConnSrv(d);    // cmd conn to server
+				if (d.ll(2)) d.l("--->action=" + action);
+				result = action.action(args);
+			} catch (Exception e) { this.exception = e; }
+		}
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/StreamIO.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,51 @@
+package hh.dejsem.net;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class StreamIO {
+	/**----------------------------------------------------------
+	 * specific network IO (java.io) operations
+	 * ----------------------------------------------------------*/
+	static int readStream(InputStream in, byte[] b, int i, int n) throws Exception {
+		int r = 0, rr = 0;
+		while(rr < n) {
+			if((r = in.read(b, i + rr, n - rr)) < 0) break;
+			rr += r; }
+		if(rr == 0 && r < 0) rr = r;
+		return rr;
+	}
+
+	static long getNum(InputStream in) throws Exception { 				// input stream may be net or file
+		byte[] b = new byte[12];
+		int n = 0;
+		while(n < b.length) {
+			int r;
+			r = readStream(in, b, n, b.length - n);
+			if(r < 0) return -2;										// rc = -1 means entity is directory
+			else n += r; }
+		if(n < b.length)throw new Exception("got deficient file attr");
+		return Long.valueOf(new String(b));
+	}
+
+	static void putNum(long n, OutputStream out) throws Exception { 	// output stream may be net or file
+		byte[] b = String.format("%012d", n).getBytes();
+		out.write(b);
+	}
+
+	static String getStr(InputStream in) throws Exception { 			// input stream may be net or file
+		int n = 0;
+		byte[] b = new byte[512];
+		if((n = (int)getNum(in)) == -2) return null;					// EOF in input stream
+		if(n == 0) return "";
+		int r = 0;
+		while((r += readStream(in, b, r, n - r)) < n);
+		return new String(b, 0, n);
+	}
+
+	static void putStr(String fn, OutputStream out) throws Exception {	// output stream may be net or file
+		byte[] b = fn.getBytes();
+		putNum(b.length, out);
+		out.write(b);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/USBBus.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,20 @@
+package hh.dejsem.net;
+
+import java.util.Date;
+
+public class USBBus {
+	static class Const {
+		static String PFX = "//";
+	}
+
+	static class Dgram {
+		static Dgram dgram(int id) { return new Dgram(id); }
+		String prefix = Const.PFX;
+		long timeStamp = new Date().getTime();
+		int id;
+		Dgram(int id) { this.id = id; }
+		public String toString() {
+			return String.format("Dgram: %s %d %d", prefix, timeStamp, id);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/trash/TitleStat.java	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,115 @@
+package hh.dejsem.trash;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.AnimationDrawable;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v7.widget.Toolbar;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import hh.dejsem.K;
+import hh.dejsem.Prefs;
+import hh.dejsem.R;
+import hh.dejsem.fm.NetTaskRef;
+import hh.lib.D;
+
+/**
+ * ... obsah 1.řádky (záhlaví) okna GUI aktivit a jejich fragmentů</br>
+ * ● záhlaví má 4 pole
+ * <ol>
+ *     <im>app id</im>
+ *     <im>proměnné stavové pole probíhajících přenosů</im>
+ *     <im>momentálně nastavený server a číslo šifrovaného kanálu</im>
+ *     <im>label aktuální komponenty GUI</im>
+ * </ol></br>
+ * ● každá komponenta GUI
+ *
+ */
+public class TitleStat extends Toolbar implements Runnable,  Handler.Callback {
+	static boolean pending = false;
+	public static void setPending() { pending = true; }
+	public static void unSetPending() {
+		pending = false;
+	}
+
+	ViewGroup container = null;
+
+	D d;
+	View titleView;
+	ImageView pendingIcon;
+	TextView pendingView;
+	TextView titleTextView;
+	String titleText = "";
+	Handler handler = new Handler(this);
+	public Messenger msgr = new Messenger(handler);
+
+	public TitleStat(Context context, AttributeSet attrs) {
+		super(context, attrs);
+		d = new D(context, getClass().getSimpleName());
+	}
+
+	@Override
+	public boolean handleMessage(Message msg) {
+		switch(msg.what) {
+			case K.MSG_WATCH_PROGRESS:
+				update();
+				return true;
+		}
+		return false;
+	}
+
+	@Override
+	public void run() {
+		update();
+	}
+
+	public TitleStat activate() {
+		final Context c = d.getContext();
+		final Resources r = c.getResources();
+		if(Prefs.host == null) new Prefs().refreshPrefs(c);
+
+		((TextView)findViewById(R.id.title_left_text)).setText(String.format("%s.%s", r.getString(R.string.app_name), r.getString(R.string.app_version_name)));
+		((TextView)findViewById(R.id.channel)).setText(String.format("%s ch %s", Prefs.host.subSequence(0, Prefs.host.indexOf('.')), r.getString(R.string.channel)));
+		titleTextView = findViewById(R.id.title_right_text);
+		pendingIcon = findViewById(R.id.pending_icon);
+	    ((AnimationDrawable)pendingIcon.getBackground()).start();
+//	    ((AnimationDrawable)((ImageView) this.titleView.findViewById(R.id.pending_icon)).getBackground()).start();
+//		pendingIcon.setImageResource(R.drawable.pending);
+		pendingView = findViewById(R.id.pending_progress);
+//		OnClickListener lastMileListener = new OnClickListener() {
+//			@Override
+//			public void onClick(View v) {
+//				c.startActivity(new Intent(c, LastMileMeterAct.class));
+//			} };
+//		findViewById(R.id.last_mile_button).setOnClickListener(lastMileListener);
+		return this;
+	}
+
+	public TitleStat setText(String titleText) { this.titleText = titleText; return this; }
+
+	public void update() {
+		if(pending) {
+			/*((NotificationManager)d.getContext().getSystemService(Context.NOTIFICATION_SERVICE)).getActiveNotifications().length
+					// od API 23 se dá využít ke kontrole přítomnosti progres-notifkace na notification bar*/
+			String progressText = "";
+			synchronized(K.transfProgress.statSet) {
+				for(NetTaskRef tr : K.transfProgress.statSet) {
+					progressText += String.format("%2d%% ", tr.getProgress().es.getPct());
+				}
+			}
+			pendingView.setText(progressText);
+			handler.postDelayed(this, K.transfProgress.refreshInterval);
+		}
+		pendingView.setVisibility(pending ? View.VISIBLE : View.INVISIBLE);
+		pendingIcon.setVisibility(pending ? View.VISIBLE : View.INVISIBLE);
+		titleTextView.setText(titleText);
+		invalidate();
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/drawable/pending.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<animation-list
+	xmlns:android="http://schemas.android.com/apk/res/android"
+    android:oneshot="false">
+
+    <item android:drawable="@drawable/x00" android:duration="120" />
+    <item android:drawable="@drawable/x12" android:duration="120" />
+    <item android:drawable="@drawable/x30" android:duration="120" />
+    <item android:drawable="@drawable/x48" android:duration="120" />
+    <item android:drawable="@drawable/x60" android:duration="120" />
+	<item android:drawable="@drawable/x48" android:duration="120" />
+	<item android:drawable="@drawable/x30" android:duration="120" />
+	<item android:drawable="@drawable/x12" android:duration="120" />
+
+</animation-list>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/basic_layout.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+	xmlns:android="http://schemas.android.com/apk/res/android"
+	xmlns:hh="http://schemas.android.com/apk/res-auto"
+	android:id="@+id/main"
+	android:layout_width="match_parent"
+	android:layout_height="match_parent"
+	android:background="#000"
+	>
+
+	<FrameLayout
+		android:id="@+id/title_bar"
+		android:layout_width="match_parent"
+		android:layout_height="wrap_content"
+		/>
+
+	<FrameLayout
+		android:id="@+id/panel"
+		android:layout_width="match_parent"
+		android:layout_height="match_parent"
+		android:layout_below="@id/title_bar"
+		/>
+
+</RelativeLayout>
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/dir_view_layout.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+	android:layout_width="match_parent"
+	android:layout_height="match_parent"
+	>
+	
+	<RelativeLayout
+		android:id="@+id/dir_list_header"
+		android:layout_width="match_parent"
+		android:layout_height="wrap_content"
+		android:layout_alignParentTop="true"
+		android:layout_alignParentLeft="true"
+		android:background="@color/dir_name_bg"
+		>
+
+		<ImageView
+			android:id="@+id/up"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			android:layout_alignParentLeft="true"
+			android:layout_centerVertical="true"
+			android:paddingLeft="6dp"
+			android:paddingRight="6dp"
+			android:src="@raw/up"
+		    android:visibility="gone"
+			/>
+
+		<TextView
+			android:id="@+id/site"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			android:layout_toLeftOf="@+id/menu"
+			android:layout_centerVertical="true"
+			android:gravity="right"
+			android:textSize="28sp"
+			android:textColor="@color/dir_name_bg2"
+			android:textStyle="italic"
+			android:text="site"
+			></TextView>
+
+		<HorizontalScrollView
+			android:id="@+id/scroll"
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:layout_toRightOf="@+id/up"
+			android:layout_toLeftOf="@+id/menu"
+			android:layout_centerVertical="true"
+			android:layout_gravity="center_vertical"
+			>
+
+			<LinearLayout
+				android:id="@+id/dirname"
+				android:layout_width="match_parent"
+				android:layout_height="wrap_content"
+				android:orientation="horizontal"
+				>
+
+			</LinearLayout>
+
+		</HorizontalScrollView>
+
+		<ImageView
+			android:id="@+id/menu"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			android:layout_alignParentRight="true"
+			android:layout_centerVertical="true"
+			android:paddingLeft="6dp"
+			android:paddingRight="6dp"
+			android:src="@raw/menu"
+			/>
+
+	</RelativeLayout>
+
+	<ListView
+		android:id="@+id/dir_list_view"
+		android:layout_width="match_parent"
+		android:layout_height="match_parent"
+		android:layout_below="@+id/dir_list_header"
+		android:layout_alignParentLeft="true"
+		android:background="@color/file_list_bg"
+		>
+
+	</ListView>
+
+</RelativeLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/file_entry.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,71 @@
+<LinearLayout
+	xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+   	android:background="@color/file_list_bg"
+	>
+
+	<CheckBox
+	    android:id="@+id/fcb"
+	    android:layout_width="wrap_content"
+	    android:layout_height="wrap_content"
+	    android:focusable="false"
+		/>
+	
+	<ImageView
+	    android:id="@+id/fic"
+	    android:layout_width="wrap_content"
+	    android:layout_height="wrap_content"
+		android:layout_marginRight="3dp"
+	    android:adjustViewBounds="true"
+	    android:scaleType="centerInside"
+		/>
+
+    <LinearLayout
+        android:id="@+id/fllv"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+	    >
+
+		<TextView
+		    android:id="@+id/fname"
+		    android:layout_width="match_parent"
+		    android:layout_height="wrap_content"
+		    android:ellipsize="end"
+		    android:maxLines="1"
+		    android:textColor="@color/file_name_color"
+		    android:textSize="@dimen/file_name_size"
+			/>
+		    
+	    <LinearLayout
+	        android:id="@+id/fllh"
+	        android:layout_width="match_parent"
+	        android:layout_height="wrap_content"
+	        android:orientation="horizontal"
+		    >
+
+			<TextView
+			    android:id="@+id/fsize"
+			    android:layout_width="0dp"
+			    android:layout_height="wrap_content"
+	        	android:layout_weight="1"
+			    android:textColor="@color/file_size_color"
+			    android:textSize="@dimen/file_attr_size"
+				/>
+
+			<TextView
+			    android:id="@+id/fdate"
+			    android:layout_width="0dp"
+			    android:layout_height="wrap_content"
+	        	android:layout_weight="1"
+			    android:textColor="@color/file_date_color"
+			    android:textSize="@dimen/file_attr_size"
+				/>
+
+		</LinearLayout>
+
+	</LinearLayout>
+	
+</LinearLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/flat_button.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+	>
+
+	<TextView
+		android:id="@+id/text"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:background="@android:color/transparent"
+		android:typeface="sans"
+		android:textStyle="normal"
+		android:gravity="center"
+	    android:text="button"
+		/>
+
+	<FrameLayout
+		android:id="@+id/encircle"
+		android:layout_width="match_parent"
+		android:layout_height="match_parent"
+		android:background="@android:color/transparent"
+		android:layout_alignTop="@+id/text"
+		android:layout_alignLeft="@+id/text"
+		android:layout_alignBottom="@+id/text"
+		android:layout_alignRight="@+id/text"
+		/>
+
+</RelativeLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/h_border_thin.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="@dimen/border_thin"
+    android:background="@color/bg0" >
+
+</View>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/hack.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" >
+    
+    <Button
+        android:id="@+id/bHack_1"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:text="hack dir" />
+
+    <Button
+        android:id="@+id/bHack_2"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:text="put net" />
+    
+    <Button
+        android:id="@+id/bHack_3"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:text="close net" />
+
+    <Button
+        android:id="@+id/bHack_4"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:text="hack SD card" />
+
+</LinearLayout>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/hist_entry.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content" >
+
+	<TextView
+	    android:id="@+id/hdate"
+	    android:layout_width="wrap_content"
+	    android:layout_height="wrap_content"
+	    android:layout_alignParentRight="true"
+	    android:textColor="@color/file_date_color"
+	    android:textSize="@dimen/file_attr_size"
+    	android:paddingRight="3dp"
+		/>
+    
+	<TextView
+	    android:id="@+id/hsize"
+	    android:layout_width="wrap_content"
+	    android:layout_height="wrap_content"
+	    android:layout_toLeftOf="@id/hdate"
+	    android:textColor="@color/file_size_color_positive"
+	    android:textSize="@dimen/file_attr_size"
+		/>
+	    
+	<TextView
+	    android:id="@+id/htext"
+	    android:layout_width="match_parent"
+	    android:layout_height="wrap_content"
+		android:layout_toLeftOf="@id/hsize"
+	    android:ellipsize="end"
+	    android:maxLines="1"
+	    android:textSize="@dimen/file_name_size"
+    	android:textColor="@color/file_name_color_positive"
+    	android:paddingLeft="3dp"
+	    android:text="bleee"
+		/>
+	
+</RelativeLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/hist_list.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+	xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical"
+	>
+
+	<include
+		android:id="@+id/sort_header"
+		android:layout_width="match_parent"
+		android:layout_height="wrap_content"
+		layout="@layout/sort_header"
+		/>
+	
+	<ListView
+	    android:id="@+id/listHist"
+	    android:layout_width="match_parent"
+	    android:layout_height="match_parent"
+		android:layout_below="@+id/sort_header"
+	    android:cacheColorHint="@color/hist_list_bg_positive"
+	    android:background="@color/hist_list_bg_positive"
+	    android:divider="@android:color/black"
+	    android:dividerHeight="1px"
+		/>
+
+</RelativeLayout>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/last_mile.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+	xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#000"
+	>
+
+	<ScrollView
+	    android:id="@+id/sc"
+	    android:layout_width="match_parent"
+	    android:layout_height="match_parent"
+		android:layout_alignParentTop="true"
+		>
+	    
+		<TextView
+	    	android:id="@+id/tv"
+		    android:layout_width="match_parent"
+		    android:layout_height="wrap_content"
+		    />
+
+	</ScrollView>
+
+	<hh.dejsem.FlatButton
+		android:id="@+id/intrr"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_alignParentTop="true"
+		android:layout_marginTop="24dp"
+		android:layout_alignParentRight="true"
+		android:textColor="@color/dir_name_color"
+		android:text="stop/restart"
+		/>
+
+</RelativeLayout>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/main_panel.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+	xmlns:android="http://schemas.android.com/apk/res/android"
+	xmlns:hh="http://schemas.android.com/apk/res-auto"
+	android:id="@+id/main"
+	android:layout_width="match_parent"
+	android:layout_height="match_parent"
+	android:background="#000"
+	>
+
+	<TextView
+		android:id="@+id/note1"
+		android:layout_width="match_parent"
+		android:layout_height="wrap_content"
+		android:text="long click buttons"
+		android:textColor="#fd0"
+		/>
+
+	<LinearLayout
+		android:id="@+id/brow1"
+		android:layout_width="match_parent"
+		android:layout_height="wrap_content"
+		android:layout_below="@id/note1"
+		android:orientation="horizontal"
+		>
+
+		<Button
+			android:id="@+id/bPush"
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:layout_weight="1"
+			android:text="PUSH\nclipboard"
+			/>
+
+		<Button
+			android:id="@+id/bPull"
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:layout_weight="1"
+			android:text="PULL\nclipboard"
+			/>
+
+		<Button
+			android:id="@+id/bHist"
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:layout_weight="1"
+			android:text="clipboard\nhistory"
+			/>
+
+		<ImageView
+			android:id="@+id/bMenu"
+			android:layout_width="wrap_content"
+			android:layout_height="match_parent"
+			android:layout_alignParentRight="true"
+			android:layout_centerVertical="true"
+			android:paddingLeft="6dp"
+			android:paddingRight="6dp"
+			android:src="@raw/menu"
+			android:contentDescription="TODO"/>
+	</LinearLayout>
+
+	<TextView
+		android:id="@+id/dir_list_header"
+		android:layout_width="match_parent"
+		android:layout_height="wrap_content"
+		android:layout_below="@id/brow1"
+		android:text="local clipboard content"
+		android:textColor="#fd0"
+		/>
+
+	<ScrollView
+		android:id="@+id/cbScroll"
+		android:layout_width="match_parent"
+		android:layout_height="match_parent"
+		android:layout_above="@id/brow2"
+		android:layout_below="@+id/dir_list_header"
+		>
+
+		<TextView
+			android:id="@+id/cbText"
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:background="@color/file_list_bg"
+			/>
+			<!-- android:textAppearance="?android:attr/textAppearanceSmallInverse" -->
+
+	</ScrollView>
+
+	<LinearLayout
+		android:id="@+id/brow2"
+		android:layout_width="match_parent"
+		android:layout_height="wrap_content"
+		android:layout_alignParentBottom="true"
+		android:orientation="horizontal"
+		>
+
+		<Button
+			android:id="@+id/bFiles"
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:layout_weight="1"
+			android:text="SERVER files"
+			/>
+
+		<Button
+			android:id="@+id/bPeer"
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:layout_weight="1"
+			android:text="PEER files"
+			/>
+
+		<Button
+			android:id="@+id/bNet"
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:layout_weight="1"
+			android:text="network"
+			/>
+
+	</LinearLayout>
+
+</RelativeLayout>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/net_info.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#000"
+    >
+
+   <!-- <FrameLayout
+        android:id="@+id/title_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        />-->
+
+    <RelativeLayout
+        android:id="@+id/panel"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_below="@id/title_bar"
+        >
+
+        <RelativeLayout
+            android:id="@+id/dashboard"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            >
+
+            <TextView
+                android:id="@+id/myip"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentLeft="true"
+                />
+
+            <hh.dejsem.FlatButton
+                android:id="@+id/throughput"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:textColor="@color/dir_name_color"
+                android:text="connection throughput"
+                />
+
+        </RelativeLayout>
+
+        <ScrollView
+            android:id="@+id/sc"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_below="@+id/dashboard"
+            >
+
+            <TextView
+                android:id="@+id/tv"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                />
+
+        </ScrollView>
+
+    </RelativeLayout>
+
+</RelativeLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/peer_file_manager.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+	>
+
+    <include
+        android:id="@+id/local"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        layout="@layout/dir_view_layout"
+	    />
+
+</LinearLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/progress_entry.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical"
+    >
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        >
+
+        <ImageView
+            android:id="@+id/cancel"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp"
+            android:src="@raw/cancel"
+            />
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            >
+
+            <LinearLayout
+                android:id="@+id/progressTitle"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                >
+
+                <TextView
+                    android:id="@+id/progressFiles"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="TextView"
+                    />
+
+                <TextView
+                    android:id="@+id/progressFn"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:singleLine="true"
+                    android:text="TextView"
+                    />
+
+            </LinearLayout>
+
+            <TextView
+                android:id="@+id/progressNum"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginHorizontal="8dp"
+                android:text="TextView"
+                />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <ProgressBar
+        android:id="@+id/progressBar"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="8dp"
+        />
+
+</LinearLayout>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/progress_list.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:orientation="vertical" >
+
+    <ListView
+    	android:id="@+id/progress_container"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" >
+    </ListView>
+
+</LinearLayout>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/server_file_manager.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical"
+	>
+
+    <include
+        android:id="@+id/remote"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:layout_weight="1"
+        layout="@layout/dir_view_layout" />
+
+    <include
+        android:id="@+id/local"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:layout_weight="1"
+        layout="@layout/dir_view_layout" />
+
+</LinearLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/sort_header.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+	>
+
+	<TextView
+	    android:id="@+id/sText"
+	    android:layout_width="0dp"
+	    android:layout_height="wrap_content"
+	    android:layout_weight="1"
+	    android:gravity="center_vertical|center_horizontal"
+	    android:clickable="true"
+    	android:background="@color/bg1"
+    	android:paddingLeft="@dimen/border"
+    	android:paddingTop="@dimen/border_thin"
+    	android:paddingBottom="@dimen/border_thin"
+	    android:text="by text"
+		/>
+	
+	<include layout="@layout/v_border_thin" />    	
+	    
+	<TextView
+	    android:id="@+id/sSize"
+	    android:layout_width="0dp"
+	    android:layout_height="wrap_content"
+	    android:layout_weight="1"
+	    android:gravity="center_vertical|center_horizontal"
+    	android:background="@color/bg1"
+    	android:paddingLeft="@dimen/border"
+    	android:paddingTop="@dimen/border_thin"
+    	android:paddingBottom="@dimen/border_thin"
+	    android:text="by targetSize"
+		/>
+	
+	<include layout="@layout/v_border_thin" />    	
+	    
+	<TextView
+	    android:id="@+id/sDate"
+	    android:layout_width="0dp"
+	    android:layout_height="wrap_content"
+	    android:layout_weight="1"
+	    android:gravity="center_vertical|center_horizontal"
+    	android:background="@color/bg1"
+    	android:paddingLeft="@dimen/border"
+    	android:paddingTop="@dimen/border_thin"
+    	android:paddingBottom="@dimen/border_thin"
+	    android:text="by date"
+		/>
+	
+</LinearLayout>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/title_bar.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<hh.dejsem.TitleBar
+	xmlns:android="http://schemas.android.com/apk/res/android"
+	xmlns:app="http://schemas.android.com/apk/res-auto"
+	android:layout_width="match_parent"
+	android:layout_height="wrap_content"
+	android:background="@color/title_bg"
+	>
+
+	<View
+		android:id="@+id/title_height"
+		android:layout_width="1dp"
+		android:layout_height="24dp"
+		android:layout_alignParentLeft="true"
+		android:layout_alignParentTop="true"
+	    />
+
+	<LinearLayout
+		android:id="@+id/title_left"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_alignParentLeft="true"
+		android:layout_alignParentTop="true"
+		android:layout_alignBottom="@id/title_height"
+        android:gravity="center"
+		android:orientation="horizontal"
+	    >
+
+		<TextView
+			android:id="@+id/title_left_text"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			android:paddingLeft="6dp"
+			android:paddingRight="6dp"
+			android:ellipsize="end"
+			android:singleLine="true"
+			android:text="@string/app_name"
+			/>
+
+		<ImageView
+			android:id="@+id/pending_icon"
+			android:layout_width="18dp"
+			android:layout_height="18dp"
+			android:background="@drawable/pending"
+			android:visibility="invisible"
+			/>
+
+		<TextView
+			android:id="@+id/pending_progress"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			android:gravity="left"
+			android:paddingLeft="6dp"
+			android:paddingRight="6dp"
+			android:ellipsize="end"
+			android:singleLine="true"
+            android:textColor="@color/dir_name_color"
+			android:visibility="invisible"
+			/>
+
+	</LinearLayout>
+
+	<LinearLayout
+		android:id="@+id/title_right"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_alignParentRight="true"
+		android:layout_alignParentTop="true"
+		android:layout_alignBottom="@id/title_height"
+		android:gravity="center"
+		android:orientation="horizontal"
+	    >
+
+		<TextView
+			android:id="@+id/channel"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			android:paddingLeft="6dp"
+			android:paddingRight="6dp"
+			android:gravity="right"
+			android:ellipsize="end"
+			android:singleLine="true"
+			android:textColor="@color/blue_font"
+			/>
+
+		<include layout="@layout/v_border_thin" />
+
+		<TextView
+			android:id="@+id/title_right_text"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			android:paddingLeft="6dp"
+			android:paddingRight="6dp"
+			android:gravity="right"
+			android:ellipsize="end"
+			android:singleLine="true"
+			android:textColor="@color/dir_name_color"
+			/>
+
+		<include layout="@layout/v_border_thin"
+			android:visibility="gone"
+			/>
+
+		<!--<hh.dejsem.FlatButton
+			android:id="@+id/last_mile_button"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			app:text_padding="2dp"
+			app:text_paddingTop="0dp"
+			android:singleLine="true"
+			android:text="throughput"
+			android:visibility="gone"
+			/>-->
+
+	</LinearLayout>
+
+</hh.dejsem.TitleBar>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/upload_panel.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+	xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+	android:background="#000"
+	>
+
+	<LinearLayout
+		android:id="@+id/buttons"
+		android:layout_width="match_parent"
+		android:layout_height="wrap_content"
+		android:orientation="horizontal"
+		>
+
+		<Button
+			android:id="@+id/analyze"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			android:layout_weight="1"
+			android:text="analyze"
+			/>
+
+		<Button
+			android:id="@+id/expose"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			android:layout_weight="1"
+			android:text="expose"
+			android:visibility="gone"
+			/>
+	</LinearLayout>
+
+	<ScrollView
+		android:id="@+id/sc"
+		android:layout_width="fill_parent"
+		android:layout_height="fill_parent"
+		android:layout_below="@+id/buttons"
+		>
+
+		<TextView
+		android:id="@+id/tv"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			/>
+
+	</ScrollView>
+
+</RelativeLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/v_border_thin.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/border_thin"
+    android:layout_height="fill_parent"
+    android:background="@color/bg0"
+	>
+
+</View>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/word_dialog.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/llv"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical" >
+
+    <EditText
+        android:id="@+id/enter_text"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:ems="10" >
+    </EditText>
+        
+    <CheckBox
+        android:id="@+id/visible"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="visible" />
+
+</LinearLayout>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_common.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <!--<item
+		android:id="@+id/menu_select_all"
+		android:title="select all"/>-->
+    <item
+        android:id="@+id/menu_prio_refresh_dir_list"
+        android:title="refresh dir list"/>
+    <group
+        android:id="@+id/menu_prio_home">
+        <item
+            android:id="@+id/menu_prio_go_home"
+            android:title="go to home dir"/>
+    </group>
+    <item android:title="selection">
+        <menu>
+            <item
+                android:id="@+id/menu_select_all"
+                android:title="select all"/>
+            <item
+                android:id="@+id/menu_clear_selection"
+                android:title="clear selection"/>
+        </menu>
+    </item>
+    <item android:title="get info">
+        <menu>
+            <item
+                android:id="@+id/menu_size_of_selection"
+                android:title="targetSize of selection"/>
+            <item
+                android:id="@+id/menu_free_space"
+                android:title="display free space"/>
+        </menu>
+    </item>
+    <item
+        android:id="@+id/menu_dir_list"
+        android:title="directory list">
+        <menu>
+            <item
+                android:id="@+id/menu_refresh_dir_list"
+                android:title="refresh dir list"/>
+            <group android:id="@+id/menu_dirlist_home">
+                <item
+                    android:id="@+id/menu_go_home"
+                    android:title="go to home dir"/>
+                <item
+                    android:id="@+id/menu_set_home_dir"
+                    android:title="set current dir as home"/>
+            </group>
+            <item
+                android:title="reorder list">
+                <menu>
+                    <item
+                        android:id="@+id/menu_order_name_asc"
+                        android:title="by name asc..."/>
+                    <item
+                        android:id="@+id/menu_order_name_desc"
+                        android:title="by name desc..."/>
+                    <item
+                        android:id="@+id/menu_order_size_asc"
+                        android:title="by targetSize asc..."/>
+                    <item
+                        android:id="@+id/menu_order_size_desc"
+                        android:title="by targetSize desc..."/>
+                    <item
+                        android:id="@+id/menu_order_date_asc"
+                        android:title="by date asc..."/>
+                    <item
+                        android:id="@+id/menu_order_date_desc"
+                        android:title="by date desc..."/>
+                </menu>
+            </item>
+        </menu>
+    </item>
+
+</menu>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_dir_list.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <item
+		android:title="directory list">
+		<menu>
+			<item
+				android:id="@+id/menu_refresh_dir_list"
+				android:title="refresh dir list"/>
+			<group android:id="@+id/menu_dirlist_home">
+				<item
+					android:id="@+id/menu_go_home"
+					android:title="go to home dir"/>
+				<item
+					android:id="@+id/menu_set_home_dir"
+					android:title="set current dir as home"/>
+			</group>
+			<item
+				android:title="reorder list">
+				<menu>
+					<item
+						android:id="@+id/menu_order_name_asc"
+						android:title="by name asc..."/>
+					<item
+						android:id="@+id/menu_order_name_desc"
+						android:title="by name desc..."/>
+					<item
+						android:id="@+id/menu_order_size_asc"
+						android:title="by targetSize asc..."/>
+					<item
+						android:id="@+id/menu_order_size_desc"
+						android:title="by targetSize desc..."/>
+					<item
+						android:id="@+id/menu_order_date_asc"
+						android:title="by date asc..."/>
+					<item
+						android:id="@+id/menu_order_date_desc"
+						android:title="by date desc..."/>
+				</menu>
+			</item>
+		</menu>
+	</item>
+
+</menu>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_info.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <item android:title="get info">
+        <menu>
+            <item
+                android:id="@+id/menu_size_of_selection"
+                android:title="targetSize of selection"/>
+            <item
+                android:id="@+id/menu_free_space"
+                android:title="display free space"/>
+        </menu>
+    </item>
+</menu>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_local_operations.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <item
+        android:id="@+id/menu_file_operations"
+        android:title="file operations"
+        >
+        <menu>
+            <item
+                android:id="@+id/menu_server_copy"
+                android:title="upload selected"/>
+            <item
+                android:id="@+id/menu_move"
+                android:title="move selected"/>
+            <item
+                android:id="@+id/menu_delete"
+                android:title="delete selected"/>
+            <item
+                android:id="@+id/menu_rename"
+                android:title="rename"/>
+            <item
+                android:id="@+id/menu_share"
+                android:title="share"/>
+            <item
+                android:id="@+id/menu_expose"
+                android:title="expose"/>
+            <item
+                android:id="@+id/menu_create_dir"
+                android:title="create diretory"/>
+        </menu>
+    </item>
+
+</menu>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_peer_operations.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <item
+        android:id="@+id/menu_file_operations"
+        android:title="file operations"
+        >
+        <menu>
+            <item
+                android:id="@+id/menu_copy_to_peer"
+                android:title="copy to peer"/>
+            <item
+                android:id="@+id/menu_copy_from_peer"
+                android:title="pull from peer"/>
+            <item
+                android:id="@+id/menu_move"
+                android:title="move selected"/>
+            <item
+                android:id="@+id/menu_delete"
+                android:title="delete selected"/>
+            <item
+                android:id="@+id/menu_rename"
+                android:title="rename"/>
+            <item
+                android:id="@+id/menu_share"
+                android:title="share"/>
+            <item
+                android:id="@+id/menu_expose"
+                android:title="expose"/>
+            <item
+                android:id="@+id/menu_create_dir"
+                android:title="create diretory"/>
+        </menu>
+    </item>
+
+</menu>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_prioritized.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <!--<item
+		android:id="@+id/menu_select_all"
+		android:title="select all"/>-->
+    <item
+        android:id="@+id/menu_refresh_dir_list"
+        android:title="refresh dir list"/>
+    <group
+        android:id="@+id/menu_prio_home">
+        <item
+            android:id="@+id/menu_go_home"
+            android:title="go to home dir"/>
+    </group>
+</menu>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_selection.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu
+	xmlns:android="http://schemas.android.com/apk/res/android">
+	<item android:title="selection">
+		<menu>
+			<item
+				android:id="@+id/menu_select_all"
+				android:title="select all"/>
+			<item
+				android:id="@+id/menu_clear_selection"
+				android:title="clear selection"/>
+		</menu>
+	</item>
+</menu>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_server_operations.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <item
+        android:id="@+id/menu_file_operations"
+        android:title="file operations"
+        >
+        <menu>
+            <item
+                android:id="@+id/menu_server_copy"
+                android:title="download selected"/>
+            <item
+                android:id="@+id/menu_move"
+                android:title="move selected"/>
+            <item
+                android:id="@+id/menu_delete"
+                android:title="delete selected"/>
+            <item
+                android:id="@+id/menu_rename"
+                android:title="rename"/>
+            <item
+                android:id="@+id/menu_create_dir"
+                android:title="create diretory"/>
+        </menu>
+    </item>
+
+</menu>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/hack.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <item
+        android:title="hack"
+		android:menuCategory="container"
+        android:orderInCategory="9"
+        >
+        <menu>
+            <item
+                android:id="@+id/menu_activity_hack"
+                android:title="activity hack"/>
+            <item
+                android:id="@+id/menu_fragment_hack"
+                android:title="fragment hack"/>
+            <item
+                android:id="@+id/menu_fm_hack"
+                android:title="file manager hack"/>
+        </menu>
+    </item>
+
+</menu>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/main.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu
+	xmlns:android="http://schemas.android.com/apk/res/android">
+	<group android:id="@+id/menu_group_main">
+		<item
+			android:id="@+id/submenu_settings"
+			android:title="settings"
+			android:menuCategory="container"
+			android:orderInCategory="9"
+			>
+			<menu>
+				<item
+					android:id="@+id/menu_settings"
+					android:title="@string/menu_settings"/>
+				<item
+					android:id="@+id/menu_uninstall"
+					android:title="@string/menu_uninstall"/>
+				<item
+					android:id="@+id/menu_del_passwd"
+					android:title="clean SSL passwd"/>
+				<item
+					android:id="@+id/menu_del_sd_card_perms"
+					android:title="clean SD card permissions"/>
+				<item
+					android:id="@+id/menu_clean_caches"
+					android:title="clean cache"/>
+				<item
+					android:id="@+id/menu_reset_prefs"
+					android:title="reset preferences"/>
+				<item
+					android:id="@+id/menu_activity_hack"
+					android:title="activity hack"/>
+			</menu>
+		</item>
+	</group>
+</menu>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/move_dest.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    <!-- <item android:id="@+id/test" android:title="test"/> -->
+	<group android:id="@+id/home_dir">
+		<item android:id="@+id/menu_go_home" android:title="go to home dir"/>
+	</group>
+	<item android:id="@+id/menu_size_of_selection" android:title="targetSize of selection"/>
+	<item android:id="@+id/menu_refresh_dir_list" android:title="refresh dir list"/>
+   	<item android:id="@+id/menu_create_dir" android:title="create diretory"/>
+    <!--
+    tohle menu je určené jako context menu v dialogu a tam je potíž (zjištěno v API 22),
+    že context menu se zobrazí normálně v popředí, kdežto submenu se zobrazí POD dialogem
+    (jinak submenu funguje normálně, ale je přístupné až po ukončení dialogu)
+    na žádný workaround jsem zatím nepříšel
+    <item android:id="@+id/menu_order" android:title="reorder list">
+		<menu>
+		    <item android:id="@OrderNameAsc_asc" android:title="by name asc..."/>
+		    <item android:id="@OrderNameDescdesc" android:title="by name desc..."/>
+		    <item android:id="@OrderSizeAsc_asc" android:title="by targetSize asc..."/>
+		    <item android:id="@OrderSizeDescdesc" android:title="by targetSize desc..."/>
+		    <item android:id="@OrderDateAsc_asc" android:title="by date asc..."/>
+		    <item android:id="@OrderDateDescdesc" android:title="by date desc..."/>
+		</menu>		
+	</item>
+	-->
+	<item android:id="@+id/menu_free_space" android:title="display free space"/>
+</menu>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/order.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item android:id="@+id/menu_order_name_asc" android:title="order by name asc..."></item>
+    <item android:id="@+id/menu_order_name_desc" android:title="order by name desc..."></item>
+    <item android:id="@+id/menu_order_size_asc" android:title="order by targetSize asc..."></item>
+    <item android:id="@+id/menu_order_size_desc" android:title="order by targetSize desc..."></item>
+    <item android:id="@+id/menu_order_date_asc" android:title="order by date asc..."></item>
+    <item android:id="@+id/menu_order_date_desc" android:title="order by date desc..."></item>
+</menu>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/values-w820dp/dimens.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,6 @@
+<resources>
+    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+    <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/values/attrs.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+	<declare-styleable name="FlatButton">
+		<attr name="android:text"/>
+		<attr name="text" format="string"/>
+		<attr name="android:background"/>
+		<attr name="layout_margin" format="dimension"/>
+		<attr name="layout_marginStart" format="dimension"/>
+		<attr name="layout_marginTop" format="dimension"/>
+		<attr name="layout_marginEnd" format="dimension"/>
+		<attr name="layout_marginBottom" format="dimension"/>
+		<attr name="text_padding" format="dimension"/>
+		<attr name="text_paddingStart" format="dimension"/>
+		<attr name="text_paddingTop" format="dimension"/>
+		<attr name="text_paddingEnd" format="dimension"/>
+		<attr name="text_paddingBottom" format="dimension"/>
+	</declare-styleable>
+</resources>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/values/channel.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <item type="integer" format="integer" name="channel">02</item>    
+</resources>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/values/colors.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="dir_name_color">#fd0</color>
+    <color name="dir_name_bg">#048</color>
+    <color name="file_size_color">#ff44ff44</color>
+    <color name="file_name_color">#ffeeeedd</color>
+    <color name="file_date_color">#ffff4444</color>
+    <color name="file_list_bg">#ff002222</color>
+	<color name="dir_name_bg2">#ff4488cc</color>
+    <color name="hh_sun">#ff7B70FF</color>
+    <color name="white">#ffffffdd</color>
+    <color name="peer_from_bg">#ff0066aa</color>
+    <color name="peer_from_bg_inv">#ffffaa66</color>
+    <color name="file_size_color_positive">#ff00aa00</color>
+    <color name="file_name_color_positive">#ff220000</color>
+    <color name="hist_list_bg_positive">#ffffffdd</color>
+    <color name="hist_list_bg">#ff002222</color>
+    <color name="bg1">#ff888888</color>
+    <color name="bg0">#ff444444</color>
+    <color name="blue_font">#08f</color>
+	<color name="title_bg">#323331</color>
+    
+</resources>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/values/dimens.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <dimen name="file_icon_size">24sp</dimen>
+    <dimen name="file_name_size">18sp</dimen>
+    <dimen name="file_attr_size">12sp</dimen>
+    <dimen name="dir_name_size">18sp</dimen>
+    <dimen name="button_small">8sp</dimen>
+    <dimen name="border_thin">2dp</dimen>
+    <dimen name="border">6dp</dimen>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+	<dimen name="activity_horizontal_margin">16dp</dimen>
+	<dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/values/ids.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <item type="id" name="srv_hdr"/>
+    <item type="id" name="srv_lv"/>
+    <item type="id" name="loc_hdr"/>
+    <item type="id" name="loc_lv"/>
+</resources>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/values/strings.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">.dejsem</string>
+    <string name="app_version_name">1.5</string>
+    <string name="app_desc">generalised sharing</string>
+    <string name="bad_pw">BAD</string>
+	<string name="channel">2</string>
+    <string name="menu_settings">settings</string>
+    <string name="menu_uninstall">uninstall</string>
+    <string name="host_key">HOST_KEY</string>
+    <string name="host_default">dejsem.org</string>
+    <string name="host_cache_key">HOST_CACHE</string>
+    <string name="host_title">server host</string>
+    <string name="host_dialog_title">server host ip addr or DNS name</string>
+    <string name="port_default"></string>
+    <string name="port_title">server port</string>
+    <string name="port_dialog_title">specify server ip port</string>
+    <string name="ssl_key">SSL_KEY</string>
+    <string name="ssl_default">true</string>
+    <string name="ssl_title">SSL crypted transfer</string>
+    <string name="txtBg_default">ff001122</string>
+    <string name="txtBg_title">text background color</string>
+    <string name="txtBg_dialog_title">specify text background color ffRRGGBB</string>
+    <string name="log_level_key">LOG_LEVEL_KEY</string>
+    <string name="log_level_default">4</string>
+    <string name="log_level_title">debug log level</string>
+
+    <string name="zkouška">zkušební položka</string>
+	
+    <string-array name="log_level_entries">
+        <item >0 - no messages</item>
+        <item >1</item>
+        <item >2</item>
+        <item >3</item>
+        <item >4</item>
+        <item >5 - full verbosity</item>
+    </string-array>
+    <string-array name="log_level_values">
+        <item >0</item>
+        <item >1</item>
+        <item >2</item>
+        <item >3</item>
+        <item >4</item>
+        <item >5</item>
+    </string-array>
+
+    <string name="chan_key">CHAN_KEY</string>
+    <string name="chan_default">2</string>
+    <string name="chan_title">server comm channel</string>
+    <string name="chan_dialog_title">choose comm channel# 1-99</string>
+    <string name="home_key">HOME_KEY</string>
+    <string name="home_cache_key">HOME_CACHE</string>
+    <string name="home_default">/mnt/sdcard</string>
+    <string name="home_dialog_title">preferred local home directory</string>
+    <string name="home_title">home directory</string>
+    <string name="app_version">0.0</string>
+    <string name="ssl_debug_key">SSL_DEBUG_KEY</string>
+    <string name="ssl_debug_default">false</string>
+    <string name="ssl_debug_title">detailed SSL log</string>
+    <string name="ssl_debug_dialog_title">detailed SSL handshaking log</string>
+
+<!-- TODO: Remove or change this placeholder text -->
+    <string name="hello_blank_fragment">Hello blank fragment</string>
+	<string name="title_activity_hack2">Hack2</string>
+
+	<string name="hello_world">Hello world!</string>
+	<string name="action_settings">Settings</string>
+
+</resources>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/values/styles.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+	<style
+		name="ThemeDej"
+		parent="Theme.AppCompat.NoActionBar"
+		>
+
+		<!--<item name="windowNoTitle">true</item>
+		<item name="windowActionBar">false</item>-->
+
+	</style>
+
+	<drawable name="transparent_background">
+		#00000000
+	</drawable>
+
+	<style name="ThemeDej.Transparent">
+		<!-- theme pro progress monitor -->
+
+		<item name="android:windowIsTranslucent">
+			true
+		</item>
+		<item name="android:windowAnimationStyle">
+			@android:style/Animation.Translucent
+		</item>
+		<item name="android:windowBackground">
+			@drawable/transparent_background
+		</item>
+		<item name="android:colorForeground">
+			#fff
+		</item>
+
+	</style>
+	
+</resources>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/app/src/main/res/xml/prefs.xml	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
+
+     <android.support.v7.preference.Preference
+        android:key="@string/host_key"
+        android:title="@string/host_title"
+		android:dialogTitle="@string/host_dialog_title"
+		android:persistent="true"
+        android:defaultValue="@string/host_default"
+		android:summary="%s"
+		/>
+
+    <android.support.v7.preference.Preference
+        android:key="@string/home_key"
+        android:title="@string/home_title"
+        android:dialogTitle="@string/home_dialog_title"
+        android:persistent="true"
+        android:defaultValue="@string/home_default"
+        />
+
+    <android.support.v7.preference.CheckBoxPreference
+    	android:enabled="false" 
+        android:key="@string/ssl_key" 
+        android:defaultValue="@string/ssl_default" 
+        android:title="@string/ssl_title"
+        android:persistent="true"
+		/>
+
+    <android.support.v7.preference.CheckBoxPreference
+		android:persistent="true" 
+		android:title="@string/ssl_debug_title" 
+		android:key="@string/ssl_debug_key" 
+		android:defaultValue="@string/ssl_debug_default"
+		/>
+    
+</PreferenceScreen>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/build.gradle	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,17 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+    repositories {
+        jcenter()
+        google()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.5.0-alpha09'
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+        google()
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/hhlibv10-debug/build.gradle	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,2 @@
+configurations.maybeCreate("default")
+artifacts.add("default", file('hhlibv10-debug.aar'))
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/local.properties	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,8 @@
+## This file must *NOT* be checked into Version Control Systems,
+# as it contains information specific to your local configuration.
+#
+# Location of the SDK. This is only used by Gradle.
+# For customization when using a Version Control System, please read the
+# header note.
+#Thu Sep 06 12:44:08 CEST 2018
+sdk.dir=/home/L/_INFO/TECHNO/Android/sdk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/android/dejsem.studio/settings.gradle	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,1 @@
+include ':app', ':hhlibv10-debug'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/python/dejsem.pycharm/client.py	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,398 @@
+# coding=utf8
+
+import socket, os, sys, time, signal, subprocess
+from d import D
+from parms import Parms
+from node import Node
+from counter import Counter
+
+# síťové operace klienta
+#	- connect to remote server na base_port+CCx
+#	- connect to remote peer na host:port, které dostane ze serveru na base_port+CC0 operací GETPEER
+#	- listen for peer on base_port+CCx - konkrétní host:port zveřejňuje peer na serveru na base_port+CC0 operací SETPEER
+#		- formalizace čísla portu je ovšem nutná jen když oba peers běží na tomtéž stroji
+
+class Client():
+
+	def __init__(self, d):
+
+		self.d = D("client".format(d.debid))
+
+		Parms.clientMode = True
+
+		self._orig = Parms.orig		# origin data paths
+		self._dest = Parms.dest		# destination data path
+
+		self._chan = Parms.sslchannel
+
+		if self.d.ll(1): self.d.log("pgm={}, homedir={}, action={}, channel={}, debug={}"
+									.format(sys.argv[0], Parms.client_homedir, Parms.action, self._chan, Parms.debugLevel))
+		if self.d.ll(3): self.d.log("CE path: {}, CA path: {}".format(Parms.sslCert, Parms.sslCAPath))
+
+		act = Parms.action
+		if	 not act:
+			return
+		elif act == "PUSHCLIP":     # from cliboard to remote server
+			self.pushclip("clipboard")
+		elif act == "PUSHPRIM":     # from X primary to remote server
+			self.pushclip("primary")
+		elif act == "PUSHFILE":     # from local files to remote server
+			self.pushfile()
+		elif act == "PUSHPEER":     # from local files to remote peer
+			self.pushpeer()
+		elif act == 'PULLCLIP':     # from remote server to cliboard
+			self.pullclip()
+		elif act == 'PULLHIST':     # synchronize local clipboard history from server
+			self.pullhist()
+		elif act == 'PULLFILE':     # from remote server files to local
+			self.pullfile()
+		elif act == 'PULLLIST':     # filelist of server dir
+			self.pulllist()
+		elif act == "PULLPEER":     # from remote peer to local
+			self.pullpeer()
+		elif act == "SRVHACK":
+			self.srv_hack()
+		elif act == "HACK":
+			self.hack()
+		else:
+			self.d.log("ABEND, unknown command {}".format(act))
+		if hasattr(self, "_node"):
+			self._node.close_sc()
+			self._node.close_ssc()
+
+	def pushclip(self, buffer):
+		p = subprocess.Popen(["xclip", "-o", "-selection", buffer], stdout=subprocess.PIPE)
+		if not p.stdout.read().lstrip():    	# nejdřív ověřit, že na clipboardu něco visí
+			self.d.abend("{} buf is empty".format(buffer), None)
+		self._node = Node(self.d, conn=True)    # establish server cmd session
+		self._node.putcmd("PUSHCLIP")
+		if self.d.ll(3): self.d.log("push clipboard starting...")
+		p = subprocess.Popen(["xclip", "-o", "-selection", buffer], stdout=subprocess.PIPE)
+		self._node.put(p.stdout.read())
+		p.wait()
+		if self.d.ll(2): self.d.log("push clipboard end")
+
+	def longtask(self):
+		# příkaz serveru k otevření paralelního portu pro dlouhý přenos
+		# dostanu číslo portu, uvolním příkazový port a otevřu conn na nový port
+		self._node.putcmd("LONGTASK")
+		port = self._node.getnum()
+		if port == 0:
+			self.d.abend("all server net ports are busy", None)
+		self._node.close()     # free server base port 0
+		self._node = Node(self.d, port=port)
+
+	def pushfile(self):
+		if not self._orig:
+			self.d.abendMsg("nothing specified")
+		else:
+			if not self._dest and len(self._orig) > 1:  # je-li na vstupu více jmen, tak poslední je destdir
+				self._dest = os.path.normpath(self._orig[-1])
+				if self._dest.startswith('/'): self._dest = self._dest[1:]  # výstup jenom relativně
+				del self._orig[-1]
+			if not any(os.path.exists(p) for p in self._orig):
+				self.d.abendMsg("{} not found".format(self._orig))
+			else:
+				self._node = Node(self.d, conn=True)
+				self.longtask()     # přechod na datový port
+				self._counter = Counter(self.d, self.batchSize())    # start transfer progress display
+				self._node.putcmd("PUSHFILE")
+				for fp in self._orig:
+					if self.d.ll(3): self.d.log("pushfile '{}' --> '{}' starting...".format(fp, self._dest))
+					if os.path.exists(fp):
+						normfp = os.path.normpath(fp)
+						self.pushfile_recurse(normfp, os.path.dirname(normfp))
+					else:
+						self.d.warn("pushfile: {} not found".format(fp))
+				self._counter.stop()
+				self.d.log("pushfile end", sev=2)
+
+	def pushfile_recurse(self, fp, prefix):
+		if self.d.ll(4): self.d.log("fp={}, relative fp={}".format(fp, os.path.relpath(fp, prefix)))
+		dest = os.path.join(self._dest, os.path.relpath(fp, prefix))
+		if os.path.isdir(fp):
+			for cwd, void, entries in os.walk(fp, topdown=True):
+				for entry in entries:
+					if self.d.ll(4): self.d.log("cwd={}, entry={}".format(cwd, entry))
+					self.pushfile_recurse(os.path.join(cwd, entry), prefix)
+				self._node.putfileinfo(cwd, os.path.join(self._dest, os.path.relpath(cwd, prefix)))
+			self._node.putfileinfo(fp, dest)
+		else:
+			self._node.putfileinfo(fp, dest)
+			with open(fp, mode='rb') as f:
+				g = self._node.genput()
+				g.send(None)
+				data = f.read(Parms.bufSize)
+				while data:
+					self._counter.update(len(data))
+					if self.d.ll(5): self.d.log("push file: len read from file={}".format(len(data)))
+					try:
+						g.send(data)
+						data = f.read(Parms.bufSize)
+					except Exception:
+						break
+				g.close()
+
+	def pullclip(self):
+		self._node = Node(self.d, conn=True)	# establish server cmd session
+		self._node.putcmd("PULLCLIP")
+		self._node.putstr("")   # empty str means LAST entry in clipboard storage on server
+		if self.d.ll(3): self.d.log("pull clipboard starting...")
+		p = subprocess.Popen(["xclip", "-i", "-selection", "clipboard"], stdin=subprocess.PIPE)
+		for data in self._node.genget(size = self._node.getnum()):
+			p.stdin.write(data)
+		p.stdin.close()
+		p.wait()
+		self._node.close_sc()
+		if self.d.ll(2): self.d.log("pull clipboard end")
+
+	def pullhist(self):
+		"""
+		synchronizace historie clipboardu se serverem
+		- entries z clipboardu se drží na serveru v jednotlivých souborech, které se synchronizují do lokálního dir
+		- po synchrnizaci se vytvoří indexový soubor seřazený podle timestampů
+		"""
+		histdir = Parms.client_histdir
+		self._node = Node(self.d, conn=True)      # establish server cmd session
+		self._node.putcmd("PULLHIST")
+		self.d.log("synchronizing clipboard history...")
+		os.makedirs(histdir, mode=0o755, exist_ok=True)
+		os.chdir(histdir)
+		entries = dict()
+		toget = dict()
+		fn = self._node.getfn()
+		while len(fn) > 0:	# inventarizace serveru
+			if self.d.ll(5): self.d.log("fn=" + fn)
+			# entries obsahují prvních 80 bytů z clipboard entry, délku clipboard entry a timestamp
+			# entries mohou být binární i textové, takže se nedekódují
+			entries[fn] = (self._node.getstr(decode=False), self._node.getnum(), self._node.getnum())
+			if not os.path.exists(fn):
+				toget[fn] = entries[fn]
+				if self.d.ll(4): self.d.log("fn {} doesn't exists, toget[fn]={}, toget size={}".format(fn, toget[fn], len(toget)))
+			fn = self._node.getfn()
+		self.d.log("toget={}, toget size={}".format(toget.keys(), len(toget)), sev=4)
+		if len(toget):		# synchronizace
+			for fn in toget.keys():
+				self._node.putcmd("PULLCLIP")
+				self._node.putstr(fn)                           # send requested entry name
+				with open(fn, mode='wb') as f:
+					for data in self._node.genget(size = self._node.getnum()):
+						f.write(data)
+				timestamp = toget[fn][2]
+				os.utime(fn, (timestamp, timestamp))
+			self._node.close_sc()
+		p = subprocess.Popen(["sort", "-k2", "-r"], stdin=subprocess.PIPE, stdout=open('.index', mode='w'), universal_newlines=True)
+		for fn in os.listdir():	# indexing
+			if fn == ".index": continue
+			digest = open(fn, mode="rb").read(80).replace(b'\n', b' ')
+			try: digest = digest.decode()	# to, co nepůjde dekódovat, necháme být
+			except UnicodeDecodeError: pass
+			size = os.path.getsize(fn)
+			timestamp = int(os.path.getmtime(fn))
+			p.stdin.write("{} {} {: 6d} {}\n".format(fn, time.strftime("%Y/%m/%d.%H:%M:%S", time.gmtime(timestamp)), size, digest))
+		p.stdin.close()
+		p.wait()
+		if p.returncode == 0:
+			p = subprocess.Popen(["mc", histdir])
+			p.wait()
+		if self.d.ll(5): self.d.log("clipboard history sync finished")
+
+
+	def pullfile(self):
+		ldp = len(Parms.datapaths)
+		if ldp:
+			if ldp > 1:	# alespoň 2 argumenty: poslední arg je destination dir, ostatní args jsou požadavky
+				self._orig = Parms.datapaths[:ldp-1]
+				self._dest = Parms.datapaths[-1]
+			else:		# jedinný arg je požadavek, destination dir podle env DEST
+				self._orig = Parms.datapaths[:1]
+				self._dest = Parms.dest
+		else:			# bez argumentů: požadavek i destinace podle env
+			self._orig = Parms.orig
+			self._dest = Parms.dest
+		if not self._orig:
+			self.d.abend("no filename specified, ABEND")
+		else:
+			self._node = Node(self.d, conn=True)
+			size, fnum, dnum = (0, 0, 0)
+			for orig in self._orig:		# zjistíme celkovou velikost dávky pro průběžné sledování
+				orig = os.path.normpath(orig)
+				self._node.putcmd("RECKON")
+				self._node.putstr(orig)
+				size += self._node.getnum()
+				fnum += self._node.getnum()
+				dnum += self._node.getnum()
+			self.longtask()
+			self.d.log("pulling {} bytes in {} files and {} dirs...".format(size, fnum, dnum), sev=3)
+			self._counter = Counter(self.d, size)
+			for orig in self._orig:		# vlastní download dávky
+				orig = os.path.normpath(orig)
+				self._node.putcmd("PULLFILE")
+				if self.d.ll(4): self.d.log("pull of '{}' starting...".format(orig))
+				self._node.putstr(orig)
+				fn = self._node.getfn()
+				while len(fn) > 0:
+					fp = os.path.join(self._dest, fn)
+					size = self._node.getnum()
+					timestamp = self._node.getnum()
+					if self.d.ll(4): self.d.log("pull to '{}, dir={}'...".format(fp, size == -1))
+					if size < 0:
+						self._node.receive_dir(fp, size, timestamp)
+					else:
+						self._node.receive_file(fp, size, timestamp, counter=self._counter)
+					fn = self._node.getfn()
+			self._counter.stop()
+			if self.d.ll(2): self.d.log("pull file end")
+
+	def pulllist(self):
+		"""
+		po odeslání příkazu se načítají údaje o souborech ve tvaru
+			<délka_fn><fn><file_size><file_timestamp>
+		"""
+		self._node = Node(self.d, conn=True)
+		self._node.putcmd("PULLLIST")
+		self._node.putstr(os.path.normpath(self._orig[0] if self._orig else "."))
+		fn = self._node.getfn()
+		while fn:
+			size = self._node.getnum()
+			timestamp = time.asctime(time.localtime(self._node.getnum()))
+			print("{: 12d} {:24} {}".format(size, timestamp, fn))
+			fn = self._node.getfn()
+			if self.d.ll(5): self.d.log("fn='{}'".format(str(fn)))
+
+	def pushpeer(self):
+		if not self._orig:
+			self.d.abendMsg("nothing specified")
+		elif not any(os.path.exists(p) for p in self._orig):
+			self.d.abendMsg("{} not found".format(self._orig))
+		else:
+			self._node = Node(self.d, conn=True, peering=True)
+			size = self.batchSize()
+			self.sendBatchSize(size)	# poskytneme partnerovi údaje o velikosti odesílané dávky
+			self._counter = Counter(self.d, size)
+			try:
+				for fp in self._orig:
+					if self.d.ll(3): self.d.log("pushpeer: from={}".format(fp))
+					if os.path.exists(fp):
+						normfp = os.path.normpath(fp)
+						self.pushpeer_recurse(normfp, os.path.dirname(normfp))
+					else:
+						self.d.warn("'{}' not found".format(fp))
+			finally:
+				self._node.sendEOD()  # end of batch
+				self._counter.stop()
+
+	def pushpeer_recurse(self, fp, prefix):
+		if self.d.ll(4): self.d.log("pushpeer recurse: fp={}, relative fp={}".format(fp, os.path.relpath(fp, prefix)))
+		if os.path.isdir(fp):
+			for cwd, void, entries in os.walk(fp, topdown=True):
+				for entry in entries:
+					self.pushpeer_recurse(os.path.join(cwd, entry), prefix)
+				self._node.putfileinfo(cwd, os.path.relpath(cwd, prefix))
+			self._node.putfileinfo(fp, os.path.relpath(fp, prefix))
+		else:
+			self._node.putfileinfo(fp, os.path.relpath(fp, prefix))
+			with open(fp, mode='rb') as f:
+				g = self._node.genput()
+				g.send(None)
+				data = f.read(Parms.bufSize)
+				while data:
+					self._counter.update(len(data))
+					try:
+						g.send(data)
+						data = f.read(Parms.bufSize)
+					except Exception:
+						break
+				g.close()
+		if self.d.ll(4): self.d.log("PUSHPEER: entry {} sent".format(fp))
+
+	def sendBatchSize(self, size):
+		"""
+		odeslání informace o velikosti připravené dávky dat
+		informace se odešle formou informace o souboru (filename, filesize, timestamp)
+		"""
+		if self.d.ll(4): self.d.log("sending batch size to peer...")
+		self._node.putstr("dummy fn for batch size")
+		self._node.putnum(size)
+		self._node.putnum(0)
+
+	def pullpeer(self):
+		if not Parms.bindhost:
+			self.d.abend("local host addr for peering not specified", None)
+		try:
+			self._node = Node(self.d, host=Parms.bindhost, tryPort=True, conn=False, peering=True)
+		except Node.AllPortsBusy as e:
+			self.d.abend("all predefined net ports are busy", None)
+		try:
+			self.d.log("accepting...", sev=4)
+			accepted = self._node.acc(acc_TO=Parms.peer_accept_timeout)
+			self.d.log("peer {}accepted".format("not " if not accepted else ""), sev=4)
+			if not accepted: return
+		except Exception as e:
+			self._node.close_ssc()
+			self.d.abend("peer pull accept", None)
+			return
+		finally:
+			self._node.UDPsignalHUP()		# stop UDP broadcast
+		self.d.log("get batch size from peer (dummy fn)", sev=4)
+		self._node.getfn()	# dummy fn in batch size info
+		batchsize = self._node.getnum()
+		self.d.log("batchsize={}".format(batchsize), sev=4)
+		self._node.getnum()	# dummy timestamp
+		counter = Counter(self.d, batchsize)
+		fn = self._node.getfn()
+		while fn:	# receive batch of file/dir objects
+			self.d.log("fn={}".format(fn), sev=4)
+			fp = os.path.join(self._dest, fn)
+			size = self._node.getnum()
+			timestamp = self._node.getnum()
+			if self.d.ll(4): self.d.log("pulling from peer to '{}'...".format(fp))
+			if size < 0:
+				if not self._node.receive_dir(fp, size, timestamp):
+					self._node.close_sc()
+			else:
+				self._node.receive_file(fp, size, timestamp, counter=counter)
+			fn = self._node.getfn()
+		counter.stop()
+		self._node.close_sc()
+		self._node.close_ssc()
+
+	def close_sc(self):
+		self._node.close_sc()
+
+	def dirsize(self, fp):
+		p = subprocess.Popen(("du", "-sb", fp), stdout=subprocess.PIPE)
+		p.wait()
+		return int(p.stdout.readlines()[0].decode().split("\t")[0]) if p.returncode == 0 else -1
+
+	def batchSize(self):
+		size = 0
+		for fp in self._orig:
+			if os.path.exists(fp):
+				size += self.dirsize(fp) if os.path.isdir(fp) else os.path.getsize(fp)
+		return size
+
+	def srv_hack(self):
+		self._node = Node(self.d, conn=True)
+		for orig in self._orig:
+			self._node.putcmd("RECKON")
+			self._node.putstr(orig)
+			wholesize = self._node.getnum()
+			fnum = self._node.getnum()
+			dnum = self._node.getnum()
+			self.d.log("reckon: {}, {}, {}".format(wholesize, fnum, dnum))
+
+	def hack(self):
+		self._node = Node(self.d, conn=False, tryPort=False, port=1111, host='10.0.1.47')
+		self._node.acc(1)
+
+	def hack_recurse(self, realfp, pref):
+		self.d.log("hack realfp={}, relfp={}".format(realfp, os.path.relpath(realfp, pref)))
+		if os.path.isdir(realfp):
+			for cwd, void, entries in os.walk(realfp, topdown=True):
+				for entry in entries:
+					self.hack_recurse2(os.path.join(cwd, entry), pref)
+				self.d.log("node.putfileinfo({}, {})".format(cwd, os.path.relpath(cwd, pref)))
+			self.d.log("node.putfileinfo({}, {})".format(realfp, os.path.relpath(realfp, pref)))
+		else:
+			self.d.log("file {} processing".format(realfp))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/python/dejsem.pycharm/counter.py	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,34 @@
+# coding=utf8
+
+import sys, time, threading
+from d import D
+
+
+class Counter():
+
+	def __init__(self, d, size):
+		self.d = D("{}, counter".format(d.debid))
+		self.counterN = None
+		if (size > 100 * 1000):
+			self.counterN = [size, 0, time.time()]
+			self.counterT = threading.Thread(target=self.counter, args=(self.counterN,), name="counter")
+			self.counterT.start()
+
+	def update(self, amount):
+		if self.counterN:
+			self.counterN[1] += amount
+
+	def stop(self):
+		if self.counterN:
+			self.counterN[1] = -1
+			self.counterT.join()
+
+	def counter(self, n):
+		self.d.log("counter started", sev=4)
+		while n[1] > -1:
+			elapsed = time.time() - n[2]
+			kbps = 0
+			if elapsed > 0: kbps = int(n[1] / (1024 * elapsed))
+			sys.stdout.write("\r{}%, {:4d} KB/s   ".format(int(100 * n[1] / n[0]), kbps))
+			time.sleep(0.5)
+		print("", file=sys.stdout)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/python/dejsem.pycharm/d.py	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,35 @@
+# coding=utf8
+
+import sys, os, time, errno, random, traceback
+from parms import Parms
+
+class D():
+	def __init__(self, debid):
+		self.debid = debid
+
+	def ll(self, level):
+		return level <= Parms.debugLevel
+
+	def log(self, *msg, sev=0):
+		if self.ll(sev):
+			print("{} {:10.6f} {}:".format(time.strftime("%Y/%m/%d.%H:%M:%S"), time.time() - D.t0, self.debid), *msg, file=sys.stderr)
+			sys.stderr.flush()
+
+	def d(self, msg):
+		self.log("+++ ====>", str(msg))
+
+	def abendMsg(self, msg, e=None):
+		emsg = "{}".format(e) if e else ""
+		self.log("ABEND: {}".format(msg + (": " + emsg if emsg else "")))
+		traceback.print_tb(sys.exc_info()[2])
+
+	def abend(self, msg, e):
+		self.abendMsg(msg, e=e)
+		if Parms.clientMode: sys.exit(1)
+
+	def abendHard(self, msg, e):
+		self.abendMsg(msg, e=e)
+		sys.exit(1)
+
+	def warn(self, *msg):
+		self.log("Warning:", *msg)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/python/dejsem.pycharm/main.py	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,86 @@
+#!/usr/bin/python3
+# coding=utf8
+
+# hal.hh.cz
+#	/usr/local/bin/dejsem.py
+#	/usr/local/dejsem/ssl
+#
+# FVWM shotcuts	Ctrl-Alt-B	Ctrl-Alt-C	Ctrl-Alt-V
+# Key B	   A   CM  Exec ACT=PUSHCLIP CHAN=N dejsem.py	 # copy local  --> shared clipboard
+# Key C	   A   CM  Exec ACT=PUSHCLIP CHAN=N dejsem.py	 # copy local  --> shared clipboard
+# Key V	   A   CM  Exec ACT=PULLCLIP CHAN=N dejsem.py	 # copy shared --> local  clipboard
+#
+# server side
+#	● akceptuje cmd-connection na základním portu kanálu a přijímá z ní příkazy
+#	● po provedení příkazu
+#		PULLCLIP
+#		PULLHIST
+#		PUSHCLIP
+#		GETPEER
+#		SETPEER
+#		EXPOSE
+#		EXPOSEUP
+#		FREE
+#		LONGTASK 
+#	  se connection uzavře
+#	● po provedení příkazu
+#		PULLFILE
+#		PUSHFILE
+#		PULLLIST
+#		MOVE
+#		DELETE
+#		CREATDIR
+#		RECKON
+#	  zůstává connection otevřená a pokračuje čtením dalšího příkazu, protože tyto příkazy mohou být dávkové
+# timeouts
+#	conn_TO				connection retry wait TO - wait before next connect try (try <connThreshold> times)
+#	block_TO			blocking net operations TO
+#	long_run_accept_TO	accept TO on ports binded dynamicaly for long duration operations
+#	peer_accept_TO		accept TO waiting for connection from peer when receiving peer files (PULLPEER)
+
+import sys, os, random, time, signal
+
+def stop(sign, frame):
+	for pid in pids:
+		os.kill(pid, signal.SIGTERM)
+
+pids = set()	# seznam subthreads pro účely mimořádného ukončení
+
+if __name__ == '__main__':
+	from parms import Parms
+	Parms.setup()
+	random.seed(Parms.random_seed) if Parms.random_seed else random.seed()
+
+	from d import D
+	D.t0 = time.time()
+	d = D(Parms.applName)
+	d.log("{}, ver. {:.2f}".format(sys.argv[0], Parms.version), sev=1)
+
+	if Parms.action == 'SRV':
+		from server import Server
+		pid = os.fork()
+		if not pid:		# child
+			from meter import Meter
+			Meter(d).run()
+			sys.exit(0)
+		else:			# parent
+			pids.add(pid)
+			d.log("Meter spawned in process {}".format(pid), sev=1)
+		for chan in range(1, 99):
+			if "{:02d}".format(chan) in os.listdir(Parms.srv_homedir):
+				pid = os.fork()
+				if not pid:		# child
+					Server(d, chan)
+					sys.exit(0)
+				else:			# parent
+					pids.add(pid)
+					d.log("server node SSL {:02d} started in process {}".format(chan, pid), sev=4)
+		d.log("all server nodes spawned", sev=1)
+		signal.signal(signal.SIGINT, stop)
+		signal.signal(signal.SIGTERM, stop)
+		signal.pthread_sigmask(signal.SIG_UNBLOCK, {signal.SIGINT, signal.SIGTERM})
+		signal.pause()
+		d.log("KeyboardInterrupt")
+	else:
+		from client import Client
+		Client(d)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/python/dejsem.pycharm/meter.py	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,55 @@
+# coding=utf8
+
+import socket, time, sys
+from d import D
+from parms import Parms
+
+class Meter():
+
+	def __init__(self, d):
+		self.d = D("{}, troughput measuring daemon".format(d.debid))
+
+	def run(self):
+		self.d.log("started", sev=3)
+		ssc, sc = None, None
+		ssc = self.bindwait('', Parms.baseport)
+		try:
+			while True:
+				self.d.log("accepting...", sev=3)
+				sc = ssc.accept()[0]
+				self.d.log("accepted", sev=3)
+				n1 = 0
+				n0 = len(sc.recv(16 * 1024))
+				while n0 > 0:
+					n1 = n1 + n0
+					if self.d.ll(5): self.d.log("n0={}, n1={}".format(n0, n1))
+					if n1 >= 16 * 1024:
+						if self.d.ll(5): self.d.log("{} received, sending acknoledgement".format(n1))
+						sc.send(bytes("=>{:08d}".format(n1), "utf8"))
+						n1 = 0
+					n0 = len(sc.recv(16 * 1024))
+				sc.close()
+		except KeyboardInterrupt:
+			pass
+		except Exception as e:
+			self.d.abendMsg("measuring", e=e)
+		self.d.log("closing ssc...", sev=4)
+		if sc: sc.close()
+		if ssc: ssc.close()
+
+	def bindwait(self, host, port):
+		ssc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+		while True:
+			try:
+				ssc.bind((host, port))
+				break
+			except Exception as e:
+				if e.strerror == "Address already in use":
+					self.d.log("Address {}:{} already in use, waiting 10 secs...".format(host, port))
+					time.sleep(10)
+					continue
+				self.d.abend("bind", e)
+				sys.exit(1)
+		ssc.listen(1)
+		self.d.log("bound to {}:{}".format(host, port), sev=2)
+		return ssc
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/python/dejsem.pycharm/node.py	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,445 @@
+# coding=utf8
+
+import sys, os, ssl, time, socket, errno, signal
+from Crypto.Cipher import DES3
+from d import D
+from parms import Parms
+
+
+class Node():
+
+	class AllPortsBusy(Exception):
+		"""všechny TCP porty pro server longtasks nebo pro peering jsou obsazeny"""
+
+	blocking = True   # select mode zatím není implementovaný
+	useSSLContext = False
+	ctx = None
+	UDPbroadcastGO = False
+
+	def __init__(self, d, chan=Parms.sslchannel, host=Parms.srvhost, port=None, conn=True, tryPort=True, peering=False):
+		self._issl = Parms.ssl
+		self._chan = chan
+		self._bindhost = host
+		self._baseport = Parms.baseport + (self._chan * 10) + (0 if self._issl else 1)
+		self._minport = self._baseport + 1
+		self._maxport = self._baseport + 9
+		self._baseid = "netnode {}SSL".format("" if self._issl else "non")
+		self.d = D("{} {}".format(d.debid, self._baseid))
+		self._srv_side = None
+		self._UDPpasswd = "heslo"
+		self._UDPbroadcast_addr = Parms.broadcast
+		self._UDPbroadcast_port = Parms.udpport
+		self._UDP_key = "PEER_IP"
+		self._UDPbroadcastGO = False
+		self.sslContext()
+		if conn:			# TCP connect
+			if peering:
+				host, port = self.get_peerport()
+			self.conn(host=host, port=port)
+		else:				# socket bind
+			if tryPort:		# hledej volný port
+				self.bindtrynext(self._bindhost)
+				if peering: self.send_peerport()
+			else:			# zkus bind a případně čekej na uvolnění
+				self.bindwait(self._bindhost)
+
+
+	def get(self, size):
+		try:
+			if self.d.ll(5): self.d.log("get data from scfile...")
+			data = self._scfile.read(size)
+			if self.d.ll(5): self.d.log("{} bytes read".format(len(data)))
+			return data
+		except Exception as e:
+				self.d.abend("read from socket", e)
+				return -1
+
+
+	def genget(self, size=-1):
+		rest = size
+		while rest != 0:
+			n = rest if 0 < rest < Parms.bufSize else Parms.bufSize
+			data = self.get(n)
+			r = len(data)
+			if r < 1: break
+			rest = rest - r
+			yield data
+
+	def getnum(self):
+		b = self._scfile.read(12).decode()
+		num = int(b) if b else -2   # -2 = EOD, -1 = directory, 0 and higher = data size
+		if self.d.ll(5): self.d.log("getnum, got {:012d} (-2 means EOD)".format(num))
+		return num
+
+	def getstr(self, decode = True):
+		lb = self.getnum()
+		if lb < 1:
+			return ""
+		else:
+			_data = self._scfile.read(int(lb))
+			return _data.decode() if decode else _data
+
+	def getfn(self):
+		return self.getstr()
+
+	def getcmd(self):
+		try:
+			return self._scfile.read(8).decode().rstrip('_')
+		except Exception as e:
+			if isinstance(e, socket.timeout):
+				if self.d.ll(4): self.d.log("getcmd timeout")
+			else:
+				self.d.log("I/O err: {}".format(e))
+			return ""
+
+	def receive_dir(self, fp, size, timestamp):
+		os.makedirs(fp, exist_ok=True)
+		os.utime(fp, (timestamp, timestamp))
+		return True
+
+	def receive_file(self, fp, size, timestamp, counter=None):
+		if os.path.dirname(fp): os.makedirs(os.path.dirname(fp), exist_ok=True)
+		tempfp = fp + ".dejsem.partX"
+		with open(tempfp, mode='w+b') as f:
+			for data in self.genget(size = size):
+				if counter: counter.update(len(data))
+				f.write(data)
+		if os.path.getsize(tempfp) == size:
+			os.rename(tempfp, fp)
+			os.utime(fp, (timestamp, timestamp))
+		return True
+
+	def receive_stream(self, fp, size, counter=None):
+		if os.path.dirname(fp): os.makedirs(os.path.dirname(fp), exist_ok=True)
+		tempfp = fp + ".{}.partX".format(Parms.applName)
+		with open(tempfp, mode='w+b') as f:
+			for data in self.genget(size = size):
+				if counter: counter.update(len(data))
+				f.write(data)
+		if os.path.getsize(tempfp) == size:
+			os.rename(tempfp, fp)
+		return True
+
+	def put(self, data):
+		if self.d.ll(5): self.d.log("PUT: data len={}, sending...".format(len(data)))
+		try:
+			l = self._scfile.write(data)
+			if self.d.ll(5): self.d.log("PUT: data len={}, sent".format(l))
+			self._scfile.flush()
+		except Exception as e:
+				self.d.abend("send err", e)
+				return False
+		return True
+
+	def genput(self):
+		try:
+			while True:
+				data = yield None
+				self.put(data)
+		except Exception as e:
+			self.d.abend("write to socket", e)
+			raise e
+		finally:
+			self._scfile.flush()
+
+	def sendEOD(self):
+		self.putnum(0)
+
+	def putnum(self, n):
+		if self.d.ll(5): self.d.log("putnum, num={:012d}".format(n))
+		self._scfile.write(bytes("{:012d}".format(n), "utf8"))
+		self._scfile.flush()
+
+	def putstr(self, fn):
+		b = bytes(str(fn), "utf8")
+		self.putnum(len(b))
+		if self.d.ll(5): self.d.log("putstr, string={}".format(fn))
+		self._scfile.write(b)
+		self._scfile.flush()
+
+	def putcmd(self, act):
+		if self.d.ll(3): self.d.log("action: " + act)
+		# self._node.payload.data = bytes("{}".format(act), "utf8")
+		self.put(bytes("{}".format(act.ljust(8, '_')), "utf8"))
+
+	def sendport(self, port):
+		"""send dynamically allocated port to client"""
+		self.putnum(port)
+
+	def putfileinfo(self, fp, relfp):
+		if self.d.ll(5): self.d.log("putfileinfo fp={}, relfp={}...".format(fp, relfp))
+		self.putstr(relfp)
+		size = os.path.getsize(fp) if os.path.isfile(fp) else -1
+		self.putnum(size)
+		timestamp = int(os.path.getmtime(fp)) if os.path.exists(fp) else 0
+		self.putnum(timestamp)
+		if self.d.ll(4): self.d.log("fileinfo sent: fn={}, size={}, timestamp={}".format(relfp, size, timestamp))
+
+	def digest(self):
+		return self.data if len(self.data) < 24 else self.data[0:8].decode() + "--------" + self.data[-8:].decode()
+
+	def sslContext(self):
+		if self._issl:
+			if Node.useSSLContext:
+				if not Node.ctx:
+					if self.d.ll(4): self.d.log(
+						"setting SSL context: certfile={}, capath={}...".format(Parms.sslCert, Parms.sslCAPath))
+					try:
+						Node.ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)	# PROTOCOL_SSLv23
+						Node.ctx.verify_mode = ssl.CERT_REQUIRED  		# CERT_REQUIRED	| CERT_OPTIONAL	| CERT_NONE
+						Node.ctx.load_cert_chain(Parms.sslCert)
+						Node.ctx.load_verify_locations(None, Parms.sslCAPath)
+					except ssl.SSLError as e:
+						self.d.abendHard("SSL context", e)
+
+	def getssc(self):
+		try:
+			ssc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+			ssc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+		except Exception as e:
+			self.d.abendHard("ssc alloc", e)
+		if self._issl:
+			try:
+				if Node.useSSLContext:
+					ssc = Node.ctx.wrap_socket(ssc, server_side=True)
+				else:
+					ssc = ssl.wrap_socket(
+						ssc,
+						certfile=Parms.sslCert,
+						ca_certs=Parms.sslCAPath,
+						server_side=True,
+						cert_reqs=ssl.CERT_REQUIRED,
+						ssl_version=ssl.PROTOCOL_TLSv1)
+			except ssl.SSLError as e:
+				self.d.abendHard("ssc SSL wrap", e)
+		return ssc
+
+	def bindwait(self, host):
+		port = self._baseport
+		self.d.log("binding to {}:{}".format(host, port))
+		ssc = self.getssc()
+		tries = 0
+		while True:
+			try:
+				ssc.bind((host, port))
+				break
+			except Exception as e:
+				if e.strerror == "Address already in use":
+					if not tries: self.d.log("Address {}:{} already in use, waiting 10 secs...".format(host, port))
+					tries = tries + 1 if tries < 77 else 0
+					try:
+						time.sleep(10)
+					except KeyboardInterrupt:
+						raise
+					continue
+				self.d.abendHard("bind", e)
+			except KeyboardInterrupt:
+				raise
+		ssc.listen(1)
+		if self.d.ll(2): self.d.log("bound to {}:{}".format(host, port))
+		self._ssc = ssc
+		self.port = port
+		return ssc
+
+	def bindtrynext(self, host):
+		for port in range(self._minport, self._maxport + 1):
+			if self.d.ll(4): self.d.log("trying to bind to {}:{}...".format(host, port))
+			try:
+				ssc = self.getssc()
+				ssc.bind((host, port))
+				ssc.listen(1)
+				break
+			except Exception as e:
+				if e.strerror == "Address already in use":
+					if port < self._maxport:
+						continue
+					raise Node.AllPortsBusy
+				self.d.abend("bind", e)
+		if self.d.ll(2): self.d.log("bound to {}:{}".format(host, port))
+		self._ssc = ssc
+		self.port = port
+		return (ssc, port)
+
+	def send_peerport(self):
+		"""UDP broadcast host:port pair for peer"""
+		ipport = "{:012d}{}{:012d}{}{:012d}".format(len(self._UDP_key), self._UDP_key, len(self._bindhost), self._bindhost, self.port)
+
+		c = DES3.new(self.rawKey(self._UDPpasswd, 24), DES3.MODE_ECB)
+		data = ipport.encode()
+		enc = c.encrypt(data + b' ' * (8 - len(data) % 8))
+		if self.d.ll(4): self.d.log("len=%d, enc=[%s]" % (len(enc), enc.hex()))
+		s = socket.socket(type=socket.SOCK_DGRAM)
+		s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
+		a = (self._UDPbroadcast_addr, self._UDPbroadcast_port)
+		if self.d.ll(4): self.d.log("start udp sending to {}:{}: {}".format(self._UDPbroadcast_addr, Parms.udpport, data.decode()))
+
+		pid = os.fork()
+		if pid: self._UDPbroadcastPID = pid
+		else:
+			self.UDPbroadcastGO = True
+			signal.signal(signal.SIGHUP, self.UDPstop)
+			signal.pthread_sigmask(signal.SIG_UNBLOCK, {signal.SIGHUP})
+			retries = 777
+			while retries > 0 and self.UDPbroadcastGO:
+				s.sendto(enc, a)
+				time.sleep(1)
+				retries -= 1
+			sys.exit(0)
+
+	def get_peerport(self):
+		"""get peer host:port pair broadcasted by peer via UDP"""
+		c = DES3.new(self.rawKey(self._UDPpasswd, 24), DES3.MODE_ECB)
+		s = socket.socket(type=socket.SOCK_DGRAM)
+		a = ('', self._UDPbroadcast_port)
+		if self.d.ll(4): self.d.log("binding to udp-port {}:{}".format(a[0], a[1]))
+		s.bind(a)
+		s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
+		while True:
+			(dataBytes, (ip, port)) = s.recvfrom(512)
+			try:	# ignore bad dgrams
+				data = c.decrypt(dataBytes).decode().strip() if Parms.ssl else dataBytes.decode()
+			except: continue
+			if self.d.ll(5): self.d.log("datalen={}, data={}".format(len(data), data))
+			strlen = int(data[:12])
+			key = data[12:12+strlen]
+			if not key == self._UDP_key: continue
+			data = data[12+strlen:]
+			strlen = int(data[:12])
+			host = data[12:12+strlen]
+			port = int(data[12+strlen:])
+			if self.d.ll(4): self.d.log("peer listening on {}:{}".format(host, port))
+			return (host, port)
+
+	def rawKey(self, passwd, keylen):
+		key = b''
+		while len(key) < keylen:
+			key = key + passwd.encode()
+		return key[:keylen]
+
+	def UDPstop(self, sign, frame):
+		self.UDPbroadcastGO = False
+
+	def UDPsignalHUP(self):
+		os.kill(self._UDPbroadcastPID, signal.SIGHUP)		# stop UDP broadcast
+
+	def acc(self, acc_TO=Parms.peer_accept_timeout):
+		if self.d.ll(4): self.d.log("accepting on {} ...".format(self.port))
+		self._ssc.settimeout(acc_TO)
+		try:
+			self._sc, (froma, fromp) = self._ssc.accept()
+		except KeyboardInterrupt:
+			if self.d.ll(4): self.d.log("KeyboardInterrupt")
+			raise
+		except Exception as e:
+			self.d.abend("accept", e)
+			return False
+		# fileno = self._sc.fileno()
+		if self.d.ll(2): self.d.log("conn request on {}SSL port {} from {}:{}"
+								.format("" if self._issl else "non", self.port, froma, fromp))
+		if Node.blocking:
+			self._sc.settimeout(Parms.blockTimeout)
+		else:   # select mode není zatím implementovaný
+			self._srv_side[self._sc] = self._sc
+			if self.d.ll(3): self.d.log("srv side={}".format(*(sc.fileno() for sc in self._srv_side.values())))
+		accepted = False
+		commonName = "nonSSL"
+		certSubject = {}
+		if self._chan > 0 and self._issl:
+			certSubject.update(i for (i,) in self._sc.getpeercert()['subject'])
+			self.d.log("client certificate subject:", certSubject, sev=4)
+			if "commonName" in certSubject: commonName = certSubject["commonName"]
+			if commonName == "{:02d}".format(self._chan): accepted = True
+			# alternativa
+			# for ((key, value),) in sc.getpeercert().get("subject"):
+			# 	if key == "commonName":
+			# 		commonName = value
+			# 		if commonName == "{:02d}".format(self._chan): accepted = True
+		else:
+			accepted = True
+		if self.d.ll(2): self.d.log("client {} {}".format(certSubject["commonName"], "accepted" if accepted else "rejected"))
+		try:
+			self._scfile = self._sc.makefile("rwb")
+		except Exception as e:
+			self.d.abendMsg("socket-makefile", e=e)
+			self.close_sc()
+			return False
+		if accepted:
+			try:
+				if self.d.ll(4): self.d.log("confirming accept")
+				self._scfile.write(b"ACCEPTED")
+				self._scfile.flush()
+				return True
+			except Exception as e:
+				self.d.abendMsg("send confirm", e=e)
+				self.close_sc()
+				return False
+		else:	
+			self._scfile.write(b"REJECTED")
+			self.close_sc()
+		return False
+
+	def conn(self, host=Parms.srvhost, port=None):
+		if not port: port = self._baseport
+		if self.d.ll(4): self.d.log("connecting to {}:{}...".format(host, port))
+		try:
+			self._sc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+		except Exception as e:
+			self.d.abend("socket alloc", e)
+		if self._issl:
+			if self.d.ll(4): self.d.log("sc SSL wrap, homedir={}, certfile={}, ca_certs={}"
+									.format(os.getcwd(), Parms.sslCert, Parms.sslCAPath))
+			try:
+				if Node.useSSLContext:
+					self._sc = Node.ctx.wrap_socket(self._sc)
+				else:
+					self._sc = ssl.wrap_socket(
+						self._sc,
+						certfile=Parms.sslCert,
+						ca_certs=Parms.sslCAPath,
+						cert_reqs=ssl.CERT_REQUIRED,
+						ssl_version=ssl.PROTOCOL_TLSv1)
+			except Exception as e:
+				self.d.abend("sc SSL wrap", e)
+		retry = Parms.connThreshold
+		connected = False
+		while not connected and retry > 0:
+			try:
+				self._sc.connect((host, port))
+				connected = True
+			except Exception as e:
+				if e.errno == errno.ECONNREFUSED:
+					retry = retry - 1
+					time.sleep(Parms.connTimeout)
+				else:
+					self.d.abend("connect to {}".format(host), e)
+		if retry == 0: self.d.abend("connection to {} refused, threshold {} reached".format(host, Parms.connThreshold), None)
+		fileno = self._sc.fileno()
+		if Node.blocking: self._sc.settimeout(Parms.blockTimeout)
+		try:
+			self._scfile = self._sc.makefile("rwb")
+		except Exception as e:
+			self.d.abend("connect makefile", e)
+		try:
+			if self._scfile.read(8) != b"ACCEPTED": self.d.abend("connection not accepted by server", None)
+		except Exception as e:
+			self.d.abend("read socket", e)
+		if self.d.ll(2): self.d.log("connected to {}:{} after {} retries, via fd {}"
+									.format(host, port, Parms.connThreshold - retry, fileno))
+
+	def close_sc(self):
+		if self.d.ll(4): self.d.log("closing socket...")
+		try:
+			if hasattr(self, '_scfile'): self._scfile.close()
+			if hasattr(self, '_sc'): self._sc.close()
+		except Exception as e:
+			self.d.abend("closing socket", e)
+
+	def close_ssc(self):
+		if self.d.ll(4): self.d.log("closing SSL socket...")
+		if hasattr(self, "_ssc"):
+			try: self._ssc.close()
+			except Exception as e: self.d.abend("closing SSL socket", e)
+
+	def close(self):
+		self.close_sc()
+		self.close_ssc()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/python/dejsem.pycharm/parms.py	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,154 @@
+#!/usr/bin/python3
+# coding=utf8
+
+import os, argparse, subprocess, socket
+
+class Parms():
+
+	positiv = ('1', 'Y', 'YES', 'ON')
+	negativ = ('0', 'N', 'NO', 'OFF')
+
+	@classmethod
+	def setup(cls):
+		cls.applName = "dejsem"
+		cls.version = 1.00
+
+		cls.defaultsFile = os.path.join("/etc/default", cls.applName)
+		cls.parsedflts()
+		cls.parsecmdl()
+
+		cls.clientMode = False		# řídí chování při ABENDu: client po ABENDu sys.exit(), server pokračuje
+
+		cls.debugLevel = cls.cmdl("debugLevel", int(cls.env("DEB", cls.dflt("DEB", 0))))
+		cls.random_seed= int(cls.env('RS', 0))
+
+		cls.action = cls.cmdl("action", cls.env("ACT", "")).upper()
+		cls.clientMode = cls.action != "SRV"
+
+		cls.ssl = True
+
+		cls.sslchannel = int(cls.cmdl("sslchannel", cls.env("CHAN", cls.dflt("CHAN", 2))))
+		cls.sslPath = cls.cmdl("sslPath", cls.env("SSLP", cls.dflt("SSLP", os.path.join("/usr/share", cls.applName, "ssl"))))
+		cls.sslCAPath = os.path.join(cls.sslPath, "dejCA.crt")
+		cls.sslCert = os.path.join(cls.sslPath, "{:02d}.pem".format(cls.sslchannel))
+
+		cls.srvhost = cls.cmdl("srvhost", cls.env("HOST", cls.dflt("HOST", "dejsem.org")))
+		cls.filedir = "files"
+		cls.exposed = ".exposed_to_http"
+		cls.clipfile = "clipboard"
+		cls.histdir = "history"		# cliboard history dir
+		cls.srv_wwwhomedir = os.path.join("/var/www/html", cls.applName)
+		cls.srv_homedir = os.path.join("/usr/local/", cls.applName)
+		cls.client_homedir = cls.env("HOME", os.path.join("/home", "{}".format(os.getlogin)))
+		cls.client_appldir = os.path.join(cls.client_homedir, ".local/share", cls.applName)
+		cls.client_histdir = os.path.join(cls.client_appldir, cls.histdir)
+
+		cls.bindhost = cls.env("BINDHOST", cls.dflt("BINDHOST", cls.getbindhost()))
+		# cls.bindhost = cls.env("BINDHOST", "")
+		# cls.broadcast = cls.env("BROADCAST", cls.getbroadcast(cls.bindhost))
+		cls.broadcast = cls.env("BROADCAST", "255.255.255.255")
+		cls.baseport = int(cls.env("BASEPORT", cls.dflt("BASEPORT", 42000)))
+		cls.udpport = 4242 if cls.applName == "dejsem" else 4224 
+		cls.pullPeerIface = None
+
+		cls.bufSize = 256 * 1024
+		cls.connThreshold = 77
+		cls.connTimeout = 0.01
+		cls.blockTimeout = 20
+		cls.accept_timeout = 60  	# seconds
+		cls.long_run_accept_timeout = 60  	# seconds
+		cls.peer_accept_timeout = 60  		# seconds
+
+		cls.datapaths = cls.args["datapaths"]	# data paths from cmdline
+		cls.orig = cls.cmdl("datapaths", list(cls.env("ORIG", "")))		# origin data path
+		cls.dest = cls.env("DEST", "")		# destination data path from ENV
+
+	@classmethod
+	def parsecmdl(cls):
+		parser = argparse.ArgumentParser(description='sdílení clipboardu a filů přes server, přenos filů peer-to-peer')
+		cmds = parser.add_mutually_exclusive_group()
+		cmds.add_argument('--push', dest='action',
+			action='store_const', const='PUSHCLIP',
+			help='copy from local clipboard to shared clipboard')
+		cmds.add_argument('--pull', dest='action',
+			action='store_const', const='PULLCLIP',
+			help='copy from shared clipboard to local clipboard')
+		cmds.add_argument('--pullhist', dest='action',
+			action='store_const', const='PULLHIST',
+			help='synchronize local clipboard history from shared clipboard')
+		cmds.add_argument('--pushsrv', '--pushfile', '--puf', dest='action',
+			action='store_const', const='PUSHFILE',
+			help='copy local file to server')
+		cmds.add_argument('--pullsrv', '--pullfile', '--plf', dest='action',
+			action='store_const', const='PULLFILE',
+			help='copy server file to local')
+		cmds.add_argument('--pulllist', dest='action',
+			action='store_const', const='PULLLIST',
+			help='list files on server')
+		cmds.add_argument('--pushpeer', '--pup', dest='action',
+			action='store_const', const='PUSHPEER',
+			help='copy from local file to LAN peer')
+		cmds.add_argument('--pullpeer', '--plp', dest='action',
+			action='store_const', const='PULLPEER',
+			help='copy from LAN peer to local dir')
+		options = parser.add_argument_group(title='options')
+		options.add_argument('-d', dest='debugLevel', type=int,
+			metavar='<debug level>', help='debug level 0-5, ENV DEB')
+		options.add_argument('-s', dest='srvhost',
+			metavar='<srvhost>', help='server domain name or ip, ENV HOST')
+		options.add_argument('-c', dest='sslchannel', type=int,
+			metavar='<channel#>', help='ssl channel NN, ENV CHAN')
+		options.add_argument('-x', dest='sslPath',
+			metavar='<sslhome>', help='ssl keys store directory, ENV SSL')
+		options.add_argument('-i', dest='pullPeerIface',
+			metavar='<iface>', help='copy from LAN peer via <iface>')
+		parser.add_argument('datapaths', nargs='*', metavar='datapath')
+		cls.args = vars(parser.parse_args())
+
+	@classmethod
+	def parsedflts(cls):
+		cls.defaults = dict()
+		with open(cls.defaultsFile) as df:
+			for line in df:
+				key, value = line.split('=')
+				cls.defaults[key] = value[:-1]
+
+	@classmethod
+	def dflt(cls, key, wired):
+		return cls.defaults[key] if key in cls.defaults else wired
+
+	@classmethod
+	def env(cls, key, default):
+		return os.environ[key] if key in os.environ else default
+
+	@classmethod
+	def cmdl(cls, arg, default):
+		return cls.args[arg] if (arg in cls.args and cls.args[arg]) else default
+
+	@classmethod
+	def getbindhost(cls):
+		return socket.getfqdn()
+
+		"""
+		p = subprocess.Popen(["host", "-t", "A", socket.gethostname()], stdout=subprocess.PIPE)
+		p.wait()
+		if p.returncode == 0:
+			return p.stdout.readlines()[0].decode().split()[3]
+		else:
+			return ''
+		"""
+
+	@classmethod
+	def getbroadcast(cls, bindhost):
+		return '255.255.255.255'
+
+		"""zatím mimo použití, je to hodně nejasné
+		p = subprocess.Popen(("ip", "-o", "addr", "list"), stdout=subprocess.PIPE)
+		p.wait()
+		if p.returncode == 0:
+			return subprocess.check_output(("grep", bindhost), stdin=p.stdout).decode().split()[5]
+		else:
+			return '255.255.255.255'
+		"""
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/python/dejsem.pycharm/server.py	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,600 @@
+#!/usr/bin/python3
+# coding=utf8
+
+import os, sys, subprocess, random, string, traceback, urllib.parse
+from os.path import join
+from d import D
+from parms import Parms
+from node import Node
+
+
+# operace v nekonečném cyklu serveru:
+#	- pro každý kanál v homedir je jedna instance Server
+#	- každá instance má k dispozici 10 portů pro LISTEN
+#	- číslování portů:
+#		- 17CCP
+#		- CC = číslo kanálu
+#		- P  = číslo portu v kanálu
+#		- port 17xx0 je vyhrazen pro rychlé synchronní operace serveru - commands
+#		- porty 17xx1-9 jsou pro long running tasks
+
+"""
+	server pro určitý uživatelský kanál
+	instance vytváří a spouští main.py
+"""
+class Server():
+	
+	def __init__(self, d, chan):
+		self._baseid = "{}.server[{:02d}]".format(d.debid, chan)
+		self.d = D("{}".format(self._baseid))
+
+		Parms.clientMode = False
+		Parms.sslCert = join(Parms.sslPath, "srv.pem")
+		self.d.log("ssl path: {}".format(Parms.sslPath), sev=3)
+
+		self._chan = chan
+		self._homedir = join(Parms.srv_homedir, "{:02d}".format(self._chan))
+		os.chdir(self._homedir)
+		self.d.log("working dir={}, ls: {}".format(os.getcwd(), os.listdir(), sev=4))
+		os.umask(0o077)
+		
+		self._filedir = join(self._homedir, Parms.filedir)
+
+		try:
+			self._node = Node(self.d, host='', chan=self._chan, conn=False, tryPort=False)
+		except Exception as e:
+			self.d.abend("creating network node", e)
+			sys.exit(1)
+		self.server()
+
+	def server(self):
+		# v této metodě server poslouchá na základním portu v kanálu (port 0);
+		# po připojení klienta alokuje socket a synchronně cyklicky volá metodu service() k provedení příkazu klienta;
+		# cyklus volání service() trvá dokud service() nevrátí False;
+		# smyslem cyklu před novým acceptem je dát možnost klientu provést rychlou dávku příkazů proveditelných okamžitě;
+		if not os.path.exists(Parms.clipfile):
+			os.system("touch " + Parms.clipfile)
+		if not os.path.exists(Parms.filedir):
+			os.mkdir(Parms.filedir)
+		while True:
+			try:
+				if self._node.acc(acc_TO=None):
+					while self.service(): pass
+			except KeyboardInterrupt:
+				self.d.log("accept: KeyboardInterrupt")
+				break
+		if self.d.ll(4): self.d.log("closing ssc...")
+		self._node.close_ssc()
+
+	def service(self):
+		# metoda synchronně ("blocking") dostává ze socketu příkaz klienta, provede ho
+		#   a vrací true, když chce nechat socket otevřený a false, když se může socket zavřít;
+		# socket se nechává otevřený, když má smysl provádět danou operaci v dávce;
+		#   je na klientu, aby dávka byla krátká a aby zavřel neprodleně
+		cmd = ""
+		try:
+			if self.d.ll(2): self.d.log("get command...")
+			random.getrandbits(16)
+			cmd = self._node.getcmd()	 # čtení osmi-znakového příkazu
+			if self.d.ll(5): self.d.log("cmd len={}".format(len(cmd)))
+			if not cmd: return False
+			if self.d.ll(3): self.d.log("cmd={}".format(cmd))
+			# názvy operací pull/push odpovídají pohledu klienta;
+			# operace jsou synchronní, blokují další příkazy v daném kanále a musí být v "lidském" měřítku krátké;
+			# dlouhé operace se zahajují příkazem LONGTASK, po němž se alokuje paralelní nit k provedení dlouhé operace (typicky přenosu dat);
+			if   cmd == "PULLCLIP":	# přenos posledního uloženého clipboardu na klienta
+				return self.cmd_pullclip()
+			elif cmd == "PULLHIST":	# přenos přehledu uložených clipboard entries na klienta
+				return self.cmd_pullhist()
+			elif cmd == "PULLLIST":	# přenos seznamu souborů v adresáři na klienta
+				return self.cmd_pulllist()
+			elif cmd == "PUSHCLIP":	# příjem nového clipboardu k uložení na serveru
+				return self.cmd_pushclip()
+			elif cmd == "GETPEER":	# předání uložené peer-adresy klientu
+				return self.cmd_getpeer()
+			elif cmd == "SETPEER":	# příjem peer-adresy k uložení na serveru
+				return self.cmd_setpeer()
+			elif cmd == "MOVE":		# přesun uloženého souboru/adresáře v rámci serveru
+				return self.cmd_move()
+			elif cmd == "EXPOSE":	# vystavení souboru/adresáře na http-serveru - vrací se URI path
+				return self.cmd_expose()
+			elif cmd == "HIDE":		# zakrytí souboru/adresáře pro http-server
+				return self.cmd_hide()
+			elif cmd == "DELETE":	# rekurzivní výmaz souboru/adresáře na serveru
+				return self.cmd_delete()
+			elif cmd == "CREATDIR":	# založení adresáře na serveru
+				return self.cmd_createdir()
+			elif cmd == "FREE":		# předání informace o volném prostoru na serveru
+				return self.cmd_freespace()
+			elif cmd == "RECKON":	# předání informace o velikost souboru/adresáře
+				return self.cmd_reckon()
+			elif cmd == "LONGTASK":	# zahájení operace v jiném procesu na jiném portu
+				return self.cmd_longtask()
+			elif cmd == "HACK":
+				self.d.log("hack")
+				return False
+			else:
+				if self.d.ll(4): self.d.log("unknown command")
+				self._node.close_sc()
+				return False
+		except KeyboardInterrupt:
+			raise
+		except Exception as e:
+			# if self.d.ll(4): self.d.log("service {} aborted: {}: {}".format(cmd, e.__class__.__name__, str(e)))
+			if self.d.ll(4): self.d.log("service {} aborted: {}".format(cmd, e))
+			traceback.print_tb(sys.exc_info()[2])
+			self._node.close_sc()
+			return False
+
+	def cmd_longtask(self):
+		"""
+		najde volný port 1-9, pošle ho klientovi, forkne se do paralelního procesu v němž
+			čeká na připojení klienta, čte příkaz k operaci a provádí operaci
+		- operace se nedávkují, na konci procesu se socket a serversocket zavírají a port uvolňuje
+		- ssc, port = self._node.bindtrynext(Parms.srvhost)
+		"""
+		try:
+			longtaskNode = Node(self.d, host='', chan=self._chan, tryPort=True, conn=False)
+		except Node.AllPortsBusy:
+			self._node.sendport(0)
+			return False
+		self._node.sendport(longtaskNode.port)
+		self._node.close_sc()
+		if os.fork():
+			return False
+		# subprocess --------------
+		self._node = longtaskNode
+		self.d.debid = "{} long task[{:d}]".format(self._baseid, self._node.port)
+		random.seed()   # je potřeba se odstřihnout od random-sekvence hlavního procesu
+		try:
+			if self._node.acc(acc_TO=Parms.long_run_accept_timeout):
+				while self.longserv(): pass	 # cykluj, dokud je longserv positivní
+		except Exception as e:
+			self.d.abendMsg("long task accept", e=e)
+		self._node.close_sc()
+		self._node.close_ssc()
+		os._exit(0)
+
+	def longserv(self):
+		cmd = self._node.getcmd()
+		if not cmd: return False
+		try:
+			if self.d.ll(4): self.d.log("longtask cmd: " + cmd)
+			# názvy operací pull/push odpovídají pohledu klienta
+			if   cmd == "PUSHFILE":	 # rekurzivní příjem souboru/adresáře k uložení na serveru
+				return self.cmd_pushfile()
+			elif cmd == "PUSHFIEX":	 # rekurzivní příjem souboru/adresáře k vystavení na http serveru
+				return self.cmd_pushFileExpose()
+			elif cmd == "PUSHSTEX":	 # příjem streamu k vystavení na http serveru
+				return self.cmd_pushStreamExpose()
+			elif cmd == "PULLFILE":	 # rekurzivní odeslání obsahu souboru/adresáře
+				return self.cmd_pullfile()
+			elif cmd == "PULLCLIP":	 # využije se pro dávkovou synchronizaci historie clipboardu
+				return self.cmd_pullclip()
+			elif cmd == "PULLHIST":	 # využije se pro dávkovou synchronizaci historie clipboardu
+				return self.cmd_pullhist()
+			elif cmd == "ECHO":
+				return self.ee(cmd)
+			elif cmd == "ECHOECHO":
+				return self.ee(cmd)
+			elif cmd == "HACK":
+				return self.rand(6)
+			else:
+				return False
+		except Exception as e:
+			self.d.abendMsg("long task action {}".format(cmd), e=e)
+			return False
+
+	def rand(self, n):
+		for i in range(n):
+			self.d.log("{:04x}".format(random.getrandbits(16)))
+		return False
+
+	def ee(self, cmd):
+		self.d.log("ECHO.004: cmd={}".format(cmd))
+		while cmd == "ECHO____" or cmd == "ECHOECHO":
+			self.d.log("echoing...")
+			self._node.putstr("ECHO")
+			if cmd == "ECHOECHO": self.d.log("echo, got={}".format(self._node.getfn()))
+			self.d.log("waiting for cmd")
+			cmd = self._node.getcmd()
+			self.d.log("cmd got={}".format(cmd))
+		self.d.log("waiting for close")
+		self._node.get(1)  # wait for other side close
+
+	def cmd_pullfile(self):
+		# operace download, v níž může klient dávkovat příkazy PULLFILE ke stažení objektů
+		cmd = "PULLFILE"
+		while cmd == "PULLFILE":
+			req = self.formpath(self._node.getfn())
+			if req:
+				fp = os.path.normpath(join(Parms.filedir, req))
+				if os.path.exists(fp):
+					prefix = os.path.dirname(fp)
+					if self.d.ll(3): self.d.log("pullfile request for '{}': starting...".format(fp))
+					self.pullfilerecurse(fp, prefix)
+				else:
+					if self.d.ll(3): self.d.log("pullfile request for '{}': not found".format(fp))
+			self._node.sendEOD()  # end of recursive subtree of files
+			cmd = self._node.getcmd()	# another PULLFILE is expected here
+		self._node.getcmd()  # wait for other side close
+		return False	# konec dávky
+
+	def pullfilerecurse(self, fp, prefix):
+		if os.path.isdir(fp):
+			for cwd, void, files in os.walk(fp, topdown=False):
+				for entry in files:
+					self.pullfilerecurse(join(cwd, entry), prefix)
+				self._node.putfileinfo(cwd, os.path.relpath(cwd, start=prefix))
+		else:
+			self._node.putfileinfo(fp, os.path.relpath(fp, start=prefix))
+			with open(fp, mode='rb') as f:
+				g = self._node.genput()
+				g.send(None)
+				data = f.read(Parms.bufSize)
+				while data:
+					try:
+						g.send(data)
+						data = f.read(Parms.bufSize)
+					except Exception:
+						break
+				g.close()
+		if self.d.ll(3): self.d.log("PULLFILE: entry {} sent".format(fp))
+
+	def cmd_pushfile(self):
+		# předpokládá se, že subadresáře, do nichž se přijímá, jsou vždycky writable, vznikly uploadem
+		if self.d.ll(4): self.d.log("push file start")
+		cmd = "PUSHFILE"
+		while cmd == "PUSHFILE":
+			req = self._node.getfn()	# relativní cesta
+			while len(req) > 0:			# rekurzivní načtení stromu - končí prázdným req
+				req = self.formpath(req)
+				uploadPath = join(Parms.filedir, req)
+				size = self._node.getnum()
+				timestamp = self._node.getnum()
+				if self.d.ll(4): self.d.log("push: req={} to=[{}, dir={}]...".format(req, uploadPath, size == -1))
+				if size < 0:
+					self._node.receive_dir(uploadPath, size, timestamp)
+				else:
+					self._node.receive_file(uploadPath, size, timestamp)
+				req = self._node.getfn()
+			cmd = self._node.getcmd()		# PUSHFILE or BATCHEND is expected here
+			if self.d.ll(4): self.d.log("cmd_pushfile(), iterace v dávce, cmd={}".format(cmd))
+		self._node.putnum(0)  				# client awaits end of transfer confirmation
+		if self.d.ll(4): self.d.log("push file finished")
+		return False	# konec dávky
+
+	def cmd_pushFileExpose(self):
+		"""
+		Upload and Expose to HTTP
+		● klient posílá na pozadí objekt, který se vystaví na http-serveru
+		● objekt se ukládá do zvláštního adresáře self._exposed pod randomizovaným jménem
+		● klientovi se posílá segment "path" z výsledného URL, URL si zkomletuje klient
+		● metoda vrací False, protože se expose nedávkuje
+		:return: False
+		"""
+		if self.d.ll(4): self.d.log("push and expose file start")
+		self.link_exposed()
+		req = self._node.getfn()  		# relativní cesta
+		if len(req) > 0:
+			stem, void, rest = self.formpath(req).partition('/')
+			exposedPath	= self.randomize_path(stem, self._exposed)
+			while len(req) > 0:			# rekurzivní načtení stromu - končí prázdným req
+				uploadPath = join(self._exposed, exposedPath)
+				void, void, rest = self.formpath(req).partition('/')
+				if rest: uploadPath = join(uploadPath, rest)
+				size = self._node.getnum()
+				timestamp = self._node.getnum()
+				if self.d.ll(4): self.d.log("push to '{}, dir={}'...".format(uploadPath, size == -1))
+				if size < 0:
+					self._node.receive_dir(uploadPath, size, timestamp)
+				else:
+					self._node.receive_file(uploadPath, size, timestamp)
+				req = self._node.getfn()  # relativní cesta
+			self.d.log("{} uploaded".format(exposedPath))
+			self.expose_link(exposedPath)
+			if self.d.ll(4): self.d.log("push and expose file end")
+		else:
+			self.node.putnum(0)
+		return False	# konec, expose se nedávkuje
+
+	def cmd_pushStreamExpose(self):
+		"""
+		Upload and Expose to HTTP
+		● klient posílá na pozadí stream, který se vystaví na http-serveru
+		● stream se ukládá do zvláštního adresáře self._exposed pod randomizovaným jménem
+		● klientovi se posílá segment "path" z výsledného URL, URL si zkomletuje klient
+		● metoda vrací False, protože se expose nedávkuje
+		:return: False
+		"""
+		if self.d.ll(4): self.d.log("push and expose stream start")
+		expName, origName = None, Node
+		mimeType = self._node.getstr()
+		size = self._node.getnum()
+		if size > 0:
+			self.link_exposed()
+			(type, suffix) = mimeType.split('/')
+			if not type or type == '*': type = "content"
+			expName = self.randomize_path(type, self._exposed)
+			if suffix and suffix != '*': expName += '.' + suffix
+			expPath = join(self._exposed, expName)
+			if self.d.ll(4): self.d.log("push stream to '{}'...".format(expPath))
+			self._node.receive_stream(expPath, size)
+			# subprocess.run(('touch', expPath))
+			self.expose_link(expName)
+			if self.d.ll(4): self.d.log("push stream to '{}' finished".format(expPath))
+		else:
+			"""self.node.putnum(0)"""
+		return False	# konec dávky
+
+	def cmd_pulllist(self):
+		req = self.formpath(self._node.getfn())
+		if req:
+			fp = os.path.normpath(join(Parms.filedir, req))
+			if self.d.ll(4): self.d.log("dir=" + fp)
+			if os.path.exists(fp) and os.access(fp, os.R_OK | os.X_OK):
+				if os.path.isdir(fp):
+					for entry in os.listdir(path=fp):
+						self._node.putfileinfo(join(fp, entry), entry)
+				else:
+					self._node.putfileinfo(fp, req)
+		self._node.putnum(0)
+		self._node.close_sc()
+		return False	# konec dávky
+
+	def cmd_pullclip(self):
+		fn = self._node.getfn()
+		if fn == "":
+			fp = Parms.clipfile
+		else:
+			fp = Parms.histdir + "/" + fn
+		if self.d.ll(4):
+			self.d.log("clip fp={}".format(fp))
+		if os.path.exists(fp):
+			self._node.putnum(os.path.getsize(fp))
+			with open(fp, mode="rb") as f:
+				g = self._node.genput()
+				g.send(None)
+				data = f.read(Parms.bufSize)
+				while len(data) > 0:
+					try:
+						g.send(data)
+						data = f.read(Parms.bufSize)
+					except Exception:
+						break
+				g.close()
+		return True		# případné dávkování
+
+	def cmd_pushclip(self):
+		with open(Parms.clipfile, mode='wb') as f:
+			for data in self._node.genget(size = -1):
+				f.write(data)
+		self._node.close_sc()
+		subprocess.call(("cp", "-a", Parms.clipfile, join(Parms.histdir, self.hist_fn())))
+		if self.d.ll(4): self.d.log("pushclip: {} bytes stored".format(os.path.getsize(Parms.clipfile)))
+		return False	# konec dávky
+
+	def hist_fn(self):	# random string file name
+		if not os.path.exists(Parms.histdir):
+			os.mkdir(Parms.histdir)
+		a = (string.digits + string.ascii_letters)
+		fn = ""
+		for i in list(range(5)):
+			fn += a[random.randint(0, 61)]
+		while os.path.exists(join(Parms.histdir, fn)):
+			fn = ""
+			for i in list(range(5)):
+				fn += a[random.randint(0, 61)]
+		return fn
+
+	def cmd_pullhist(self):
+		if os.path.exists(Parms.histdir) and os.path.isdir(Parms.histdir):
+			for entry in os.listdir(Parms.histdir):
+				p = join(Parms.histdir, entry)
+				self._node.putstr(entry)
+				sample = open(p, mode="rb").read(80)	# pošli vzorek max.80 z každého entry
+				self._node.putnum(len(sample))
+				self._node.put(sample)
+				self._node.putnum(os.path.getsize(p))
+				self._node.putnum(int(os.path.getmtime(p)))
+		self._node.putnum(0)	# konec streamu
+		return True
+
+	def cmd_expose(self):
+		"""
+		Expose to HTTP
+		● klient posílá jméno objektu na serveru, který se má vystavit na http-serveru
+		● jméno je cesta relativní k self._filedir
+		● objekt se symlinkuje ve zvláštním adresáři self._exposed randomizovaným jménem odvozeným ze jména objektu
+		● klientovi se posílá segment "path" z výsledného URL, URL si zkompletuje klient
+		● metoda vrací False, protože se expose nedávkuje
+		:return: False
+		"""
+		fpath = join(self._filedir, self.formpath(self._node.getfn()))	# node.getfn() je cesta relativně k self._filedir
+		self.link_exposed()
+		expName = self.randomize_path(os.path.basename(fpath), self._exposed)
+		expPath = join(self._exposed, expName)
+		subprocess.run(('ln', '-sfnr', fpath, expPath), check=True)
+		if self.d.ll(3): self.d.log("fn={}".format(expPath))
+		self.expose_link(expName)
+		return False		# expose nemůže být v dávce, protože se klientovi posílá zpátky URI path
+
+	def cmd_hide(self):
+		fpath = self.formpath(self._node.getfn())
+		if self.d.ll(3): self.d.log("fn={}".format(fpath))
+		channel = "{:02d}".format(self._chan)
+		fpath = join(Parms.srv_homedir, channel, Parms.filedir, fpath)
+		subprocess.run(('chmod', '-R', 'o-rwx', fpath))
+		subprocess.run(('find', fpath, '-name', '.htaccess', '-exec', 'rm', '-f', '{}', '+'))
+		return True		# případné dávkování
+
+	def cmd_getpeer(self):	# atavismus z doby před použitím UDP broadcast
+		host = ""
+		port = 0
+		if len(self.peer) == 2:
+			host = "10.0.2.2" if self.peer[0] == "10.0.2.15" else str(self.peer[0])
+			port = self.peer[1]
+			self.peer = []
+		self._node.putstr(host)
+		self._node.putnum(port)
+		self._node.close_sc()
+		return False
+
+	def cmd_setpeer(self):	# atavismus z doby před použitím UDP broadcast
+		self.peer = (str(self._node.getfn()), int(self._node.getnum()))
+		self._node.close_sc()
+		return False
+
+	def cmd_move(self):
+		orig = self.formpath(self._node.getfn())
+		target = self.formpath(self._node.getfn())
+		if orig and target:
+			if self.d.ll(4): self.d.log("performing mv -n {} {}".format(orig, target))
+			subprocess.call(["mv", "-n", join(Parms.filedir, orig), join(Parms.filedir, target)])
+		return True	 	# případné dávkování
+
+	def cmd_delete(self):
+		req = self.formpath(self._node.getfn())
+		if req:
+			if self.d.ll(4): self.d.log("performing rm -rf {}".format(req))
+			subprocess.call(["rm", "-rf", join(Parms.filedir, req)])
+		return True		# případné dávkování
+
+	def cmd_createdir(self):
+		req = self.formpath(self._node.getfn())
+		if req:
+			fp = join(Parms.filedir, req)
+			if self.d.ll(4): self.d.log("performing mkdir -p {}".format(fp))
+			subprocess.call(["mkdir", "-p", fp])
+		return True	 		# případné dávkování
+
+	def cmd_freespace(self):
+		# df v Debianu 7.6 nemá parametr --output   :-(
+		p = subprocess.Popen(["df", "--block-size=1", "."], stdout=subprocess.PIPE)
+		p.wait()
+		if p.returncode == 0:
+			free = int(p.stdout.readlines()[1].decode().split()[3])
+		else:
+			free = -1
+		self._node.putnum(free)
+		self._node.close_sc()
+		return True	 		# případné dávkování
+
+	def cmd_reckon(self):
+		req = self.formpath(self._node.getfn())
+		self.sendobjectsize(join(Parms.filedir, req) if req else None)
+		return True	 		# případné dávkování
+
+	def sendobjectsize(self, fp):
+		if not fp or not os.path.exists(fp):
+			size, fnum, dnum = (0, 0, 0)
+		elif os.path.isfile(fp):
+			size, fnum, dnum = (os.path.getsize(fp), 1, 0)
+		else:
+			size, fnum, dnum = self.dirsize(fp)
+		self._node.putnum(int(size))
+		self._node.putnum(int(fnum))
+		self._node.putnum(int(dnum))
+
+	def link_exposed(self):
+		"""
+		● založení adresáře pro vystavené objekty self._exposed podle Parms.exposed
+		● adresář je symlinkován z http-serveru číslem kanálu
+		● symlink z http-serveru musí vytvořit instalace nebo super-user
+		● na http-serveru se při instalaci aplikace zakládá adresář pro tuto aplikaci se symlinky na exposed dirs jednotlivých kanálů
+			● <http_server>/<appl_name>/<kanál> --> <appl_home>/<kanál>/<files>/<exposed>
+			● <http_server>/<appl_name> musí mít povoleny symlinky a povolen( instalovat .htaccess s Options Indexes
+			● http-server musí mít x-access po cestě <http_server>/<appl_name>/<kanál> --> <exposed>
+			● výšeuvedené atributy se nemůže zařídit aplikace, musí být nastaveny při instalaci nebo administrátorem
+		"""
+		self._exposed = join(self._filedir, Parms.exposed)
+		if not os.path.exists(self._exposed):
+			subprocess.run(('mkdir', '-pm771', self._exposed))
+		# os.mkdir(self._exposed, mode=0o771)
+		channel = "{:02d}".format(self._chan)
+		self._wwwhome = join(Parms.srv_wwwhomedir, channel)
+		if not os.path.exists(self._wwwhome):
+			subprocess.run(('ln', '-sfnr', self._exposed, self._wwwhome), check=True)
+
+	def expose_link(self, path):
+		"""
+		● fpath je cesta relativní k self._exposed
+		● úkolem je zařídit read-access k filům, x-access k dirs, případně .htacess v kořenu stromu
+		"""
+		orig = join(self._exposed, path)
+		self.d.log("orig={}".format(orig), sev=4)
+		try:
+			subprocess.run(('chmod', 'o+r', orig), check=True)
+			if os.path.isdir(orig):
+				self.expose_dir_tree(orig)
+			elif path.find('/') > 0:
+				self.expose_dir_path(path)
+			channel = "{:02d}".format(self._chan)
+			# uriPath = urllib.parse.quote(join(Parms.applName, channel, path))
+			uriPath = join(Parms.applName, channel, path)
+			self._node.putstr(uriPath)  # pošli URL-path klientovi
+			self.d.log("uri path {} sent".format(uriPath), sev=3)
+		except Exception as e:
+			self.d.abend("exposing file to web server", e)
+
+	def expose_dir_path(self, rel_file_path):
+		"""
+		po cestě k vystavenému souboru je potřeba nastavit x-access pro http-server
+		:param rel_file_path: cesta relativní k self._exposed
+		"""
+		base = self._exposed
+		while rel_file_path:
+			subprocess.run(('chmod', 'o=x', base))
+			(subdir, sep, rel_file_path) = rel_file_path.partition('/')
+			base = join(base, subdir)
+
+	def expose_dir_tree(self, rel_dir_path):
+		"""
+		do vystaveného stromu je třeba umístit .htaccess, po cestě nastavit x-access, ve stromu nastavit rx-access
+		:param rel_dir_path: cesta relativní k self._exposed
+		"""
+		htaccess = join(rel_dir_path, ".htaccess")
+		with open(htaccess, mode="w") as f:
+			f.write("Options Indexes")
+		subprocess.run(('chmod', 'o+r', htaccess))
+		subprocess.run(('chmod', 'o+x', rel_dir_path))
+		subprocess.run(('chmod', '-R', 'o+r', rel_dir_path))
+		for (rel_dir_path, dirs, files) in os.walk(rel_dir_path):
+			for dir in dirs:
+				dpath = join(rel_dir_path, dir)
+				subprocess.run(('chmod', 'o+x', dpath))
+
+	def randomize_path(self, fn, dirname):
+		name, void, suff = os.path.basename(fn).rpartition(".")
+		expName = ""
+		while os.path.exists(join(dirname, expName)) or expName == "":
+			uniq = "{:04x}".format(random.getrandbits(16))
+			if name:
+				expName = name + "." + uniq + "." + suff
+			else:
+				expName = suff + "." + uniq
+		return expName
+
+	def dirsize(self, fp):
+		if self.d.ll(4): self.d.log("performing du -sb '{}'".format(fp))
+		try:
+			size = subprocess.check_output(["du", "-sb", fp])[:-1].decode().split("\t")[0]
+			fnum = len(subprocess.check_output(["find", fp, "-type", "f"]).split(b'\n'))-1
+			dnum = len(subprocess.check_output(["find", fp, "-type", "d"]).split(b'\n'))-1
+		except subprocess.CalledProcessError:
+			(size, fnum, dnum) = (-1, 0, 0)
+		if self.d.ll(5): self.d.log("size={}, type={}, fnum={}. type={}, dnum={}, type={}"
+								.format(size, type(size), fnum, type(fnum), dnum, type(dnum)))
+		return (size, fnum, dnum)
+
+	def close_sc(self):
+		self._node.close_sc()
+
+	def formpath(self, path):
+		"""
+		- v žádném případě absolutní cesta
+		- vrací None, když path jde up from current
+		"""
+		if path.startswith('/'): path = path[1:]
+		p = os.path.normpath(path)
+		return None if p == '..' or p.startswith('../') else p
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/ssl/CA.ext	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,1 @@
+basicConstraints = CA:true
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/ssl/ssl.sh	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,120 @@
+#!/bin/bash
+(($#)) || { echo "Syntax: $0 <channel#> | CA"; exit -1; }
+
+ca() {
+    read -p "Really to discard existing credentials? y|[N]" x
+    x=$x.
+    x=${x:0:1}
+    [[ ${x^[a-z]} != Y ]] && exit 1
+    rm -f *.crt
+    rm -f *.bks
+    who=$ca
+    set -e
+    # create CA keys -----------------------------------------------------------
+    echo "creating $who keys..."
+    openssl req -new -nodes -out $who.req -keyout $who.key -subj /CN=$who -newkey rsa:2048 -sha512
+    chmod 600 $who.key
+    # selfsign CA public key ---------------------------------------------------
+    echo "selfsigning $who public key..."
+    openssl x509 -req -in $who.req -signkey $who.key -days 9999 -set_serial $RANDOM -sha512 -extfile CA.ext -out $who.crt
+    # upload CA public key -----------------------------------------------------
+    echo "uploading $who public key..."
+    up $who.crt
+    echo "$ca successfully created and uploaded."
+}
+channel() {
+    [[ -e $ca.crt ]] && [[ -e $ca.key ]] || { echo "$ca keys not available."; exit 1; }
+    who=$chan
+    bouncy_store="-keystore $who.bks -storetype bks-v1 -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath $jar -storepass:env PASS"
+    default_store="-keystore $who.jks -storepass:env PASS"
+    p=heslo
+    set -e
+    rm -f $chan.{pem,bks,jks}
+    # import CA public key into android key store ------------------------------
+    store=$bouncy_store
+    echo "importing $ca public key into android key store..."
+    PASS=$p keytool -import -noprompt -alias $ca -file $ca.crt $store
+    # create android client keys -----------------------------------------------
+    echo "creating $who android client keys..."
+    PASS=$p keytool -genkey -alias $who -keysize 2048 -keyalg RSA -dname "CN=$who" -validity 9999 -keypass:env PASS $store
+    PASS=$p keytool -certreq -alias $who -file $who.req $store
+     # sign android client public key -------------------------------------------
+    echo "signing $who android client public key..."
+    openssl x509 -req -in $who.req -CAkey $ca.key -CA $ca.crt -days 9999 -set_serial $RANDOM -sha512 -out $who.crt
+    cat $ca.crt >> $who.crt
+    # import client public key into android key store --------------------------
+    echo "importing $who client public key into android key store..."
+    PASS=$p keytool -import -alias $who -file $who.crt $store
+    # import server public key into android client key store -------------------
+    echo "importing server public key into android client key store..."
+    PASS=$p keytool -import -noprompt -alias srv -file srv.crt $store
+    rm -f $who.{req,crt}
+    echo -e "*-----\n* Android client keystore $who.bks successfully created.\n*-----"
+    # import CA public key into default java key store -------------------------
+    store=$default_store
+    echo "importing $ca public key into default java key store..."
+    PASS=$p keytool -import -noprompt -alias $ca -file $ca.crt $store
+    # create default java client keys ------------------------------------------
+    echo "creating $who default java client keys..."
+    PASS=$p keytool -genkey -alias $who -keysize 2048 -keyalg RSA -dname "CN=$who" -validity 9999 -keypass:env PASS $store
+    PASS=$p keytool -certreq -alias $who -file $who.req $store
+    # sign default java client public key --------------------------------------
+    echo "signing $who default java client public key..."
+    openssl x509 -req -in $who.req -CAkey $ca.key -CA $ca.crt -days 9999 -set_serial $RANDOM -sha512 -out $who.crt
+    cat $ca.crt >> $who.crt
+    # import client public key into default java key store ---------------------
+    echo "importing $who client public key into default java key store..."
+    PASS=$p keytool -import -alias $who -file $who.crt $store
+    rm -f $who.{req,crt}
+    # import server public key into default java client key store --------------
+    echo "importing server public key into default java client key store..."
+    PASS=$p keytool -import -noprompt -alias srv -file srv.crt $store
+    echo -e "*-----\n* Default java client keystore $who.jks successfully created.\n*-----"
+    # create openssl client keys -----------------------------------------------
+    echo "creating $who openssl client keys..."
+    openssl req -new -nodes -out $who.req -keyout $who.key -subj /CN=$who -newkey rsa:2048 -sha512
+    # sign openssl client public key -------------------------------------------
+    echo "signing $who openssl client public key..."
+    openssl x509 -req -in $who.req -CAkey $ca.key -CA $ca.crt -days 9999 -set_serial $RANDOM -sha512 -out $who.crt    
+    cat $who.key $who.crt > $who.pem
+    chmod 600 $who.pem
+    rm -f $who.{key,req,crt}
+    echo -e "*-----\n* Client keys $who.pem successfully created.\n*-----"
+}
+srv() {
+    set -e
+    who=srv
+    rm -f $who.{pem,crt,key}
+    # create server keys -------------------------------------------------------
+    echo "creating $who server keys..."
+    openssl req -new -nodes -out $who.req -keyout $who.key -subj /CN=$who -newkey rsa:2048 -sha512
+    # sign server public key ---------------------------------------------------
+    echo "signing $who server public key..."
+    openssl x509 -req -in $who.req -CAkey $ca.key -CA $ca.crt -days 9999 -set_serial $RANDOM -sha512 -out $who.crt
+    cat $who.key $who.crt > $who.pem
+    chmod 600 $who.pem
+    rm -f $who.{key,req}
+    # upload server keys -------------------------------------------------------
+    echo "uploading $who server keys..."
+    up $who.pem
+    echo -e "*-----\n* Server keys $who.pem successfully created and uploaded.\n*-----"
+}
+up() {
+#    scp -p $1 hh@hal.hh.cz:/L/dejsem/ssl/
+	echo "--->DUMMY UPLOAD<---"
+}
+
+ca=dejCA
+# bcprov od verze 149 nabízí zvláštní typ KeyStore "BKS_V1" pro zpětnou kompatibilitu
+jar=bcprov-jdk15on-150.jar
+[[ -e $jar ]] || { echo "Bouncy Castle $jar not found in current dir"; exit 1; }
+if [[ $1 == CA ]] 
+then    ca
+        srv
+else    declare -i n=10#${1^^[a-z]}
+        if [[ $n -gt 99 ]] || [[ $n -lt 0 ]]
+        then    { echo "needed 0 =< channel# < 100"; exit 1; }
+        else    chan=$(printf %02d $n)
+                channel
+        fi
+fi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/bin/d.l	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,1 @@
+dejsem
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/bin/d.list	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,1 @@
+dejsem
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/bin/d.plc	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,1 @@
+dejsem
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/bin/d.plf	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,1 @@
+dejsem
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/bin/d.plp	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,1 @@
+dejsem
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/bin/d.puc	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,1 @@
+dejsem
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/bin/d.puf	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,1 @@
+dejsem
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/bin/d.pull	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,1 @@
+dejsem
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/bin/d.pullfile	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,1 @@
+dejsem
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/bin/d.pulllist	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,1 @@
+dejsem
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/bin/d.pullpeer	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,1 @@
+dejsem
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/bin/d.pup	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,1 @@
+dejsem
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/bin/d.push	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,1 @@
+dejsem
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/bin/d.pushfile	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,1 @@
+dejsem
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/bin/d.pushpeer	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,1 @@
+dejsem
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/bin/dejsem	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+case $(basename $0) in
+	d.l 		) p='--pulllist' ;;
+	d.list		) p='--pulllist' ;;
+	d.plc		) p='--pull' ;;
+	d.plf		) p='--pullfile' ;;
+	d.plp		) p='--pullpeer' ;;
+	d.puc		) p='--push' ;;
+	d.puf		) p='--pushfile' ;;
+	d.pull		) p='--pull' ;;
+	d.pullfile	) p='--pullfile' ;;
+	d.pulllist	) p='--pulllist' ;;
+	d.pullpeer	) p='--pullpeer' ;;
+	d.pup		) p='--pushpeer' ;;
+	d.push		) p='--push' ;;
+	d.pushfile	) p='--pushfile' ;;
+	d.pushpeer	) p='--pushpeer' ;;
+esac
+
+[[ $p$@ ]] || p=-h
+/usr/lib/dejsem/main.py $p "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/bin/dejsemd	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+ENV_LOG=$LOG
+eval $(grep ^LOG= /etc/default/dejsem)
+DEF_LOG=${LOG:-/var/log/dejsem.log}
+
+[[ $(tty) == "not a tty" ]] && exec >>${ENV_LOG:-$DEF_LOG} 2>&1
+
+ACT=SRV exec /usr/lib/dejsem/main.py $p
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/debian/client.DEBIAN/config	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,12 @@
+#!/bin/sh
+set -e
+. /usr/share/debconf/confmodule
+
+return
+
+db_set dejsem/channel 02
+db_input high dejsem/channel || true
+db_go || true
+
+db_input high dejsem/user || true
+db_go || true
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/debian/client.DEBIAN/control	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,9 @@
+Package: dejsem-client
+Version: VERSION
+Architecture: all
+Depends: dejsem-common (= VERSION), xclip, python3-crypto
+Section: network
+Priority: optional
+Maintainer: hh@hh.cz
+Description: Clipboard and files exchange mediator, client code
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/debian/client.DEBIAN/postinst	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,21 @@
+#!/bin/sh
+set -e
+. /usr/share/debconf/confmodule
+
+#expr match $DPKG_MAINTSCRIPT_PACKAGE. ^dejsem >/dev/null 2>&1 && {
+#	ln -sfn dejsem /usr/bin/dd.l
+#	ln -sfn dejsem /usr/bin/dd.list
+#	ln -sfn dejsem /usr/bin/dd.plc
+#	ln -sfn dejsem /usr/bin/dd.plf
+#	ln -sfn dejsem /usr/bin/dd.plp
+#	ln -sfn dejsem /usr/bin/dd.puc
+#	ln -sfn dejsem /usr/bin/dd.puf
+#	ln -sfn dejsem /usr/bin/dd.pull
+#	ln -sfn dejsem /usr/bin/dd.pullfile
+#	ln -sfn dejsem /usr/bin/dd.pulllist
+#	ln -sfn dejsem /usr/bin/dd.pullpeer
+#	ln -sfn dejsem /usr/bin/dd.pup
+#	ln -sfn dejsem /usr/bin/dd.push
+#	ln -sfn dejsem /usr/bin/dd.pushfile
+#	ln -sfn dejsem /usr/bin/dd.pushpeer
+#} || true
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/debian/client.DEBIAN/postrm	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,5 @@
+#!/bin/sh
+set -e
+. /usr/share/debconf/confmodule
+
+update-rc.d dejsemd remove || true
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/debian/client.DEBIAN/prerm	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,29 @@
+#!/bin/sh
+set -e
+. /usr/share/debconf/confmodule
+
+rm -rf /usr/lib/dejsem/__pycache__ || true
+
+/etc/init.d/dejsemd stop || true
+
+test -e /etc/default/dejsem && . /etc/default/dejsem || true
+test x$LOG != x && rm -f $LOG || true
+
+#expr match $DPKG_MAINTSCRIPT_PACKAGE. ^dejsem >/dev/null 2>&1 && {
+#	rm -f /usr/bin/dd.l
+#	rm -f /usr/bin/dd.list
+#	rm -f /usr/bin/dd.plc
+#	rm -f /usr/bin/dd.plf
+#	rm -f /usr/bin/dd.plp
+#	rm -f /usr/bin/dd.puc
+#	rm -f /usr/bin/dd.puf
+#	rm -f /usr/bin/dd.pull
+#	rm -f /usr/bin/dd.pullfile
+#	rm -f /usr/bin/dd.pulllist
+#	rm -f /usr/bin/dd.pullpeer
+#	rm -f /usr/bin/dd.pup
+#	rm -f /usr/bin/dd.push
+#	rm -f /usr/bin/dd.pushfile
+#	rm -f /usr/bin/dd.pushpeer
+#} || true
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/debian/client.DEBIAN/templates	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,9 @@
+Template: dejsem/channel
+Type: string
+Description: Specify default communication channel to be used by dejsem on this machine (two digits)
+
+Template: dejsem/user
+Type: string
+Description: Specify user name under which dejsem will be used.
+ .
+ Users need be memebers of dejsem group in order to have access to cryptographic credentials.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/debian/common.DEBIAN/control	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,8 @@
+Package: dejsem-common
+Version: 2019.11.27.00
+Architecture: all
+Depends: python3 (>= 3.0), python3-crypto
+Section: network
+Priority: optional
+Maintainer: hh@hh.cz
+Description: Clipboard and files exchange mediator, common code
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/debian/common.DEBIAN/postinst	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,14 @@
+#!/bin/sh
+set -e
+
+getent group dejsem >/dev/null 2>&1 || groupadd dejsem
+chgrp -R dejsem /usr/share/dejsem/ssl
+chmod g+r /usr/share/dejsem/ssl/*
+
+set -- $(ip r | head -1)
+int=$5
+[ $int ] && {
+	set -- $(ip r | grep dev\ $int | tail -1)
+	ip=$9
+	[ $ip ] && echo BINDHOST=$ip >> /etc/default/dejsem
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/debian/dummy.DEBIAN/control	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,8 @@
+Package: dejsem
+Version: VERSION
+Architecture: all
+Depends: python3 (>= 3.0), python3-crypto, dejsem-common (=VERSION), dejsem-client (=VERSION), dejsem-server (=VERSION)
+Section: network
+Priority: optional
+Maintainer: hh@hh.cz
+Description: Clipboard and files exchange mediator, common code
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/debian/etc/default/dejsem	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,6 @@
+DEB=0
+SSLP=/usr/share/dejsem/ssl
+LOG=/var/log/dejsem.log
+HOST=dejsem.org
+BASEPORT=42000
+CHAN=02
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/debian/etc/init.d/dejsemd	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,85 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides:				dejsemd
+# Required-Start:		$local_fs $network
+# Required-Stop:		$local_fs $network
+# Default-Start:		2 3 4 5
+# Default-Stop:			0 1 6
+# Short-Description:	dejsem daemon
+# Description:			dejsem is development clone of dejsem
+#						dejsem is clipboard and files exchange mediator 
+### END INIT INFO
+# -*- coding: utf-8 -*-
+# Debian init.d script for dejsem
+
+set -e
+
+DAEMON=/usr/bin/dejsemd
+NAME=dejsemd
+DAEMONUSER=dejsem
+PIDFILE=/var/run/dejsem.pid
+LOG=/var/log/dejsem.log
+DESC="development clone of dejsem"
+
+test -x $DAEMON || exit 0
+
+. /lib/lsb/init-functions
+
+# Source defaults file; edit that file to configure this script.
+if [ -e /etc/default/dejsem ]
+then	. /etc/default/dejsem
+fi
+export DEB
+export SSLP
+export LOG
+export BASEPORT
+
+start_it_up()
+{
+  if [ ! -e $LOG ]; then
+    touch $LOG
+    chown $DAEMONUSER.$DAEMONUSER $LOG
+  fi
+
+  if [ -e $PIDFILE ]; then
+    if $0 status > /dev/null ; then
+      log_success_msg "$DESC already started; not starting."
+      return
+    else
+      log_success_msg "Removing stale PID file $PIDFILE."
+      rm -f $PIDFILE
+    fi
+  fi
+
+  log_daemon_msg "Starting $DESC" "$NAME"
+  start-stop-daemon --start --pidfile $PIDFILE --make-pidfile --background --chuid $DAEMONUSER --exec $DAEMON
+  log_end_msg $?
+}
+
+shut_it_down()
+{
+  log_daemon_msg "Stopping $DESC" "$NAME"
+  start-stop-daemon --stop --retry 5 --quiet --oknodo --pidfile $PIDFILE --remove-pidfile --user $DAEMONUSER
+  log_end_msg $?
+  rm -f $PIDFILE
+}
+
+case "$1" in
+  start)
+    start_it_up
+  ;;
+  stop)
+    shut_it_down
+  ;;
+  restart)
+    shut_it_down
+    start_it_up
+  ;;
+  status)
+    status_of_proc -p $PIDFILE $DAEMON $NAME && exit 0 || exit $?
+    ;;
+  *)
+    echo "Usage: /etc/init.d/$NAME {start|stop|restart|status}" >&2
+    exit 2
+  ;;
+esac
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/debian/makefile	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,13 @@
+all:
+	./pack
+
+pack:
+	./pack pack
+	
+upload:
+	./pack upload
+
+clean:
+	./pack clean
+
+.PHONY:	all pack upload clean
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/debian/pack	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,105 @@
+#!/bin/bash
+
+cd $(dirname $0)
+fn_dummy=
+fn_common=
+fn_client=
+fn_server=
+
+clean() {
+	rm -rf debian
+	mkdir -p debian
+}
+
+fnames() {
+	version="$(sed -n "/^Version:/s/^Version:[[:blank:]]//p" common.DEBIAN/control)"
+	arch="$(sed -n "/^Architecture:/s/^Architecture:[[:blank:]]//p" common.DEBIAN/control)"
+	pname="$(sed -n "/^Package:/s/^Package:[[:blank:]]//p" common.DEBIAN/control)"
+	fn_common=${pname}_${version}_${arch}
+	pname="$(sed -n "/^Package:/s/^Package:[[:blank:]]//p" client.DEBIAN/control)"
+	fn_client=${pname}_${version}_${arch}
+	pname="$(sed -n "/^Package:/s/^Package:[[:blank:]]//p" server.DEBIAN/control)"
+	fn_server=${pname}_${version}_${arch}
+	pname="$(sed -n "/^Package:/s/^Package:[[:blank:]]//p" dummy.DEBIAN/control)"
+	fn_dummy=${pname}_${version}_${arch}
+}
+
+changes() {
+	rm -rf $1.changes	
+	changestool --create $1.changes add $1.deb
+	changestool $1.changes setdistribution unstable
+}
+
+pack() {
+	[[ $FAKEROOT ]] || { FAKEROOT=1 fakeroot -- $0 pack; return; }
+
+	version="$(sed -n "/^Version:/s/^Version:[[:blank:]]//p" common.DEBIAN/control)"
+	if [[ ${version%.*} == $(date  +%Y.%m.%d) ]] 
+	then	suff=$(printf "%02d" $(($(echo ${version##*.} | bc) + 1)))
+	else	suff=00
+	fi
+	version=$(date +%Y.%m.%d.$suff)
+	control=$(<common.DEBIAN/control)
+	sed -e "/^Version:/s/^.*$/Version: $version/" <<<$control >common.DEBIAN/control
+
+	# dejsem.common
+	clean
+	cp -a common.DEBIAN debian/DEBIAN
+	mkdir -p debian/etc/default debian/usr/lib/dejsem debian/usr/share/dejsem/ssl
+	cp -a ../../python/dejsem.pycharm/*.py	debian/usr/lib/dejsem/	
+	cp -a ../../ssl/*.pem	debian/usr/share/dejsem/ssl/
+	cp -a ../../ssl/dejCA.crt	debian/usr/share/dejsem/ssl/
+	cp -a etc/default/dejsem	debian/etc/default/
+	chown -R root.root debian
+	dpkg-deb -b debian .
+	
+	# dejsem.client
+	clean
+	cp -a client.DEBIAN debian/DEBIAN
+	sed -e "s/VERSION/$version/g" client.DEBIAN/control >debian/DEBIAN/control
+	mkdir -p debian/usr/bin/
+	cp -a ../bin/dejsem debian/usr/bin/				
+	cp -a ../bin/d.* debian/usr/bin/				
+	chown -R root.root debian
+	dpkg-deb -b debian .
+	
+	# dejsem.server
+	clean
+	cp -a server.DEBIAN debian/DEBIAN
+	sed -e "s/VERSION/$version/g" server.DEBIAN/control >debian/DEBIAN/control
+	mkdir -p debian/etc/init.d debian/usr/bin
+	cp -a etc/init.d debian/etc/
+	cp -a ../bin/dejsemd debian/usr/bin/				
+	chown -R root.root debian
+	dpkg-deb -b debian .
+	
+	# dejsem.dummy
+	clean
+	cp -a dummy.DEBIAN debian/DEBIAN
+	sed -e "s/VERSION/$version/g" dummy.DEBIAN/control >debian/DEBIAN/control
+	chown -R root.root debian
+	dpkg-deb -b debian .
+	
+	fnames
+	changes $fn_common
+	changes $fn_client
+	changes $fn_server
+	changes $fn_dummy
+	
+	clean
+}
+
+upload() {
+	fnames
+	scp -p $fn_common.{deb,changes} $fn_client.{deb,changes} $fn_server.{deb,changes} $fn_dummy.{deb,changes} root@deb.hh.cz:/w/debian/incoming/
+}
+
+case $1 in
+	clean ) 	clean ;;
+	pack )		pack ;;
+	upload )	upload ;;
+	* )
+		pack
+		upload
+		;;
+esac
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/debian/server.DEBIAN/control	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,9 @@
+Package: dejsem-server
+Version: VERSION
+Architecture: all
+Depends: dejsem-common (= VERSION)
+Section: network
+Priority: optional
+Maintainer: hh@hh.cz
+Description: Clipboard and files exchange mediator, server code
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/debian/server.DEBIAN/postinst	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,15 @@
+#!/bin/sh
+set -e
+
+getent passwd dejsem >/dev/null 2>&1 || useradd -g dejsem -d /none -s /bin/false dejsem
+
+mkdir -p /usr/local/dejsem
+chown dejsem /usr/local/dejsem
+
+a=1M
+b=
+for i in $(seq 1 19); do b=$a$a; a=$b; done
+echo $a >/var/www/html/hh.1M
+
+update-rc.d dejsemd defaults
+update-rc.d dejsemd enable
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/debian/server.DEBIAN/postrm	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,5 @@
+#!/bin/sh
+set -e
+
+. /usr/share/debconf/confmodule
+update-rc.d dejsemd remove || true
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/debian/server.DEBIAN/prerm	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,11 @@
+#!/bin/sh
+set -e
+. /usr/share/debconf/confmodule
+
+rm -rf /usr/lib/dejsem/__pycache__ || true
+
+/etc/init.d/dejsemd stop || true
+
+#test -e /etc/default/dejsem && . /etc/default/dejsem || true
+test x$LOG != x && rm -f $LOG || true
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dejsem.1.5/unix/test.sh	Wed Nov 27 09:50:16 2019 +0100
@@ -0,0 +1,330 @@
+#!/bin/bash
+base=$(dirname $0)
+P=dejsem
+DEB=${DEB:-4}
+CHAN=${CHAN:-2}
+HOST=${HOST:-racek.hh.cz}
+export DEB CHAN HOST
+xclip="xclip -selection clipboard"
+eta="tplzKrx6tž88ČJGlŘeMf."
+tmp=/tmp/dejsem
+tmpf=$tmp/file
+tmpd=$tmp/dir
+
+red="\e[1;31m"
+green="\e[1;32m"
+std="\e[0;39m"
+
+help() {
+	n=$(basename $0)
+	echo -e "syntax:"
+	echo -e "\t$n clip { short | long }"
+	echo -e "\t$n hist"
+	echo -e "\t$n srv  { short | long | dir }"
+	echo -e "\t$n peer { short | long | dir }"
+	echo -e "\t$n all"
+	echo 
+	echo ENVIRONMENT:
+	echo DEB=0-5
+	echo HOST="<server>"
+	echo SSL="<path to ssl repository>"
+	exit 1
+}
+
+clean() { rm -rf /tmp/dejsem; }
+
+etalon() {
+#----------------------------------------
+#	create etalon file of specified size
+#----------------------------------------
+	mkdir -p $tmpf/chk
+	rm -f $tmpf/etalon
+	echo -n $eta >$tmpf/etalon
+	cat $tmpf/etalon >$tmpf/f
+	for i in `seq $1`
+	do	cat $tmpf/etalon >> $tmpf/f
+		cat $tmpf/f >> $tmpf/etalon
+	done
+	[[ $# -gt 1 ]] && echo -n "$2 " | cat - $tmp/f >$tmpf/etalon
+	touch -r /etc $tmpf/etalon
+	( cd $tmpf; ls -l etalon >chk/0.stat; )
+}
+
+etadir() {
+#----------------------------------------
+#	create etalon directory
+#----------------------------------------
+	mkdir -p $tmpd/chk
+	rm -rf $tmpd/etalon
+	mkdir -p $tmpd/etalon/d/alev
+	mkdir -p $tmpd/etalon/epol
+	
+	etalon 3
+	cp $tmpf/etalon $tmpd/etalon/f0
+	touch -r /etc $tmpd/etalon/f0
+	cp $tmpf/etalon $tmpd/etalon/d/f1
+	
+	etalon 5
+	cp $tmpf/etalon $tmpd/etalon/epol/f2
+	touch -r /lib $tmpd/etalon/epol/f2
+	cp $tmpf/etalon $tmpd/etalon/d/alev/f3
+	touch -r /usr/lib $tmpd/etalon/d/alev/f3
+	
+	touch -r /home $tmpd/etalon/d/alev
+	touch -r /bin $tmpd/etalon/epol
+	touch -r /usr $tmpd/etalon
+	
+	( 	cd $tmpd		
+		zip -qr chk/0.zip etalon
+		set - $(du -sbx etalon)
+		echo $1 >chk/0.size
+		ls -R etalon >chk/0.list
+		ls -lR etalon >chk/0.stat
+	)
+}
+
+say() {
+	echo -e $*
+	echo
+}
+
+chkdir() {
+#----------------------------------------
+#	compare sent and received etalon directory
+#----------------------------------------
+(	cd $tmpd/chk
+	zip -qr 1.zip etalon || return 1
+	set - $(du -sbx etalon)
+	echo $1 >1.size
+	ls -R etalon >1.list
+	ls -lR etalon >1.stat
+	
+	if diff 0.list 1.list &>/dev/null
+	then	say file/dir names ${green}OK${std}.
+	else	say original and result file list ${red}differ${std}!!!
+			return 1
+	fi
+	if diff 0.size 1.size &>/dev/null
+	then	say total byte count ${green}OK${std}.
+	else	say original and result byte count ${red}differ${std}!!!
+			return 1
+	fi
+	if diff 0.stat 1.stat &>/dev/null
+	then	say time stamps ${green}OK${std}.
+	else	say original and result time stamps ${red}differ${std}!!!
+			return 1
+	fi
+	if diff 0.zip 1.zip &>/dev/null
+	then	return 0
+	else	say data contents ${red}differ${std}!!!
+			return 1
+	fi		
+)}
+
+chkfile() {
+#----------------------------------------
+#	compare sent and received etalon files
+#----------------------------------------
+	if diff $tmpf/etalon $tmpf/chk/etalon
+	then	say content of received file ${green}OK$std.
+	else	say data content ${red}differs${std}!!!
+			return 1
+	fi		
+	( cd $tmpf/chk; ls -l etalon >1.stat; )
+	if diff $tmpf/chk/0.stat $tmpf/chk/1.stat
+	then	say time stamps ${green}OK${std}.
+	else	say original and result time stamps ${red}differ${std}!!!
+			return 1
+	fi
+}
+
+clip() {
+#----------------------------------------
+#	test clipboard exchange
+#----------------------------------------
+	[[ $1 ]] || help
+	case $1 in
+		short )	n=2 ;;
+		long )	n=7 ;;
+	esac	
+	etalon $n
+	clf=$tmpf/etalon
+	$xclip -i <$clf
+	$xclip -o >$tmpf/f1
+	cl=$($xclip -o)
+	echo
+	say ">>> clipboard $1: ${#cl} chars >>> $($xclip -o | head -c 66)"
+	$P --push
+	echo
+	read -p ">>> on Android perform pull clipboard "
+	echo
+	echo "" | $xclip -i
+	ssh root@$HOST rm -f "/L/dejsem/$(printf %02d $CHAN)/clipboard"
+	read -p ">>> on Android perform push clipboard "
+	echo
+	$P --pull
+	$xclip -o >$tmpf/f2
+	echo
+    diff $tmpf/f1 $tmpf/f2 && return 0 || return 1
+}
+
+hist() {
+#----------------------------------------
+#	test clipboard history
+#----------------------------------------
+	ssh root@$HOST rm -rf "/L/dejsem/$(printf %02d $CHAN)/history"
+	etalon 1
+	echo 1111 entry 1, $(<$tmpf/etalon) | $xclip -i
+	$P --push
+	etalon 3
+	echo 2222 long entry 2, $(<$tmpf/etalon) >$tmpf/chk/0.entry
+	$xclip -i $tmpf/chk/0.entry
+	$P --push
+	etalon 1
+	echo 3333 entry 3, $(<$tmpf/etalon) | $xclip -i
+	$P --push
+	rm -rf ~/.local/share/dejsem/history
+	echo
+	read -p ">>> pulling clipboard history to local host - it should contain 3 entries, hit ENTER "
+	echo
+	$P --pullhist
+	echo
+	read -p ">>> on Android go to clipboard  history and choose \"2222 long entry 2\" "
+	echo
+	read -p ">>> on Android push clipboard "
+	$P --pull
+	$xclip -o >$tmpf/chk/1.entry
+	echo
+    diff $tmpf/chk/0.entry $tmpf/chk/1.entry && return 0 || return 1
+}
+
+srv() {
+#----------------------------------------
+#	test file exchange with server
+#----------------------------------------
+	[[ $1 ]] || help
+	if [[ $1 == dir ]]
+	then
+		etadir
+		fp=$tmpd/etalon
+		ssh root@$HOST rm -rf "/L/dejsem/$(printf %02d $CHAN)/files/etalon"
+		say ">>> uploading directory etalon..."
+		$P --pushfile $fp
+		echo
+		read -p ">>> on Android refresh / on server and download directory etalon "
+		echo
+		ssh root@$HOST rm -rf "/L/dejsem/$(printf %02d $CHAN)/files/etalon"
+		echo
+		read -p ">>> on Android upload directory etalon to / on server "
+		echo
+		rm -rf $tmpd/chk/etalon
+		$P --pullfile etalon $tmpd/chk
+		echo
+		chkdir	
+	else
+		case $1 in
+			short )		n=3 ;;
+			long )		n=8 ;;
+		esac	
+		etalon $n
+		fp=$tmpf/etalon
+		ssh root@$HOST rm -rf "/L/dejsem/$(printf %02d $CHAN)/files/etalon"
+		say ">>> uploading file etalon: $(set -- $(ls -lh $fp); echo $5) bytes"
+		$P --pushfile $fp
+		echo
+		read -p ">>> refresh Android server-list and dowload file etalon "
+		ssh root@$HOST rm -rf "/L/dejsem/$(printf %02d $CHAN)/files/etalon"
+		echo
+		read -p ">>> on Android upload file etalon "
+		echo
+		say ">>> downloading file etalon: $(set -- $(ls -lh $fp); echo $5) bytes"
+		rm -rf $tmpf/chk/etalon
+		$P --pullfile etalon $tmpf/chk
+		echo
+		chkfile
+	fi
+}
+
+peer() {
+#----------------------------------------
+#	test file exchange with peer
+#----------------------------------------
+	[[ $1 ]] || help
+	if [[ $1 == dir ]]
+	then
+		etadir
+		fp=$tmpd/etalon
+		echo
+		read -p ">>> on Android start pull from peer "
+		echo
+		$P --pushpeer $fp
+		say ">>> on Android copy directory etalon to peer (${red}1 minute TIMEOUT!${std})"
+		rm -rf $tmpd/chk/etalon
+		$P --pullpeer $tmpd/chk
+		echo
+		chkdir
+	else
+		case $1 in
+			short )		n=3 ;;
+			long )		n=13 ;;
+		esac	
+		etalon $n
+		read -p ">>> on Android start pull from peer "
+		echo
+		$P --pushpeer $tmpd/etalon
+		echo
+		say ">>> on Android copy file etalon to peer (${red}1 minute TIMEOUT${std})"
+		rm -rf $tmpf/chk/etalon
+		$P --pullpeer $tmpf/chk
+		echo
+		chkfile
+	fi
+}
+
+tst() {
+#----------------------------------------
+#	run specified test
+#----------------------------------------
+	x=y 
+	while [[ $x == y ]]; do
+		echo
+		echo "*----------------------------------------"
+		echo "* $*"
+		echo "*----------------------------------------"
+		echo
+		$* &&  say "${green}>>> OK${std}" || say "${red}>>> orig & result don't compare !!!${std}"
+		read -p  ">>> repaeat the test? y/N " x
+	done	
+}
+
+all() {
+#----------------------------------------
+#	run all tests
+#----------------------------------------
+	tst clip short
+	tst srv dir
+	tst clip long
+	tst srv short
+	tst srv long
+	tst peer short
+	tst peer long
+	tst peer dir
+}
+
+trap clean exit
+clean
+mkdir -p $tmp
+
+[[ $1 == all ]] && {
+	all
+	exit
+	}
+	
+(($#)) && {
+	[[ $# -lt 1 ]] && help
+	tst $*
+	exit
+0	}
+
+help
+10
\ No newline at end of file