();
+ // 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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/Main.java
--- /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í:
+ *
+ * - sdílení clipboardu skrze server včetně udržování historie
+ * - sdílení datových souborů se serverem
+ * - peer-to-peer přenos datových souborů v LANu
+ * - vystavení datových souborů na serveru
+ * - informace o síťovém připojení včetně průchodnosti internetové last mile
+ *
+ * ● 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));
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/PassW.java
--- /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
+ *
+ * ● 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);
+ }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/Prefs.java
--- /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);
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/SdUri.java
--- /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 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 persistedUriPermissions() {
+ List 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); }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/SendUriNG.java
--- /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 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);
+ }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/TitleBar.java
--- /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
+ * stavů datových přenosů probíhajících na pozadí;
+ * animovaná ikona indikuje probíhající přenos na pozadí
+ * ... obsah 1.řádky (záhlaví) okna GUI aktivit a jejich fragmentů
+ * ● záhlaví má 4 pole
+ *
+ * app id
+ * proměnné stavové pole probíhajících přenosů
+ * momentálně nastavený server a číslo šifrovaného kanálu
+ * label aktuální komponenty GUI
+ *
+ * ● 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();
+ }
+}
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/clipboard/Clipboard.java
--- /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;
+ }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/clipboard/HistFrag.java
--- /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 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 {
+ D d;
+ HashMap map;
+ K.HistOrder order;
+ int direction = 1;
+
+ HistEntryComparator(D d, HashMap 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;
+ }
+ }
+}
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/clipboard/HistList.java
--- /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 entries;
+ public ArrayList list;
+
+ public static HashMap allocEntries() { return new HashMap<>(); }
+
+ public static ArrayList allocList(Collection col) { return new ArrayList<>(col); }
+
+ public static class HistEntry {
+ public String name;
+ public String text = "";
+ public long size;
+ public long date;
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/BarNotification.java
--- /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();
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/DialogFragmentDelete.java
--- /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);
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/DialogFragmentEntry.java
--- /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);
+ }
+
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/DialogFragmentMoveDest.java
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/EntitySize.java
--- /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
+ *
+ * ● 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 + ")";
+ }*/
+}
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FM.java
--- /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
+ *
+ * ● 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 stack = new LinkedList();
+ HashMap cache = new HashMap();
+
+ 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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FMLocal.java
--- /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();*/ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FMServer.java
--- /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(); }
+}
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FSLocal.java
--- /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
+ *
+ * ● všechno se točí kolem ošetření externí SD-karty, využívá se DocumentFile
+ *
+ * ● 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á
+ *
+ * ● 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 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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FSServer.java
--- /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);
+ }
+}
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FileAttrs.java
--- /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; }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FileList.java
--- /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 a operace na něm
+ *
+ * ● instance drží ještě
+ *
+ * - directory name
+ *
- seznam file names jako String[]
+ *
- aktuální pozici v seznamu
+ *
- ListView adapter
+ *
+ **/
+abstract class FileList extends TreeMap {
+
+ 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 fa = new ArrayList(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 chkSet = new HashSet(size());
+ for(Map.Entry 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 chkSet = new HashSet(size());
+ for(Map.Entry 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 fSet = new HashSet(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); }
+}
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FileListAdapter.java
--- /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
+
+ 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;
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FragFM.java
--- /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);
+ }
+}
+
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FragFMPeer.java
--- /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ů
+ }*/
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/FragFMServer.java
--- /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);
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/NetTaskRef.java
--- /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; }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelDirView.java
--- /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
+ *
+ * ● drží hlavně menu operací a metody, které je spouštějí
+ *
+ * ● vnořená třída DirListView drží subviews (záhlaví a ListView)
+ *
+ * ● 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 items = new ArrayList();
+
+ 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.
+ * ● u serveru objedná port pro kopírování na pozadí
+ * ● do parametrů uloží port, pole se jmény vybraných souborů, jméno cílového adresáře
+ * ● 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.
+ * ● expose se nedávkuje, vyřizuje se jen 1.vybraný
+ * ● 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();
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelLocalDir.java
--- /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);
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelLocalMoveDest.java
--- /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(); }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelPeerDir.java
--- /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);
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelServerDir.java
--- /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);
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/PanelServerMoveDest.java
--- /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(); }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/ProgressMonitor.java
--- /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ů
+ *
+ * ● aktivita se spouští z notifikace v Notification Bar
+ *
+ * ● informace se zobrazují AlertDialogem jako list of progress bars
+ *
+ * ● 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;
+ }
+ }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/TaskProgress.java
--- /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();
+ }
+}
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/TransferAlert.java
--- /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();
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/TransferOverview.java
--- /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 statSet = new ArrayList();
+ 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");
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/UploadAct.java
--- /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();
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/fm/UploadFrag.java
--- /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 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;
+ }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackA.java
--- /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;
+ }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackAlertTest.java
--- /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) {}
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackContextMenu.java
--- /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 items = new ArrayList();
+ 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"); }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackDF.java
--- /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");
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackDfTst.java
--- /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);
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackDfUse.java
--- /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);
+ }
+
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackDgrams.java
--- /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 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) {} }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackDialogFragment.java
--- /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;
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackFS.java
--- /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));
+ }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackFile.java
--- /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);
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackNotifDialogAct.java
--- /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);
+ }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackNotifListAct.java
--- /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 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();
+ 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;
+ }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackPanelMenu.java
--- /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 items = new ArrayList();
+
+ 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;
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackUDP.java
--- /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 en = NetworkInterface.getNetworkInterfaces();
+// if(en.hasMoreElements()) {
+ for(; en.hasMoreElements(); ) {
+ NetworkInterface i = en.nextElement();
+ for(Enumeration 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);
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/hack/HackViewDialog.java
--- /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;
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/lastmile/LastMileMeterFrag.java
--- /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();
+ }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/lastmile/LastMileMeterRun.java
--- /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 {
+ /**----------------------------------------------------------
+ * 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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/ExposeFile.java
--- /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); }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/ExposeStream.java
--- /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); }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/FileIO.java
--- /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()); }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/GetPeerHostThread.java
--- /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; }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/GetPut.java
--- /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;
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/InChIO.java
--- /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()); }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/LongAsyncTask.java
--- /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 {
+ 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))
+ );
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetChIO.java
--- /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); }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetChOps.java
--- /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()));
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetConn.java
--- /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);
+ }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetConnPeer.java
--- /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 en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
+ NetworkInterface intf = en.nextElement();
+ for (Enumeration 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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetConnSrv.java
--- /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
+ *
+ * ● 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
+ *
+ * ● 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
+ *
+ * ● 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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetCrypt.java
--- /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 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 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=%s",
+ 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);
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetNode.java
--- /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();
+ }
+}
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetScIO.java
--- /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; }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetSslChIO.java
--- /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);
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetSslChbIO.java
--- /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);
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetSslChnIO.java
--- /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); }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetSslScIO.java
--- /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; }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetThread.java
--- /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 {
+ 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; }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/NetUDP.java
--- /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 en = NetworkInterface.getNetworkInterfaces();
+ for(; en.hasMoreElements(); ) {
+ NetworkInterface i = en.nextElement();
+ for(Enumeration 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
+ *
● 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(); }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/PullFromPeer.java
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/PullFromServer.java
--- /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(); }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/PushToPeer.java
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/PushToServer.java
--- /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); }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/SendPeerHostThread.java
--- /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; }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/ServerCopy.java
--- /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); }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/SrvCmd.java
--- /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
+ *
+ * ● actions are initiated via execute().get(), i.e. blocking
+ *
+ * ● actions should be short time, immediate
+ *
+ * ● after action (or batch of actions) cmd connection needs to be closed
+ *
+ * ● connection close is up to caller (to be able to do actions in batch)
+ *
+ * ● 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; }
+ }
+ }
+}
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/StreamIO.java
--- /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);
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/net/USBBus.java
--- /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);
+ }
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/trash/TitleStat.java
--- /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ů
+ * ● záhlaví má 4 pole
+ *
+ * app id
+ * proměnné stavové pole probíhajících přenosů
+ * momentálně nastavený server a číslo šifrovaného kanálu
+ * label aktuální komponenty GUI
+ *
+ * ● 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();
+ }
+}
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/drawable/pending.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/basic_layout.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/dir_view_layout.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/file_entry.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/flat_button.xml
--- /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 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/h_border_thin.xml
--- /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 @@
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/hack.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/hist_entry.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/hist_list.xml
--- /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 @@
+
+
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/last_mile.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/main_panel.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/net_info.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/peer_file_manager.xml
--- /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 @@
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/progress_entry.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/progress_list.xml
--- /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 @@
+
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/server_file_manager.xml
--- /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 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/sort_header.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/title_bar.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/upload_panel.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/v_border_thin.xml
--- /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 @@
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/layout/word_dialog.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_common.xml
--- /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 @@
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_dir_list.xml
--- /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 @@
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_info.xml
--- /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 @@
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_local_operations.xml
--- /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 @@
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_peer_operations.xml
--- /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 @@
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_prioritized.xml
--- /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 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_selection.xml
--- /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 @@
+
+
+ -
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/fm_server_operations.xml
--- /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 @@
+
+
+ -
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/hack.xml
--- /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 @@
+
+
+ -
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/main.xml
--- /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 @@
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/move_dest.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/menu/order.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/values-w820dp/dimens.xml
--- /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 @@
+
+
+ 64dp
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/values/attrs.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/values/channel.xml
--- /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 @@
+
+
+ - 02
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/values/colors.xml
--- /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 @@
+
+
+ #fd0
+ #048
+ #ff44ff44
+ #ffeeeedd
+ #ffff4444
+ #ff002222
+ #ff4488cc
+ #ff7B70FF
+ #ffffffdd
+ #ff0066aa
+ #ffffaa66
+ #ff00aa00
+ #ff220000
+ #ffffffdd
+ #ff002222
+ #ff888888
+ #ff444444
+ #08f
+ #323331
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/values/dimens.xml
--- /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 @@
+
+
+ 24sp
+ 18sp
+ 12sp
+ 18sp
+ 8sp
+ 2dp
+ 6dp
+
+ 16dp
+ 16dp
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/values/ids.xml
--- /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 @@
+
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/values/strings.xml
--- /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 @@
+
+
+ .dejsem
+ 1.5
+ generalised sharing
+ BAD
+ 2
+ settings
+ uninstall
+ HOST_KEY
+ dejsem.org
+ HOST_CACHE
+ server host
+ server host ip addr or DNS name
+
+ server port
+ specify server ip port
+ SSL_KEY
+ true
+ SSL crypted transfer
+ ff001122
+ text background color
+ specify text background color ffRRGGBB
+ LOG_LEVEL_KEY
+ 4
+ debug log level
+
+ zkušební položka
+
+
+ - 0 - no messages
+ - 1
+ - 2
+ - 3
+ - 4
+ - 5 - full verbosity
+
+
+ - 0
+ - 1
+ - 2
+ - 3
+ - 4
+ - 5
+
+
+ CHAN_KEY
+ 2
+ server comm channel
+ choose comm channel# 1-99
+ HOME_KEY
+ HOME_CACHE
+ /mnt/sdcard
+ preferred local home directory
+ home directory
+ 0.0
+ SSL_DEBUG_KEY
+ false
+ detailed SSL log
+ detailed SSL handshaking log
+
+
+ Hello blank fragment
+ Hack2
+
+ Hello world!
+ Settings
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/values/styles.xml
--- /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 @@
+
+
+
+
+
+
+ #00000000
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/res/xml/prefs.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/build.gradle
--- /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()
+ }
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/hhlibv10-debug/build.gradle
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/local.properties
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/settings.gradle
--- /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'
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/python/dejsem.pycharm/client.py
--- /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
+
+ """
+ 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))
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/python/dejsem.pycharm/counter.py
--- /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)
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/python/dejsem.pycharm/d.py
--- /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)
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/python/dejsem.pycharm/main.py
--- /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 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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/python/dejsem.pycharm/meter.py
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/python/dejsem.pycharm/node.py
--- /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()
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/python/dejsem.pycharm/parms.py
--- /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='', help='debug level 0-5, ENV DEB')
+ options.add_argument('-s', dest='srvhost',
+ metavar='', help='server domain name or ip, ENV HOST')
+ options.add_argument('-c', dest='sslchannel', type=int,
+ metavar='', help='ssl channel NN, ENV CHAN')
+ options.add_argument('-x', dest='sslPath',
+ metavar='', help='ssl keys store directory, ENV SSL')
+ options.add_argument('-i', dest='pullPeerIface',
+ metavar='', help='copy from LAN peer via ')
+ 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'
+ """
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/python/dejsem.pycharm/server.py
--- /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ů
+ ● // --> ///
+ ● / musí mít povoleny symlinky a povolen( instalovat .htaccess s Options Indexes
+ ● http-server musí mít x-access po cestě // -->
+ ● 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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/ssl/CA.ext
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/ssl/ssl.sh
--- /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 | 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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/bin/d.l
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/bin/d.list
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/bin/d.plc
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/bin/d.plf
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/bin/d.plp
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/bin/d.puc
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/bin/d.puf
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/bin/d.pull
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/bin/d.pullfile
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/bin/d.pulllist
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/bin/d.pullpeer
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/bin/d.pup
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/bin/d.push
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/bin/d.pushfile
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/bin/d.pushpeer
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/bin/dejsem
--- /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 "$@"
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/bin/dejsemd
--- /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
+
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/debian/client.DEBIAN/config
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/debian/client.DEBIAN/control
--- /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
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/debian/client.DEBIAN/postinst
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/debian/client.DEBIAN/postrm
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/debian/client.DEBIAN/prerm
--- /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
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/debian/client.DEBIAN/templates
--- /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.
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/debian/common.DEBIAN/control
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/debian/common.DEBIAN/postinst
--- /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
+}
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/debian/dummy.DEBIAN/control
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/debian/etc/default/dejsem
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/debian/etc/init.d/dejsemd
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/debian/makefile
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/debian/pack
--- /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
+
+ # 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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/debian/server.DEBIAN/control
--- /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
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/debian/server.DEBIAN/postinst
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/debian/server.DEBIAN/postrm
--- /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
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/debian/server.DEBIAN/prerm
--- /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
+
diff -r 000000000000 -r 676905a3b03c dejsem.1.5/unix/test.sh
--- /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=""
+ echo SSL=""
+ 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