# HG changeset patch # User hh # Date 1574844616 -3600 # Node ID 676905a3b03ca3c0eabb3aac7162f18b271aa491 -- diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/build.gradle --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dejsem.1.5/android/dejsem.studio/app/build.gradle Wed Nov 27 09:50:16 2019 +0100 @@ -0,0 +1,35 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + + defaultConfig { + applicationId "hh.dejsem" + minSdkVersion 22 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + postprocessing { + removeUnusedCode false + removeUnusedResources false + obfuscate false + optimizeCode false + proguardFile 'proguard-rules.pro' + } + } + } +} + +dependencies { + implementation project(':hhlibv10-debug') + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'com.android.support:support-v4:28.0.0-beta01' + implementation 'com.android.support:appcompat-v7:28.0.0-beta01' + implementation 'com.android.support:preference-v7:28.0.0-beta01' +} diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/AndroidManifest.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dejsem.1.5/android/dejsem.studio/app/src/main/AndroidManifest.xml Wed Nov 27 09:50:16 2019 +0100 @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/Act.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/Act.java Wed Nov 27 09:50:16 2019 +0100 @@ -0,0 +1,96 @@ +package hh.dejsem; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.view.ContextMenu; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +import hh.lib.DA; + +public class Act extends DA implements Handler.Callback { + + @Override + public void onCreate(Bundle b) { + super.onCreate(b); + new Prefs().refreshPrefs(this); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { // volá se jen jednou + d.l(4, "onCreateOptionsMenu"); + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if(d.ll(4)) d.l(String.format("options menu item selected=%s", item.getTitle())); + if(onItemSelected(item)) return true; + else return super.onOptionsItemSelected(item); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + d.l(2, "onCreateContextMenu"); + getMenuInflater().inflate(R.menu.main, menu); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + if(d.ll(4)) d.l(String.format("context menu item selected=%s", item.getTitle())); + if(onItemSelected(item)) return true; + else return super.onContextItemSelected(item); + } + + @Override + public boolean handleMessage(Message msg) { + if(d.ll(4)) d.l("handleMessage: what=" + msg.what); + return false; + } + + public boolean onItemSelected(MenuItem item) { + switch(item.getItemId()) { + case R.id.menu_settings: + getSupportFragmentManager().beginTransaction().replace(android.R.id.content, new Prefs()).addToBackStack("Prefs").commit(); + return true; + case R.id.menu_uninstall: + Uri packageURI = Uri.parse("package:" + getComponentName().getPackageName().toString()); + Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI); + startActivity(uninstallIntent); + return true; + case R.id.menu_activity_hack: + hack(); + return true; + case R.id.menu_del_passwd: + PassW.clearPW(); + return true; + case R.id.menu_del_sd_card_perms: + SdUri.clearPermissions(); + return true; + case R.id.menu_clean_caches: + cleanCache(); + return true; + case R.id.menu_reset_prefs: + new Prefs().resetPrefs(this); + return true; + } + return false; + } + + public void cleanCache() { + Prefs.sp.edit().remove(Prefs.HOST_CACHE_KEY).apply(); + Prefs.sp.edit().remove(Prefs.HOME_CACHE_KEY).apply(); + } + + @Override + public void hack() { + if(d.ll(4)) d.l("hack"); + } + +} + diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/BroadcastReceiver.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/BroadcastReceiver.java Wed Nov 27 09:50:16 2019 +0100 @@ -0,0 +1,17 @@ +package hh.dejsem; + +import android.content.Context; +import android.content.Intent; + +import hh.lib.D; + +public class BroadcastReceiver extends android.content.BroadcastReceiver { + + D d; + + @Override + public void onReceive(Context context, Intent intent) { + d = new D(context, getClass().getSimpleName()); + d.setContext(context); + } +} diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/FlatButton.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/FlatButton.java Wed Nov 27 09:50:16 2019 +0100 @@ -0,0 +1,265 @@ +package hh.dejsem; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.support.v7.widget.AppCompatTextView; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import hh.lib.D; + +public class FlatButton extends RelativeLayout { + /**------------------------------------------------------------------------- + * Základem je RelativeLayout jako kontejner + * - do levého horního rohu se umístí TextView s textem, s paddigem, + * s defaultním nebo explicitním marginem + * - na okraje textu (včetně jeho paddingu) se chytne rámeček s kulatými rohy + * a průhledným pozadím (FrameLayout s vepsaným obdélníkem). + * + * Poloměr rohů rámečku a padding textu se odvozuje z velikosti fontu. + * Barva se přebírá z barvy textu. + * Padding vytváří odstup rámečku od textu, margin vytváří odstup vně rámečku + * od sousedních views (hlavně od sousedních tlačítek). + * + * Kontejner, text i rámeček dostávají všechny XML-atributy z nichž se natvrdo přebíjejí + * android:background průhledným pozadím + * android:gravity centrováním textu v jeho view + * android:padding... padding textu se odvozuje z velikosti fontu + * + * Při vynechání android:text se doplňuje "button" + * + * Všechny android:layout_margins se vztahují k nadřídené viewGroup. + * Margins pro umístění textu v kontejneru se berou z custom name space + * ns:layout_margin přebíjí default margin stejný na všech stranách + * ns:layout_marginStart přebíjí margin na jedné straně + * ns:layout_marginTop přebíjí margin na jedné straně + * ns:layout_marginEnd přebíjí margin na jedné straně + * ns:layout_marginBottom přebíjí margin na jedné straně + * + * Programově lze nastavit + * text setText(String) + * velikost písma setTargetSize(float) + * barvu setColor(int) + * marginStart v kontejneru + * setMarginStart(int) + * marginEnd v kontejneru + * setMarginEnd(int) + * V se předpokládá definice styleable-grupy FlatButton + * + * + * <-- TODO + * + * + * + * + * + * + */ + D d; + static final int WRAP_CONTENT = RelativeLayout.LayoutParams.WRAP_CONTENT; + TextView t = null; + Encircle e = null; + View v = null; /* overlay for click */ + float rad; /* poloměr rohů */ + final int defaultMargin = 8; /* margin textu v kontejneru */ + final int textId = android.R.id.text1; + String text = "button"; + int textPadding = -1, textPaddingStart = -1, textPaddingTop = -1, textPaddingEnd = -1, textPaddingBottom = -1; + + class Encircle extends View { + String tag; + final Paint stroke; + int width = 0, height = 0; + + Encircle(Context c, AttributeSet as) { + super(c, as); +// setBackgroundResource(Color.TRANSPARENT); + setBackgroundResource(android.R.color.transparent); + stroke = new Paint(); + stroke.setAntiAlias(true); + stroke.setStyle(Paint.Style.STROKE); + stroke.setColor(t.getCurrentTextColor()); + stroke.setStrokeWidth(3f); + if(d.ll(5)) d.l(String.format("text created")); + } + + public void onDraw(Canvas canvas) { + canvas.drawRoundRect(new RectF(0, 0, width, height), rad, rad, stroke); + if(d.ll(5)) d.l(String.format("onDraw: width=%d, height=%d, rad=%activeFragment", width, height, rad)); + } + + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if(d.ll(5)) d.l(String.format("onLayout: changed=%b, w=%d, h=%d", changed, getWidth(), getHeight())); + if(changed) { + width = getWidth(); + height = getHeight(); +// rad = height / 4; + } + } + } + + class Text extends AppCompatTextView { + String tag; + + Text(Context c, AttributeSet as) { + super(c, as); + setId(textId); + setText(text); +// Typeface tf = Typeface.create(defaultText, Typeface.NORMAL); +// setTypeface(tf); + setGravity(Gravity.CENTER); + FlatButton.this.setPadding(this); + setBackgroundResource(android.R.color.transparent); + if(d.ll(5)) d.l(String.format("text created")); + } + + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + if(d.ll(5)) d.l(String.format("onDraw: w=%d, h=%d", getWidth(), getHeight())); + } + + protected void onMeasure(int w, int h) { + super.onMeasure(w, h); + if(d.ll(5)) d.l(String.format("onMeasure: w=%d, h=%d", w, h)); + } + } + + public FlatButton(Context c) { + this(c, null); + } + + public FlatButton(Context c, AttributeSet as) { + super(c, as); + d = new D(c, getClass().getSimpleName() + (getId() == View.NO_ID ? ".xx" : ".id" + getId())); + + int margin = defaultMargin; + int marginStart = defaultMargin; + int marginTop = defaultMargin; + int marginEnd = defaultMargin; + int marginBottom = defaultMargin; + + if(as != null) { + TypedArray ta = c.getResources().obtainAttributes(as, R.styleable.FlatButton); + try { + if(d.ll(5)) d.l(String.format("android:text has value=%b, hh:text has value=%b", + ta.hasValue(R.styleable.FlatButton_android_text), ta.hasValue(R.styleable.FlatButton_text))); + if(ta.hasValue(R.styleable.FlatButton_android_text)) + text = ta.getString(R.styleable.FlatButton_android_text); + if(ta.hasValue(R.styleable.FlatButton_layout_margin)) + margin = ta.getDimensionPixelOffset(R.styleable.FlatButton_layout_margin, defaultMargin); + if(ta.hasValue(R.styleable.FlatButton_layout_marginStart)) + marginStart = ta.getDimensionPixelOffset(R.styleable.FlatButton_layout_marginStart, margin); + else marginStart = margin; + if(ta.hasValue(R.styleable.FlatButton_layout_marginTop)) + marginTop = ta.getDimensionPixelOffset(R.styleable.FlatButton_layout_marginTop, margin); + else marginTop = margin; + if(ta.hasValue(R.styleable.FlatButton_layout_marginEnd)) + marginEnd = ta.getDimensionPixelOffset(R.styleable.FlatButton_layout_marginEnd, margin); + else marginEnd = margin; + if(ta.hasValue(R.styleable.FlatButton_layout_marginBottom)) + marginBottom = ta.getDimensionPixelOffset(R.styleable.FlatButton_layout_marginBottom, margin); + else marginBottom = margin; + if(d.ll(5)) d.l(String.format("text_padding has value=%b", ta.hasValue(R.styleable.FlatButton_text_padding))); + textPadding = ta.getDimensionPixelOffset(R.styleable.FlatButton_text_padding, -1); + if(ta.hasValue(R.styleable.FlatButton_text_paddingStart)) + textPaddingStart = ta.getDimensionPixelOffset(R.styleable.FlatButton_text_paddingStart, -1); + if(ta.hasValue(R.styleable.FlatButton_text_paddingTop)) + textPaddingTop = ta.getDimensionPixelOffset(R.styleable.FlatButton_text_paddingTop, -1); + if(ta.hasValue(R.styleable.FlatButton_text_paddingEnd)) + textPaddingEnd = ta.getDimensionPixelOffset(R.styleable.FlatButton_text_paddingEnd, -1); + if(ta.hasValue(R.styleable.FlatButton_text_paddingBottom)) + textPaddingBottom = ta.getDimensionPixelOffset(R.styleable.FlatButton_text_paddingBottom, -1); + } finally { ta.recycle(); } + } + + t = new Text(c, as); + LayoutParams textLayout = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT); + textLayout.addRule(ALIGN_PARENT_TOP); + textLayout.addRule(ALIGN_PARENT_LEFT); + textLayout.setMargins(marginStart, marginTop, marginEnd, marginBottom); + + e = new Encircle(c, as); + LayoutParams encircleLayout = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT); + encircleLayout.addRule(ALIGN_RIGHT, textId); + encircleLayout.addRule(ALIGN_LEFT, textId); + encircleLayout.addRule(ALIGN_TOP, textId); + encircleLayout.addRule(ALIGN_BOTTOM, textId); + + v = new View(c, as); + + addView(e, encircleLayout); + addView(t, textLayout); + addView(v, encircleLayout); + + if(d.ll(5)) d.l(String.format("flat button created")); + } + + void setPadding(TextView t) { + rad = t.getTextSize() / 3; + final int defaultPad = textPadding == -1 ? Math.round(rad * 0.7f) : textPadding; + final int padStart = textPaddingStart == -1 ? defaultPad : textPaddingStart; + final int padTop = textPaddingTop == -1 ? defaultPad : textPaddingTop; + final int padEnd = textPaddingEnd == -1 ? defaultPad : textPaddingEnd; + final int padBottom = textPaddingBottom == -1 ? defaultPad : textPaddingBottom; + t.setPadding(padStart, padTop, padEnd, padBottom); + invalidate(); + if(d.ll(5)) d.l(String.format("textPadding=%d, defaultPad=%d, padTop=%d", textPadding, defaultPad, padTop)); + } + + public FlatButton setText(String text) { + t.setText(text); + invalidate(); + return this; + } + + CharSequence getText() { return t.getText(); } + + public FlatButton setSize(float textSize) { + t.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); + setPadding(t); + invalidate(); + return this; + } + + float getSize() { return t.getTextSize(); } + + public FlatButton setColor(int color) { + if(d.ll(5)) d.l(String.format("setColor=%x", color)); + t.setTextColor(color); + e.stroke.setColor(color); + invalidate(); + return this; + } + + int getColor() { return e.stroke.getColor(); } + + public FlatButton setMarginStart(int margin) { + LayoutParams lp = (LayoutParams)t.getLayoutParams(); + lp.setMarginStart(margin); + t.setLayoutParams(lp); + invalidate(); + return this; + } + + FlatButton setMarginEnd(int margin) { + LayoutParams lp = (LayoutParams)t.getLayoutParams(); + lp.setMarginEnd(margin); + t.setLayoutParams(lp); + invalidate(); + return this; + } + + public void setOnClickListener(View.OnClickListener lstnr) { + v.setOnClickListener(lstnr); + } + + public void setTag(String tag) { v.setTag(tag); } +} diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/GetReady.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/GetReady.java Wed Nov 27 09:50:16 2019 +0100 @@ -0,0 +1,79 @@ +package hh.dejsem; + +import android.content.Context; +import android.content.Intent; +import android.os.Messenger; +import android.support.v7.app.AppCompatActivity; + +import java.lang.ref.WeakReference; +import java.util.HashSet; +import java.util.Set; + +import hh.lib.AbendDialogFragment; +import hh.lib.D; + +public class GetReady implements PassW.PasswListener, SdUri.SDUriListener, AbendDialogFragment.IgnoreAbend { + + public interface ReadyListener { void onReady(boolean ok); } + + static GetReady instance = null; + + public static boolean isReady(D d) { + final boolean ready = PassW.isReady() && SdUri.isReady(); + d.l(4, "isReady=" + ready); + return ready; + } + + D d; + Context c; + + Set subscribers = new HashSet(); + private GetReady() {} + + public static void getReady(D d, Object subscriber) { + if(instance == null) { + instance = new GetReady(); + instance.d = d.klon(instance); + instance.d.l(4, "getReady"); + if(isReady(instance.d)) ((ReadyListener)subscriber).onReady(true); + else { + instance.c = (Context)subscriber; + instance.subscribers.add(subscriber); + instance.go(subscriber); + } + } + else { + if(isReady(instance.d)) ((ReadyListener)subscriber).onReady(true); + else instance.subscribers.add(subscriber); + } + } + + @Override + public void onPasswReady(int result) { + if(result < PassW.PW_BAD) { + SdUri.subscriber = this; + c.startActivity(new Intent(c, SdUri.class)); + } + else result(false); + instance = null; + } + + @Override + public void onSDPermissionReady() { result(true); } + + @Override + public void ignoreAbend(boolean ignore) { if(!ignore) d.getAct().finish(); } + + void go(Object subscriber) { + d.l(4, "go"); + K.app = c.getApplicationContext(); + D.applMsgr = new WeakReference(d.actMsgr); + new PassW(d, (AppCompatActivity)subscriber, this); + } + + void result(boolean success) { + d.l(4, "notifying subscribers: success=" + success); + for(Object subscriber: subscribers) ((ReadyListener)subscriber).onReady(success); + subscribers.clear(); + } +} diff -r 000000000000 -r 676905a3b03c dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/K.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dejsem.1.5/android/dejsem.studio/app/src/main/java/hh/dejsem/K.java Wed Nov 27 09:50:16 2019 +0100 @@ -0,0 +1,276 @@ +package hh.dejsem; + +import android.content.Context; +import android.os.Environment; +import android.support.v4.provider.DocumentFile; + +import java.io.File; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.util.HashMap; + +import hh.dejsem.fm.TransferOverview; +import hh.dejsem.net.NetConnSrv; +import hh.dejsem.net.NetNode; +import hh.dejsem.net.NetUDP; +import hh.dejsem.net.SrvCmd; +import hh.lib.D; + +/* TODO +- problémy k 30.10.2018 + - když se ztratí Notification, nedá se obnovit i když všechny procesy běží normálně dál + - po server upload se nerefreshuje file list +- kontejner roznášející libovolné objekty +- abend msg s bezpodmínečným abendem +- StrictMode.ThreadPolicy +- konsolidace K +- optimalizace cachingu +*/ + +public class K { + /*---------------------------------------------------------- + * konstanty + *---------------------------------------------------------*/ + // velký timeout má smysl u pomalého výstupu, po němž následuje vstup - data se odlejí do bufferu a přejde se na čtení, + // ale vstup z protější strany přijde, až když odejseme celý buffer pomalým výstupem na protější stranu} + public static final int acceptTO = 60 * 1000; // peer server accept timeout in msecs + public static final int socketTO = 33 * 1000; // socket IO timeout in msecs + + public static final int BASE_PORT_NUM = 42000; + public static final int buffSize = 8192; // data transfer buffer size + + public static final String ACTION_KEY = "ACTION"; + public static final String TXT_KEY = "TXT"; +// public static final String SRC_KEY = "SRC"; + public static final String PORT_KEY = "SERVER_PORT"; + public static final String FILES_FROM_KEY = "FILES_FROM"; + public static final String FILE_TO_KEY = "FILE_TO"; + public static final String FN_KEY = "FILE_NAME"; + public static final String STREAM_KEY = "INTENT_STREAM"; + public static final String MIME_TYPE_KEY = "MIME_TYPE"; + public static final String LFN_KEY = "LOCAL_FILE_NAME"; + public static final String RFN_KEY = "REMOTE_FILE_NAME"; + + public static final String SD_CARD_MOUNT_POINT = "/storage/extSdCard/"; + public static DocumentFile SD_CARD_ROOT_DOC_FILE = null; // sd card root odsouhlasený na začátku a uložený v SharedPrefs + + public static final String CACHEFNR_KEY = "remoteFileListCache"; + public static final String CACHEFNL_KEY = "localFileListCache"; + public static final String CACHED_LOC_CWD_KEY = "CACHED_LOC_CWD"; + public static final String CACHED_REM_CWD_KEY = "CACHED_REM_CWD"; + + public static final String HISTORY_TAG = "HISTORY"; + public static final String NET_INFO_TAG = "NET_INFO"; + public static final String LAST_MILE_TAG = "LAST_MILE"; + public static final String UPLOAD_TAG = "UPLOAD"; + + public static final String FM_SRV_TAG = "fmsrv"; + public static final String FM_PEER_TAG = "fmpeer"; + public static final String FM_DIALOG_SAVED_STATUS_KEY = "DIALOG_SAVED_STATUS"; + public static final String FM_LOC_SAVED_POSITION_KEY = "FM_LOC_SAVED_POSITION"; + public static final String FM_LOC_SAVED_SELECTION_KEY = "FM_LOC_SAVED_SELECTION"; + public static final String FM_SRV_SAVED_POSITION_KEY = "FM_SRV_SAVED_POSITION"; + public static final String FM_SRV_SAVED_SELECTION_KEY = "FM_SRV_SAVED_SELECTION"; + public static final String FM_SAVED_CWD_KEY = "FM_SAVED_CWD"; + public static final String FN_RESERVED_CHARS_REGEX = "[\"*|:<>\\?]"; + public static final String FS_KIND_KEY = "FS_KIND"; + + /** + * request codes + */ + public static final int MAIN_REFRESH = 16; + public static final int HISTORY = 17; + public static final int CHOOSE_SD_CARD_TREE = 18; + public static final int GETPW = 19; + + public static final int LOC_FS = 72; + public static final int SRV_FS = 82; + + public static final int MSG_COPY_TO_SRV = 224; + public static final int MSG_COPY_FROM_SRV = 225; + public static final int MSG_COPY_TO_PEER = 226; + public static final int MSG_COPY_FROM_PEER = 227; + public static final int MSG_EXPOSE_TO_SRV = 228; + + public static final int MSG_TEST = 230; + public static final int MSG_REFRESH_LOC = 231; // refresh local FS dir list + public static final int MSG_REFRESH_SRV = 232; // refresh remote FS dir list + public static final int MSG_WATCH_PROGRESS = 233; + + public static final int MSG_END = 240; + public static final int MSG_PING = 241; + public static final int MSG_EXPOSE = 242; + public static final int MSG_SVC_ABEND = 243; + public static final int MSG_SVC_WARNING = 244; + public static final int MSG_SVC_INFO = 245; + + public static final int TRANSFER_NOTIFICATION_ID = 300; + public static final int TRANSFER_PIN_NOTIFICATION = 301; + public static final int TRANSFER_DISPLAY_LIST = 302; + public static final int TRANSFER_WARNING = 303; + public static final int TRANSFER_ABEND = 304; + public static final int TRANSFER_DISPLAY_TEST = 305; + public static final int TRANSFER_SEND_URI = 306; + + public static final int FM_ACTION_DELETE = 400; + public static final int FM_ACTION_RENAME = 401; + public static final int FM_ACTION_CREATE = 402; + public static final int FM_MOVE_DEST = 403; + public static final int FM_MOVE_CANCEL = 404; + public static final int FM_MENU_SERVER = 405; + public static final int FM_MENU_LOCAL = 406; + public static final int FM_MENU_PEER = 406; + + public static final String UDP_ADVERT_KEY = "PEER_IP"; + public static final String CRYPT_ALG = "DESede/ECB/NoPadding"; + + public static final int CRYPT_KEYLEN = 24; + public static int UDP_PORT = 4242; // dejsem 4224, dejsem 4242 + + public static final String testText = "<---- 150chars/180bytes of test text ---------------- " + + "příliš žluťoučký kůň úpěl ďábelské kódy ------------ " + + "PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ KÓDY -->"; + public static final String passId = "word"; + private static final String ext_stg = Environment.getExternalStorageDirectory().getAbsolutePath(); + public static final String home = ext_stg + "/_HH"; + public static final String dnld = ext_stg + "/Download"; + + public static void cmdConnClose(D d) { + if (cmdConn != null && cmdConn.netIO.isConnected()) + try { SrvCmd.CLOSE.exec(d, null); } + catch(Exception e) { d.abendMsg("closing cmd connection", e); } + } + + public static boolean blocked() { return netInUse; } + + public static String printShared() { + return String.format("D.netNode=%s, D.cmdConn=%s, D.transfProgress=%s", netNode, cmdConn, transfProgress); + } + + public static class NotCreated extends Exception { + public NotCreated() { super("not created"); } + public NotCreated(String msg) { super(msg); } + } + + public static class NotRenamed extends Exception { NotRenamed() { super("not renamed"); } } + + public static class NotDeleted extends Exception { NotDeleted() { super("not deleted"); } } + + public static class NothingSelected extends Exception { public NothingSelected() { super("nothing selected"); } } + + public static class AllPortsBusy extends Exception { public AllPortsBusy() { super("all server ports busy"); } } + + public static class NotCompleted extends Exception { public NotCompleted() { super("not completed"); } } + + public static class AlreadyExists extends Exception { AlreadyExists(String msg) { super(msg); } } + + public static class EntryNotFound extends Exception {} + + public static class NoPeerListening extends Exception { public NoPeerListening() { super("no peer is listening"); } } + + public static class IntentionalABEND extends Exception { public IntentionalABEND(String msg) { super(msg); } } + + /** + * handle Abnormal End information + */ + public static class Abend extends Exception { + /*---------------------------------------------------------- + * handle Abnormal End information + *----------------------------------------------------------*/ + Abend() { this(""); } + + Abend(String m) { + this(m, null); + } + + Abend(String m, Throwable t) { + super(m, t); + if (t != null) t.printStackTrace(); + } + } + + /** + * handle Quiet Abnormal End information + */ + public static class End extends Exception { + public End() { + this(null, null); + } + + public End(String m) { + this(m, null); + } + + public End(Throwable t) { this(null, t); } + + public End(String m, Throwable t) { + super(m, t); + if(t != null) t.printStackTrace(); + } + } + + public enum HistOrder {TEXT, SIZE, DATE} + + public enum DirListOrder { + NAME_ASC(0), NAME_DESC(1), + SIZE_ASC(2), SIZE_DESC(4), + DATE_ASC(6), DATE_DESC(7); + + DirListOrder(int value) { + this.value = value; + } + + private final int value; + + public int value() { + return value; + } + } + + public enum LongTaskAction { + BATCHEND("BATCHEND"), PUSHFIEX("PUSHFIEX"), PUSHSTEX("PUSHSTEX"), + PULLFILE("PULLFILE"), PUSHFILE("PUSHFILE"); + + LongTaskAction(String action) { + this.action = action; + } + + public final String action; + + public String act() { + return action; + } + } + + /**---------------------------------------------------------- + * shared values + * ----------------------------------------------------------*/ + public static final Object synchron = new Object(); + public static Context app = null; + public static TitleBar title; + public static HashMap base = new HashMap(); + // 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í: + *
    + *
  1. sdílení clipboardu skrze server včetně udržování historie
  2. + *
  3. sdílení datových souborů se serverem
  4. + *
  5. peer-to-peer přenos datových souborů v LANu
  6. + *
  7. vystavení datových souborů na serveru
  8. + *
  9. informace o síťovém připojení včetně průchodnosti internetové last mile
  10. + *
+ * ● 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 @@ + + + +