From 28b2eb95106de6721edeff49bf2b0029e1360642 Mon Sep 17 00:00:00 2001 From: staccatomamba Date: Fri, 5 Aug 2016 16:23:58 +0200 Subject: [PATCH] =?UTF-8?q?BeWoMitarbeiterapp:=20Socket-Management=20?= =?UTF-8?q?=C3=BCberarbeitet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Android/BewoMitarbeiterApp/.idea/misc.xml | 2 +- Android/BewoMitarbeiterApp/app/app.iml | 18 +- .../app/src/main/AndroidManifest.xml | 4 +- .../bewomitarbeiterapp/ChatActivity.java | 365 +++--------------- .../bewomitarbeiterapp/KontaktAdapter.java | 13 +- .../KontaktChatActivity.java | 183 ++++----- .../bewomitarbeiterapp/LoginActivity.java | 21 +- .../MainActivityMitarbeiter.java | 284 ++------------ .../src/main/java/soapConnection/Packet.java | 6 +- .../java/tcpConnection/SocketManager.java | 217 +++++++++++ .../main/java/tcpConnection/UIHandler.java | 10 + BeWoChatServer/Server.cs | 2 + BeWoOhneReports.v12.suo | Bin 705024 -> 707584 bytes 13 files changed, 418 insertions(+), 707 deletions(-) create mode 100644 Android/BewoMitarbeiterApp/app/src/main/java/tcpConnection/SocketManager.java create mode 100644 Android/BewoMitarbeiterApp/app/src/main/java/tcpConnection/UIHandler.java diff --git a/Android/BewoMitarbeiterApp/.idea/misc.xml b/Android/BewoMitarbeiterApp/.idea/misc.xml index 6a1e020df..95f0f0310 100644 --- a/Android/BewoMitarbeiterApp/.idea/misc.xml +++ b/Android/BewoMitarbeiterApp/.idea/misc.xml @@ -37,7 +37,7 @@ - + diff --git a/Android/BewoMitarbeiterApp/app/app.iml b/Android/BewoMitarbeiterApp/app/app.iml index d84469f91..3bb9e0c40 100644 --- a/Android/BewoMitarbeiterApp/app/app.iml +++ b/Android/BewoMitarbeiterApp/app/app.iml @@ -64,14 +64,6 @@ - - - - - - - - @@ -80,6 +72,14 @@ + + + + + + + + @@ -114,8 +114,8 @@ - + diff --git a/Android/BewoMitarbeiterApp/app/src/main/AndroidManifest.xml b/Android/BewoMitarbeiterApp/app/src/main/AndroidManifest.xml index 86e0f7f9a..79b5f2452 100644 --- a/Android/BewoMitarbeiterApp/app/src/main/AndroidManifest.xml +++ b/Android/BewoMitarbeiterApp/app/src/main/AndroidManifest.xml @@ -62,8 +62,8 @@ android:name=".LoginActivity" android:label="@string/title_activity_login"> - - + chatMessages; public static ArrayAdapter adapter; File file; - File UnsentFile = null; protected String recipientName; protected int recipientPersonOid; private SpiceManager spiceManager = new SpiceManager(UncachedSpiceService.class); + private UIHandler chatUIHandler; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Log.i("CHAT_ON_CREATE", "Kreiere..."); - setContentView(R.layout.chat); ActionBar action = getSupportActionBar(); @@ -122,11 +104,9 @@ public class ChatActivity extends ActionBarActivity{ LayoutInflater inflate = LayoutInflater.from(this); View mCustomView = inflate.inflate(R.layout.custom_menuebar, null); - // Initialiesiere die Variablen TextView titlename = (TextView)mCustomView.findViewById(R.id.PersonalName); ImageView titlepic = (ImageView)mCustomView.findViewById(R.id.PersonalPic); - //get Daten aus vorheriger Activity Bundle zielkorb = getIntent().getExtras(); recipientName = zielkorb.getString("Name"); recipientPersonOid = zielkorb.getInt("RecipientOid"); @@ -134,7 +114,6 @@ public class ChatActivity extends ActionBarActivity{ byte[] bildArray = zielkorb.getByteArray("Bild"); Bitmap customerImage; - //ein try catch wäre vorteilhaft || Später if(bildArray != null) { customerImage = BitmapFactory.decodeByteArray(bildArray, 0, bildArray.length); titlepic.setImageBitmap(GetCircleBitmap(customerImage)); @@ -158,329 +137,97 @@ public class ChatActivity extends ActionBarActivity{ adapter = new MessageAdapter(this, R.layout.item_chat_left, chatMessages); listView.setAdapter(adapter); + chatUIHandler = new UIHandler() { + @Override + public void updateUserInterface(final Packet chatPacket) { + try { + if(!chatPacket.ChatDataIdentifier.equals(DataIdentifier.MESSAGE)) { + return; + } - try { - socket = KontaktChatActivity.socket; + listView.post(new Runnable() { + @Override + public void run() { + chatPacket.writeToLog(); - KontaktChatActivity.CloseSocketListener(); - MainActivityMitarbeiter.CloseSocketListener(); + ChatMessage message = new ChatMessage(chatPacket.SenderPersonOid, chatPacket.RecipientPersonOid, chatPacket.MessageTimeStamp, chatPacket.ChatMessage, false, chatPacket.ServerseitigeOid); - socketOutputStream = new DataOutputStream(socket.getOutputStream()); + databaseHandler.addChatMessage(message); - socketListener = new SocketListener(); - listen = new Thread(socketListener); - listen.start(); - } catch(Exception e){ - Log.e("CHAT_ERROR", "Fehler bei der Socket-Erstellung: " + e.getMessage()); - } + chatMessages.add(message); + adapter.notifyDataSetChanged(); + } + }); + } catch(Exception pe) { + pe.printStackTrace(); + } + } + }; text = (EditText)findViewById(R.id.editText); senden.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if(!text.getText().toString().isEmpty()) { - new Thread(new MessageSender()).start(); - } else { - Toast.makeText(ChatActivity.this, "Sie müssen schon Text eingeben ", Toast.LENGTH_SHORT).show(); - } - } - }); - - Thread t1 = new Thread(new RequestSender()); - t1.start(); - } - - @Override - public void onResume() { - Log.i("CHAT_ON_RESUME", "Fahre fort..."); - - if(socketListener != null && !socketListener.isRunning) { - socketListener.isRunning = true; - Log.i("CHAT_ON_RESUME", "Listener läuft wieder"); - } - - super.onResume(); - } - - @Override - public void onStop(){ - Log.i("CHAT_ON_STOP", "Stoppe..."); - super.onStop(); - } - - public void onDestroy(){ - Log.i("CHAT_ON_DESTROY", "Zerstöre..."); - - CloseSocketListener(); - - super.onDestroy(); - } - - public class RequestSender implements Runnable { - - @Override - public void run() { - try { - if(socket != null ) { - Packet p = new Packet(); - - p.ChatDataIdentifier = DataIdentifier.OLD_MESSAGE; - p.RecipientPersonOid = recipientPersonOid; - - SharedPreferences settings = getSharedPreferences(Util.PREFS_NAME, 0); - - byte[] tenantByteArray = Base64.decode(settings.getString(Util.PREFS_TENANT_KEY, null), Base64.DEFAULT); - ByteArrayInputStream tenantStream = new ByteArrayInputStream(tenantByteArray); - - p.Tenant = SecurityUtils.decrypt(tenantStream).toString(); - - socketOutputStream.write(p.GetDataStream()); - socketOutputStream.flush(); - } - } catch(IOException s){ - Log.v("Status: ","Senden einer Anfrage, alle vergangenen Nachrichten zu laden konnte nicht ausgeführt werden."); - - s.printStackTrace(); - } catch (NoSuchPaddingException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | InvalidKeyException e) { - e.printStackTrace(); - } - } - } - - public class MessageSender implements Runnable{ - public void run(){ - try{ - if(socket != null ) { Calendar c = Calendar.getInstance(); - final Date now = c.getTime(); + Date now = c.getTime(); - final Packet p = new Packet(); - p.ChatMessage = text.getText().toString(); - p.ChatDataIdentifier = DataIdentifier.MESSAGE; - p.RecipientPersonOid = recipientPersonOid; - p.MessageTimeStamp = now; + Packet packet = new Packet(); + packet.ChatMessage = text.getText().toString(); + packet.ChatDataIdentifier = DataIdentifier.MESSAGE; + packet.RecipientPersonOid = recipientPersonOid; + packet.MessageTimeStamp = now; SharedPreferences settings = getSharedPreferences(Util.PREFS_NAME, 0); byte[] tenantByteArray = Base64.decode(settings.getString(Util.PREFS_TENANT_KEY, null), Base64.DEFAULT); ByteArrayInputStream tenantStream = new ByteArrayInputStream(tenantByteArray); - p.Tenant = SecurityUtils.decrypt(tenantStream).toString(); + try { + packet.Tenant = SecurityUtils.decrypt(tenantStream).toString(); + } catch (IOException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) { + e.printStackTrace(); + } - final ChatMessage message = new ChatMessage(p.SenderPersonOid, p.RecipientPersonOid, p.MessageTimeStamp, p.ChatMessage, false, 0); + packet.writeToLog(); - databaseHandler.addChatMessage(message); + final ChatMessage message = new ChatMessage(packet.SenderPersonOid, packet.RecipientPersonOid, packet.MessageTimeStamp, packet.ChatMessage, false, 0); - socketOutputStream.write(p.GetDataStream()); - socketOutputStream.flush(); + long coid = databaseHandler.addChatMessage(message); + + packet.ClientseitigeOid = Long.valueOf(coid).intValue(); + + SocketManager.sendMessage(packet); listView.post(new Runnable() { @Override public void run() { - - chatMessages.add(message); adapter.notifyDataSetChanged(); text.setText(""); } }); + } else { + Toast.makeText(ChatActivity.this, "Sie müssen schon Text eingeben ", Toast.LENGTH_SHORT).show(); } - else { - Log.i("SEND_CHAT_MESSAGE","Socket ist null"); - } - - - }catch(IOException s){ - Log.v("Status: ","Senden einer Nachricht konnte nicht ausgeführt werden. Save wird ausgeführt"); - - SaveAfterLoseConnection(saveCompleteMessage); - s.printStackTrace(); - } catch (NoSuchPaddingException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | InvalidKeyException e) { - e.printStackTrace(); } - } + }); } - public static void CloseSocketListener() { - if(listen != null && listen.isAlive()) { - try { - socketListener.terminate(); - listen.join(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } + @Override + public void onResume() { + SocketManager.uiHandler = chatUIHandler; + + super.onResume(); } - // Erhalte Messages all the time - public class SocketListener implements Runnable{ - Packet chatPacket; + public void onDestroy(){ + Log.i("CHAT_ON_DESTROY", "Zerstöre..."); - private volatile boolean isRunning = true; - - public void terminate() { - - isRunning = false; - } - - public void run(){ - try{ - while(isRunning){ - if (socket != null) { - if(socket.isConnected()) { - final ListView listV = (ListView) findViewById(R.id.list_msg); - - InputStream stream = socket.getInputStream(); - - int size = stream.available(); - - if(size == 0) { - continue; - } - - StringBuilder total = new StringBuilder(stream.available()); - - int counter = 0; - - while(counter < size) { - - char c = (char) stream.read(); - total.append(c); - - counter++; - } - - - - try { - chatPacket = new Packet(total.toString()); - - Log.i("CHAT_ACTIVIY_LISTENER", "Empfange Nachricht... " + chatPacket.ChatMessage); - - if(!chatPacket.ChatDataIdentifier.equals(DataIdentifier.MESSAGE)) { - continue; - } - - listV.post(new Runnable() { - @Override - public void run() { - - ChatMessage message = new ChatMessage(chatPacket.SenderPersonOid, chatPacket.RecipientPersonOid, chatPacket.MessageTimeStamp, chatPacket.ChatMessage, false, chatPacket.ServerseitigeOid); - - databaseHandler.addChatMessage(message); - - chatMessages.add(message); - adapter.notifyDataSetChanged(); - } - }); - } catch(Exception pe) { - Log.e("CONVERTING JSON: ", pe.getMessage()); - } - } else { - Log.i("SOCKETLISTENER/CHAT", "Nicht mit dem Socket verbunden"); - } - } - } - } catch(Exception e) { - Log.e("Status ","Verbindung zum Server wurde seitens dem Server getrennt: " + e.getMessage()); - } - } - } - - public void SaveAfterLoseConnection(String UnsendMessage){ - CreateCustomFileForUnsentMessage(); - try{ - FileOutputStream stream = new FileOutputStream(UnsentFile,true); - - try{ - stream.write(UnsendMessage.getBytes()); - }finally { - stream.close(); - } - }catch (Exception e){ - Log.v("Fehler",e.getMessage()); - } - } - - public void SendAfterGetConnection(){ - //ist eine UnsendMessage File vorhanden - if(UnsentFile != null && socket.isConnected()){ - try{ - FileInputStream stream = new FileInputStream(UnsentFile); - InputStreamReader in = new InputStreamReader(stream); - BufferedReader readLn = new BufferedReader(in); - - String line = readLn.readLine(); - String allLines=""; - - while (line != null) { - allLines += line; - - String[] splitt = line.split("/"); - - if(splitt.length >1 ) { - String[] ArtDate = splitt[1].split("%"); - - final String message = splitt[0]; - final boolean art = Boolean.valueOf(ArtDate[0]); - final String datetime = ArtDate[1]; - - //für jede Gelesene Line Sende Nachricht - new Thread(new SendUnsentMessage(message)).start(); - } - line = readLn.readLine(); - } - stream.close(); - in.close(); - UnsentFile.delete(); - }catch(Exception fe){ - Log.v("Status","Ein problem ist im BEreich Unsend Ms Aufgetreten"); - } - //recreate(); - } - } - - public class SendUnsentMessage implements Runnable{ - - String al; - - SendUnsentMessage(String unMS){ - this.al = unMS; - } - - public void run() { - - byte[] ts = al.getBytes(); - try { - if (socket != null && socketOutputStream != null) { - - //try catch bei catch SaveAfterLoseConnection(); - socketOutputStream.write(ts); - socketOutputStream.flush(); - } - }catch(Exception xs){ - Log.e("SendUnsentMessage", xs.getMessage()); - } - } - } - - public void CreateCustomFileForUnsentMessage(){ - try{ - File pfad = getBaseContext().getFilesDir(); - - //Für external Save - //File pfad = getBaseContext().getExternalFilesDir(null); - - //Erstelle das File Objekt - UnsentFile = new File(pfad,recipientName+"_Unsend"+".txt"); - - }catch(Exception e){ - Log.v("Fehler"," Das erstellen/öffnen einer Datei ist Derzeit nicht möglich"); - } + super.onDestroy(); } public boolean onCreateOptionsMenu(Menu menu) { diff --git a/Android/BewoMitarbeiterApp/app/src/main/java/beyondsoft/bewomitarbeiterapp/KontaktAdapter.java b/Android/BewoMitarbeiterApp/app/src/main/java/beyondsoft/bewomitarbeiterapp/KontaktAdapter.java index bbdeba7c3..54e2aa27a 100644 --- a/Android/BewoMitarbeiterApp/app/src/main/java/beyondsoft/bewomitarbeiterapp/KontaktAdapter.java +++ b/Android/BewoMitarbeiterApp/app/src/main/java/beyondsoft/bewomitarbeiterapp/KontaktAdapter.java @@ -23,6 +23,7 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; +import java.util.ArrayList; /** * Created by bib on 03.05.2016. @@ -30,7 +31,7 @@ import java.io.InputStreamReader; public class KontaktAdapter extends ArrayAdapter { private final Context context; - private final ContactListItem[] values; + public final ArrayList values; private Bitmap bitmaping; ImageView imageView; @@ -43,7 +44,7 @@ public class KontaktAdapter extends ArrayAdapter { Thread getPic; - public KontaktAdapter(Context context, ContactListItem[] values) { + public KontaktAdapter(Context context, ArrayList values) { super(context, R.layout.kontakt_list_chat, values); this.context = context; this.values = values; @@ -60,7 +61,7 @@ public class KontaktAdapter extends ArrayAdapter { ImageView onlinestats = (ImageView)rowView.findViewById(R.id.status); imageView = (ImageView) rowView.findViewById(R.id.logo); - textView.setText(values[position].getChatName()); + textView.setText(values.get(position).getChatName()); // if(values[position].getChatName().equals("Demo")) { @@ -71,10 +72,10 @@ public class KontaktAdapter extends ArrayAdapter { // } //ab hier fehlerhaft - if(values[position].getPicture() != null) { + if(values.get(position).getPicture() != null) { //Konvert byte[] to Image - Bitmap i = BitmapFactory.decodeByteArray(values[position].getPicture(),0,values[position].getPicture().length); + Bitmap i = BitmapFactory.decodeByteArray(values.get(position).getPicture(),0,values.get(position).getPicture().length); @@ -109,7 +110,7 @@ public class KontaktAdapter extends ArrayAdapter { // else if(values[position].getArt().equals("3")) // onlinestats.setImageResource(R.drawable.offline); - onlinestats.setImageResource((values[position].isOnline ? R.drawable.online : R.drawable.offline)); + onlinestats.setImageResource((values.get(position).isOnline ? R.drawable.online : R.drawable.offline)); return rowView; } diff --git a/Android/BewoMitarbeiterApp/app/src/main/java/beyondsoft/bewomitarbeiterapp/KontaktChatActivity.java b/Android/BewoMitarbeiterApp/app/src/main/java/beyondsoft/bewomitarbeiterapp/KontaktChatActivity.java index 489fc9d65..cfe698f8d 100644 --- a/Android/BewoMitarbeiterApp/app/src/main/java/beyondsoft/bewomitarbeiterapp/KontaktChatActivity.java +++ b/Android/BewoMitarbeiterApp/app/src/main/java/beyondsoft/bewomitarbeiterapp/KontaktChatActivity.java @@ -29,9 +29,7 @@ import org.ksoap2.serialization.PropertyInfo; import org.ksoap2.serialization.SoapPrimitive; import java.io.ByteArrayOutputStream; -import java.io.InputStream; import java.lang.reflect.Type; -import java.net.Socket; import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -43,6 +41,8 @@ import soapConnection.JsonSoapPrimitiveRequest; import soapConnection.Packet; import soapConnection.SoapCalls; import soapConnection.SoapConnectionManager; +import tcpConnection.SocketManager; +import tcpConnection.UIHandler; /** * Created by bib on 02.05.2016. @@ -54,19 +54,16 @@ public class KontaktChatActivity extends ListActivity { private ArrayList onlinePersonOids = new ArrayList<>(); - public static Socket socket; - - public static Thread listen; - public static SocketListener socketListener; + private UIHandler kontaktChatUIHandler; public ListView listView; + ArrayList contactList = new ArrayList<>(); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Log.i("KONTACT_CHAT_CREATE", "Kreiere..."); - databaseHandler = new DatabaseHandler(this); //TODO: nicht löschen, sondern updaten... @@ -83,21 +80,66 @@ public class KontaktChatActivity extends ListActivity { listView = getListView(); - try { - socket = MainActivityMitarbeiter.socket; + kontaktChatUIHandler = new UIHandler() { + @Override + public void updateUserInterface(final Packet chatPacket) { + try { + switch(chatPacket.ChatDataIdentifier) { + case MESSAGE: + ChatMessage message = new ChatMessage(chatPacket.SenderPersonOid, chatPacket.RecipientPersonOid, chatPacket.MessageTimeStamp, chatPacket.ChatMessage, false, chatPacket.ServerseitigeOid); - MainActivityMitarbeiter.CloseSocketListener(); - ChatActivity.CloseSocketListener(); + databaseHandler.addChatMessage(message); -// Thread t = new Thread(new SocketListener()); -// t.start(); + SetNotify(chatPacket.ChatName, chatPacket.ChatMessage, chatPacket.SenderPersonOid); + break; + case STATUS_NOTIFICATION_LOGIN: + if(!onlinePersonOids.contains(chatPacket.SenderPersonOid)) { + onlinePersonOids.add(chatPacket.SenderPersonOid); - socketListener = new SocketListener(); - listen = new Thread(socketListener); - listen.start(); - } catch(Exception e){ - Log.e("CHAT_ERROR", "Fehler bei der Socket-Erstellung: " + e.getMessage()); - } + runOnUiThread(new Runnable() { + @Override + public void run() { + KontaktAdapter ka =((KontaktAdapter) listView.getAdapter()); + + for(ContactListItem item : ka.values) { + if(item.getPersonOid() == chatPacket.SenderPersonOid) { + item.isOnline = true; + break; + } + } + + ka.notifyDataSetChanged(); + } + }); + } + break; + case STATUS_NOTIFICATION_LOGOUT: + if(onlinePersonOids.contains(chatPacket.SenderPersonOid)) { + onlinePersonOids.remove(Integer.valueOf(chatPacket.SenderPersonOid)); + + runOnUiThread(new Runnable() { + @Override + public void run() { + KontaktAdapter ka =((KontaktAdapter) listView.getAdapter()); + + for(ContactListItem item : ka.values) { + if(item.getPersonOid() == chatPacket.SenderPersonOid) { + item.isOnline = false; + break; + } + } + + ka.notifyDataSetChanged(); + } + }); + } + break; + } + } catch(Exception pe) { + pe.printStackTrace(); + } + } + }; } @Override @@ -152,113 +194,22 @@ public class KontaktChatActivity extends ListActivity { @Override public void onResume() { - Log.i("KONTAKT_CHAT_ON_RESUME", "Fahre fort..."); - - if(socketListener != null && !socketListener.isRunning) { - socketListener.isRunning = true; - } - + SocketManager.uiHandler = kontaktChatUIHandler; super.onResume(); } - public void onDestroy(){ - Log.i("KONTAKT_CHAT_ON_DESTROY", "Zerstöre..."); - - CloseSocketListener(); - - super.onDestroy(); - } - @Override public void onStop(){ - Log.i("KONTAKT_CHAT_ON_STOP", "Stoppe..."); - spiceManager.shouldStop(); super.onStop(); } @Override protected void onStart() { - Log.i("KONTAKT_CHAT_ON_START", "Starte..."); - super.onStart(); spiceManager.start(this); } - public class SocketListener implements Runnable { - Packet chatPacket; - - private volatile boolean isRunning = true; - - public void terminate() { - - isRunning = false; - } - - @Override - public void run() { - try { - while(isRunning) { - if (socket != null) { - if (socket.isConnected()) { - InputStream stream = socket.getInputStream(); - - int size = stream.available(); - - if(size == 0) { - continue; - } - - StringBuilder total = new StringBuilder(stream.available()); - - int counter = 0; - - while(counter < size) { - - char c = (char) stream.read(); - total.append(c); - - counter++; - } - - try { - chatPacket = new Packet(total.toString()); - - Log.i("KONTAKT_CHAT_LISTENER", "Empfange Nachricht... " + chatPacket.ChatMessage); - - if(!chatPacket.ChatDataIdentifier.equals(DataIdentifier.MESSAGE)) { - continue; - } - - ChatMessage message = new ChatMessage(chatPacket.SenderPersonOid, chatPacket.RecipientPersonOid, chatPacket.MessageTimeStamp, chatPacket.ChatMessage, false, chatPacket.ServerseitigeOid); - - databaseHandler.addChatMessage(message); - - SetNotify(chatPacket.ChatName, chatPacket.ChatMessage, chatPacket.RecipientPersonOid); - - } catch(Exception pe) { - Log.e("KONTAKT_CHAT_LISTENER_R", pe.getMessage()); - } - } - } - } - } catch(Exception e) { - Log.e("KONTAKT_CHAT_LISTENER_L", e.getMessage()); - } - } - } - - public static void CloseSocketListener() { - if(listen != null && listen.isAlive()) { - try { - socketListener.terminate(); - listen.join(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - private final class JsonSoapPrimitiveRequestListener implements RequestListener { @Override @@ -299,18 +250,14 @@ public class KontaktChatActivity extends ListActivity { } } - ContactListItem[] Klientel = lc.toArray(new ContactListItem[lc.size()]); + contactList = new ArrayList<>(lc); - setListAdapter(new KontaktAdapter(KontaktChatActivity.this, Klientel)); + setListAdapter(new KontaktAdapter(KontaktChatActivity.this, contactList)); databaseHandler.close(); } } public void SetNotify(String username, String message, int recipientOid){ - - //Convertiert in Pic Resource in ein Bitmap - //BitmapFactory.decodeResource(getResources(), R.drawable.bslogo) - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_stat_name) diff --git a/Android/BewoMitarbeiterApp/app/src/main/java/beyondsoft/bewomitarbeiterapp/LoginActivity.java b/Android/BewoMitarbeiterApp/app/src/main/java/beyondsoft/bewomitarbeiterapp/LoginActivity.java index be2e84f03..4d67c628c 100644 --- a/Android/BewoMitarbeiterApp/app/src/main/java/beyondsoft/bewomitarbeiterapp/LoginActivity.java +++ b/Android/BewoMitarbeiterApp/app/src/main/java/beyondsoft/bewomitarbeiterapp/LoginActivity.java @@ -3,9 +3,7 @@ package beyondsoft.bewomitarbeiterapp; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.TargetApi; -import android.app.AlertDialog; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.os.Build; @@ -29,8 +27,6 @@ import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.StringRequest; import com.android.volley.toolbox.Volley; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.octo.android.robospice.SpiceManager; import com.octo.android.robospice.UncachedSpiceService; import com.octo.android.robospice.persistence.exception.SpiceException; @@ -41,9 +37,7 @@ import org.ksoap2.serialization.SoapPrimitive; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; @@ -57,6 +51,7 @@ import soapConnection.ApplicationUser; import soapConnection.JsonSoapPrimitiveRequest; import soapConnection.SoapCalls; import soapConnection.SoapConnectionManager; +import tcpConnection.SocketManager; import util.SecurityUtils; import util.Util; @@ -158,20 +153,17 @@ public class LoginActivity extends ActionBarActivity { @Override protected void onStart() { - super.onStart(); spiceManager.start(this); } @Override protected void onStop() { - spiceManager.shouldStop(); super.onStop(); } private void attemptLogin() { - mTenantView.setError(null); mChatCodeView.setError(null); mUsernameView.setError(null); @@ -284,10 +276,6 @@ public class LoginActivity extends ActionBarActivity { return; } - Gson gson = new GsonBuilder().create(); - - //boolean loginResult = Boolean.valueOf(soapPrimitive.toString()); - //Employee loggedInUser = gson.fromJson(soapPrimitive.toString(), Employee.class); String abc = soapPrimitive.toString(); if(abc != null) { @@ -321,6 +309,11 @@ public class LoginActivity extends ActionBarActivity { } } + SocketManager.tenant = mTenantView.getText().toString(); + + SocketManager.connectToServer(); + SocketManager.LoginOnServer(); + Intent mainActivityMitarbeiter = new Intent(LoginActivity.this, MainActivityMitarbeiter.class); LoginActivity.this.startActivity(mainActivityMitarbeiter); } @@ -332,7 +325,6 @@ public class LoginActivity extends ActionBarActivity { } private void showLoginUIErrors(boolean token, boolean tenant, boolean username, boolean password) { - mChatCodeView.setError(token ? getString(R.string.error_invalid_input) : null); mTenantView.setError(tenant ? getString(R.string.error_invalid_input) : null); @@ -343,7 +335,6 @@ public class LoginActivity extends ActionBarActivity { } private void clearSharedPreferences() { - SharedPreferences settings = getSharedPreferences(Util.PREFS_NAME, 0); settings.edit().clear().commit(); } diff --git a/Android/BewoMitarbeiterApp/app/src/main/java/beyondsoft/bewomitarbeiterapp/MainActivityMitarbeiter.java b/Android/BewoMitarbeiterApp/app/src/main/java/beyondsoft/bewomitarbeiterapp/MainActivityMitarbeiter.java index beef5a40b..b7780ee61 100644 --- a/Android/BewoMitarbeiterApp/app/src/main/java/beyondsoft/bewomitarbeiterapp/MainActivityMitarbeiter.java +++ b/Android/BewoMitarbeiterApp/app/src/main/java/beyondsoft/bewomitarbeiterapp/MainActivityMitarbeiter.java @@ -5,35 +5,24 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Intent; -import android.content.SharedPreferences; import android.graphics.Color; import android.support.v4.app.NotificationCompat; import android.os.Bundle; -import android.util.Base64; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; -import android.widget.Toast; -import java.io.ByteArrayInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.Socket; import java.util.ArrayList; import java.util.Random; -import java.util.Timer; -import java.util.TimerTask; import soapConnection.DataIdentifier; import soapConnection.Packet; -import soapConnection.SoapCalls; import soapConnection.SoapConnectionManager; -import util.SecurityUtils; -import util.Util; +import tcpConnection.SocketManager; +import tcpConnection.UIHandler; public class MainActivityMitarbeiter extends Activity { @@ -42,26 +31,20 @@ public class MainActivityMitarbeiter extends Activity { protected Button chatbutton; protected Button logoutbutton; - public static Socket socket; - DataOutputStream dataOutputStream; - - //Name, der Zum Server gesendet wird protected String loggedInUserName; - public static Thread listen; - public static SocketListener socketListener; + private UIHandler mainUIHandler; int loggedInUserPersonOid; private ArrayList onlinePersonOids; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main_activity_mitarbeiter); - Log.i("MAIN_CREATE", "Kreiere..."); - setzeButtonListener(); loggedInUserName = SoapConnectionManager.User.getFullName(); @@ -70,7 +53,24 @@ public class MainActivityMitarbeiter extends Activity { onlinePersonOids = new ArrayList<>(); - StartServerConnection(); + mainUIHandler = new UIHandler() { + @Override + public void updateUserInterface(Packet chatPacket) { + if(chatPacket.ChatDataIdentifier.equals(DataIdentifier.MESSAGE)) { + Log.i("MAIN_ACTIVIY_LISTENER", "Empfange Nachricht... <<" + chatPacket.ChatMessage + ">>"); + chatPacket.writeToLog(); + SetNotify(chatPacket.ChatName, chatPacket.ChatMessage, chatPacket.SenderPersonOid); + } else if(chatPacket.ChatDataIdentifier.equals(DataIdentifier.STATUS_NOTIFICATION_LOGIN_BROADCAST)) { + + String[] bitch = chatPacket.ChatMessage.split(","); + onlinePersonOids = new ArrayList<>(); + + for (String aBitch : bitch) { + onlinePersonOids.add(Integer.parseInt(aBitch)); + } + } + } + }; } @Override @@ -111,11 +111,11 @@ public class MainActivityMitarbeiter extends Activity { chatbutton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - if(socket == null || !socket.isConnected()) { - Toast.makeText(getApplicationContext(), "Der Chatserver ist zurzeit nicht erreichbar. \nBitte versuchen Sie es später erneut.", Toast.LENGTH_LONG).show(); - - return; - } +// if(socket == null || !socket.isConnected()) { +// Toast.makeText(getApplicationContext(), "Der Chatserver ist zurzeit nicht erreichbar. \nBitte versuchen Sie es später erneut.", Toast.LENGTH_LONG).show(); +// +// return; +// } Bundle bundle = new Bundle(); @@ -133,17 +133,19 @@ public class MainActivityMitarbeiter extends Activity { logoutbutton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - if(socket != null && socket.isConnected()) { - try { - Packet p = new Packet(); +// if(socket != null && socket.isConnected()) { +// try { +// Packet p = new Packet(); +// +// p.ChatDataIdentifier = DataIdentifier.LOGOUT; +// +// new Thread(new MessageSender(p.getDataStream())).start(); +// } catch(Exception e) { +// Log.e("SendAfterGetConnection", "Fehler beim Logout: " + e.getMessage()); +// } +// } - p.ChatDataIdentifier = DataIdentifier.LOGOUT; - - new Thread(new MessageSender(p.GetDataStream())).start(); - } catch(Exception e) { - Log.e("SendAfterGetConnection", "Fehler beim Logout: " + e.getMessage()); - } - } + SocketManager.Logout(); finish(); System.exit(0); @@ -151,182 +153,6 @@ public class MainActivityMitarbeiter extends Activity { }); } - public void StartServerConnection(){ - - new Thread(new OpenConnection()).start(); - } - - public class OpenConnection implements Runnable{ - public void run(){ - try { - socket = new Socket(SoapCalls.DESTINATION_ADDRESS, SoapCalls.DESTINATION_PORT); - - SendAfterGetConnection(); - - dataOutputStream = new DataOutputStream(socket.getOutputStream()); - - socketListener = new SocketListener(); - listen = new Thread(socketListener); - listen.start(); - - } catch (Exception e) { - Log.e("Connection: ","Konnte nicht aufgebaut werden. Es wird erneut versucht. Nachricht: " + e.getMessage()); - - socket = null; - try { - new Timer().schedule(new TimerTask() { - @Override - public void run() { - Log.i("Status", "Starte jetzt den Neu versuch"); - StartServerConnection(); - } - }, 120000); //----> Versuche beim Scheitern nach 2 Minuten eine Neue Verbindung aufzubauen - }catch(Exception s){ - Log.e("Fehler", s.getMessage()); - e.printStackTrace(); - } - } - } - } - - public static void CloseSocketListener() { - if(listen != null && listen.isAlive()) { - try { - socketListener.terminate(); - listen.join(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - - public class SocketListener implements Runnable { - - Packet chatPacket; - - private volatile boolean isRunning = true; - - public void terminate() { - isRunning = false; - } - - public void run() { - try { - while (isRunning) { - if (socket != null) { - if (socket.isConnected()) { - - InputStream stream = socket.getInputStream(); - - int size = stream.available(); - - if(size == 0) { - continue; - } - - StringBuilder total = new StringBuilder(stream.available()); - - int counter = 0; - - while(counter < size) { - - char c = (char) stream.read(); - - total.append(c); - - counter++; - } - - chatPacket = new Packet(total.toString()); - - Log.i("MAIN_ACTIVIY_LISTENER", "Empfange Nachricht... " + chatPacket.ChatMessage); - - if(chatPacket.ChatDataIdentifier.equals(DataIdentifier.MESSAGE)) { - SetNotify(chatPacket.ChatName, chatPacket.ChatMessage, chatPacket.RecipientPersonOid); - } else if(chatPacket.ChatDataIdentifier.equals(DataIdentifier.STATUS_NOTIFICATION_LOGIN_BROADCAST)) { - - String[] bitch = chatPacket.ChatMessage.split(","); - onlinePersonOids = new ArrayList<>(); - - for (String aBitch : bitch) { - onlinePersonOids.add(Integer.parseInt(aBitch)); - } - } - - Log.i("MAIN_ACTIVITY", "Erhaltene Nachricht: " + chatPacket.ChatMessage); - } - } - } - } catch(Exception e) { - Log.e("Listener/Main", "" + e); - - e.printStackTrace(); - } - } - } - - public class ConnectionClose implements Runnable{ - public void run(){ - try { - if (dataOutputStream != null) { - dataOutputStream.flush(); - dataOutputStream.close(); - } - - if (socket != null) { - socket.close(); - } - - Log.i("MAIN CONNECTION CLOSE","Socket succesfully closed"); - }catch(IOException se){ - Log.e("MAIN ERROR:", se.getMessage()); - } - } - } - - public void SendAfterGetConnection(){ - - if(socket.isConnected()) { - try { - Packet p = new Packet(); - - SharedPreferences settings = getSharedPreferences(Util.PREFS_NAME, 0); - - byte[] tenantByteArray = Base64.decode(settings.getString(Util.PREFS_TENANT_KEY, null), Base64.DEFAULT); - ByteArrayInputStream tenantStream = new ByteArrayInputStream(tenantByteArray); - - p.Tenant = SecurityUtils.decrypt(tenantStream).toString(); - p.ChatDataIdentifier = DataIdentifier.LOGIN; - - new Thread(new MessageSender(p.GetDataStream())).start(); - } catch(Exception e) { - Log.e("SendAfterGetConnection", "Fehler beim Senden einer Nachricht an den Server: " + e.getMessage()); - } - } - } - - public class MessageSender implements Runnable { - - byte[] packetToSend; - - public MessageSender(byte[] packet2Send) { - packetToSend = packet2Send; - } - - @Override - public void run() { - try { - if(socket != null && dataOutputStream != null) { - - dataOutputStream.write(packetToSend); - dataOutputStream.flush(); - } - } catch(Exception e) { - Log.e("MessageSender", "Fehler beim Senden der TestMessage " + e.getMessage()); - } - } - } - public void SetNotify(String username, String message, int recipientOid){ //Convertiert in Pic Resource in ein Bitmap @@ -364,38 +190,8 @@ public class MainActivityMitarbeiter extends Activity { @Override public void onResume() { - - KontaktChatActivity.CloseSocketListener(); - ChatActivity.CloseSocketListener(); - - Log.i("ON_RESUME_MAIN", "socketListener ist null: " + (socketListener == null) + "; listen-thread ist null: " + (listen == null)); - - if(socketListener != null) { - Log.i("ON_RESUME_MAIN", "socketListener läuft: " + socketListener.isRunning); - } - - if(socket != null) { - Log.i("ON_RESUME_MAIN", "socket ist verbunden: " + socket.isConnected()); - } - - if(socketListener != null && !socketListener.isRunning) { - socketListener.isRunning = true; - Log.i("ON_RESUME_MAIN", "socketListener läuft: " + socketListener.isRunning); - } + SocketManager.uiHandler = mainUIHandler; super.onResume(); } - - @Override - public void onStop(){ - - super.onStop(); - } - - public void onDestroy(){ - Thread t = new Thread(new ConnectionClose()); - t.start(); - - super.onDestroy(); - } } diff --git a/Android/BewoMitarbeiterApp/app/src/main/java/soapConnection/Packet.java b/Android/BewoMitarbeiterApp/app/src/main/java/soapConnection/Packet.java index b3aeeaf52..5cb1c4615 100644 --- a/Android/BewoMitarbeiterApp/app/src/main/java/soapConnection/Packet.java +++ b/Android/BewoMitarbeiterApp/app/src/main/java/soapConnection/Packet.java @@ -93,7 +93,7 @@ public class Packet { IsDelivered = Integer.parseInt(actualMessage.substring(chatMessageLength + chatNameLength + 19, chatMessageLength + chatNameLength + 20)) != 0; } - public byte[] GetDataStream() { + public byte[] getDataStream() { String dataStreamAsString = "" + SenderPersonOid + ";" + RecipientPersonOid + ";" + ChatName.length() + ";" + ChatMessage.length()+ ";" + ChatDataIdentifier.getValue() + ";" + ServerseitigeOid + ";" + ClientseitigeOid + ";" + Tenant + ";"; @@ -104,13 +104,13 @@ public class Packet { try { blaaah = dataStreamAsString.getBytes("UTF8"); } catch (UnsupportedEncodingException e) { - Log.e("Packet.GetDataStream", e.getMessage()); + Log.e("Packet.getDataStream", e.getMessage()); } return blaaah; } - public void WriteToLog() { + public void writeToLog() { Log.i("PACKET_LOG", "SenderPersonOid: " + SenderPersonOid + "\nRecipientPersonOid: " + RecipientPersonOid + "\nChatDataIdentifier: " + ChatDataIdentifier.name() + "\nChatName: " + ChatName + "\nChatMessage: " + ChatMessage + "\nTenant: " + Tenant + "\nIsDelivered: " + IsDelivered + "\nMessageTimeStamp: " + MessageTimeStamp + "\nServerseitigeOid: " + ServerseitigeOid + "\nClientseitigeOid: " + ClientseitigeOid); } } diff --git a/Android/BewoMitarbeiterApp/app/src/main/java/tcpConnection/SocketManager.java b/Android/BewoMitarbeiterApp/app/src/main/java/tcpConnection/SocketManager.java new file mode 100644 index 000000000..bf35d4939 --- /dev/null +++ b/Android/BewoMitarbeiterApp/app/src/main/java/tcpConnection/SocketManager.java @@ -0,0 +1,217 @@ +package tcpConnection; + +import android.util.Log; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.util.Timer; +import java.util.TimerTask; + +import soapConnection.DataIdentifier; +import soapConnection.Packet; +import soapConnection.SoapCalls; + +/** + * Created by JettenM on 04.08.2016. + */ +public class SocketManager { + + private static final String TAG = "SOCKET_MANAGER"; + + public static String tenant; + public static Socket socket; + public static Thread listenerThread; + public static SocketListener socketListener; + public static UIHandler uiHandler; + public static DataOutputStream dataOutputStream; + + public static void connectToServer(){ + new Thread(new OpenConnection()).start(); + } + + public static void sendMessage(Packet packet) { + new Thread(new MessageSender(packet.getDataStream())).start(); + } + + public static class OpenConnection implements Runnable { + public void run() { + try { + socket = new Socket(SoapCalls.DESTINATION_ADDRESS, SoapCalls.DESTINATION_PORT); + + LoginOnServer(); + + dataOutputStream = new DataOutputStream(socket.getOutputStream()); + + socketListener = new SocketListener(); + listenerThread = new Thread(socketListener); + listenerThread.start(); + } catch(Exception exception) { + socket = null; + try { + new Timer().schedule(new TimerTask() { + @Override + public void run() { + connectToServer(); + } + }, 30000); + } catch(Exception exception2) { + exception2.printStackTrace(); + } + + exception.printStackTrace(); + } + } + } + + public static void closeSocketListener() { + if(listenerThread != null && listenerThread.isAlive()) { + try { + socketListener.terminate(); + listenerThread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public static void Logout() { + if(socket != null && socket.isConnected()) { + try { + Packet packet = new Packet(); + + packet.ChatDataIdentifier = DataIdentifier.LOGOUT; + + new Thread(new MessageSender(packet.getDataStream())).start(); + + } catch(Exception exception) { + exception.printStackTrace(); + } + } + } + + public static void LoginOnServer() { + if(socket != null && socket.isConnected()) { + try { + Packet packet = new Packet(); + packet.Tenant = tenant; + + packet.ChatDataIdentifier = DataIdentifier.LOGIN; + + new Thread(new MessageSender(packet.getDataStream())).start(); + + } catch(Exception exception) { + exception.printStackTrace(); + } + } + } + + public static class ConnectionClose implements Runnable{ + public void run(){ + try { + if (dataOutputStream != null) { + dataOutputStream.flush(); + dataOutputStream.close(); + } + + if (socket != null) { + socket.close(); + } + + }catch(IOException se){ + se.printStackTrace(); + } + } + } + + public static class MessageSender implements Runnable { + byte[] packetToSend; + + public MessageSender(byte[] packet2Send) { + packetToSend = packet2Send; + } + + @Override + public void run() { + if(!socket.isConnected()) { + Log.e("MESSAGE_SENDER", "Socket ist nicht verbunden"); + } + + if(socket != null && dataOutputStream != null) { + try { + dataOutputStream.write(packetToSend); + dataOutputStream.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public static class SocketListener implements Runnable { + + Packet chatPacket; + + private volatile boolean isRunning = true; + + public void terminate() { + isRunning = false; + } + + @Override + public void run() { + try { + while (isRunning) { + if (socket != null) { + if (socket.isConnected()) { + + InputStream stream = socket.getInputStream(); + + int size = stream.available(); + + if(size == 0) { + continue; + } + + StringBuilder total = new StringBuilder(stream.available()); + + int counter = 0; + + byte[] message = new byte[size]; + + stream.read(message, 0, size); + +// while(counter < size) { +// +// char c = (char) stream.read(); +// +// total.append(c); +// +// counter++; +// } + + try { + //chatPacket = new Packet(total.toString()); + chatPacket = new Packet(new String(message, "UTF-8")); + + Log.i(TAG, "Empfange Nachricht... " + chatPacket.ChatMessage); + + } catch(Exception e) { + e.printStackTrace(); + } + + if(uiHandler != null) { + uiHandler.updateUserInterface(chatPacket); + } else { + Log.e("ERROR_SOCKETLISTENER", "uiHandler ist null"); + } + } + } + } + } catch(Exception exception) { + exception.printStackTrace(); + } + } + } +} diff --git a/Android/BewoMitarbeiterApp/app/src/main/java/tcpConnection/UIHandler.java b/Android/BewoMitarbeiterApp/app/src/main/java/tcpConnection/UIHandler.java new file mode 100644 index 000000000..86a036b9f --- /dev/null +++ b/Android/BewoMitarbeiterApp/app/src/main/java/tcpConnection/UIHandler.java @@ -0,0 +1,10 @@ +package tcpConnection; + +import soapConnection.Packet; + +/** + * Created by JettenM on 04.08.2016. + */ +public interface UIHandler { + void updateUserInterface(Packet chatPacket); +} diff --git a/BeWoChatServer/Server.cs b/BeWoChatServer/Server.cs index fc849f947..19969c8b9 100644 --- a/BeWoChatServer/Server.cs +++ b/BeWoChatServer/Server.cs @@ -201,6 +201,8 @@ namespace BeWoChatServer { recipient.Key.Send(dataPacket.GetDataStream()); } + + // Oid zurückschicken } } finally diff --git a/BeWoOhneReports.v12.suo b/BeWoOhneReports.v12.suo index 94ee29ecf30f92ce18fbe17d52d024786f6bfeca..47e63d77944b6edd3e89632bb76dbe9d6cf92cd5 100644 GIT binary patch delta 2589 zcma)8eN0nV6o2>fUU|K=Sg-;j(^|2Bpp=a{=KNrM7~@3Chajkkir4Zp0Xjm0;$CXLu%aODF!ZEN0WiEHh?K6SKv{F{5?K5|y1(TV=|OH~DeyIrrRi z&(}M}y*t%?FtvU5oYy5u3IjYE9v%h~*mEjL`vC_4od6GjfKn5H$da@V_<5k)A^Osl z34QG~#6xQgcW%0`#SLli8VZS-q`>iH09YO7g<*+iD=-ff_yfzxi9ErY0XsZ?HAn-h zPTATxE+~iz((yo7z*STj3;RicR{&9fF#xnW1%U6p0l-kqKwkso10DjO3DgNF1?4oL zXbTtW;+hGva6q~w#Y;0_;{YrJ?_NzJtV`Z+NlW$cFT_S>N4TgeI;^qEu zYoiKx@bSk32y?>hZ69|0DDXV^0-1tz&$1* z;^`T0QO$hKCaXCXWF`RmF^R9*6w5EjY6=T#R)N*&u*V3oN@!^U&>E;R?jLqEjpY{Q zj>nXFG?P&+lU*SFk@bYvN40q@=lS`_VLli&NA!2m7#Wv?|AhtvPXo z266BmRl`a=ydTNmTiw!~Og+_ue2`{n{^}Wo+cO<%*$92DK642%OC=yGA0bwD0`r0P z&-8HGU!P0B$37#rbpy7(_Ap~A<@RxQxd^y`xAl3DP@2K(H@Hm2AjHlMIc+)sOb-B8 z|7H`Bv9OwDi-l;mtQ%q2Jfo*_F7+%Pe4MpXPzo8;{EJP*Q|c4$UY6Zm7D#!bO|#9v zuJrF*K=O;h(NKruoigC(!2PvAD*(#@alo$xY75#;4pP*|l>%KkzN5XutnP#je1u2$sT^cp^@bB9V0;{O8lm{rPF!R(&*BYsgUjPHpF!RswH8&)B#vr$@yp6mnMc z*P00@EAIX0`HnU@NlV&zg}h`tjx4A~6oT!IEus0NO{BlhM!79B`Pr6hBcA9%1^OUTUEy+r$ z;ltMER?^%`n_I)Yt@3`aL!sV@irUeh$P;H;WE`nl*39n+47V7(@mYAwsdNQk&`+g@ zmmJW~tXCh*^~x$0ci#?+6PE3&+;Z`ls$V>2bXvuc9dfO360$)2FktlSy=YSLyL{OI2Avw_Lr;^t<01@7c*Y@%x`f7pW7gZyBeF zNw>Vu3q3R$Xt z)y<}o<8PHQi?;{j>R8UEh{_GD`$^zVGBCh=RQL`uS;&XjS?(wr$CnnQ3g01?E%t3@ z3&r7bHb(5!qln}sFMY$9G^?)DP z0Mr2Y0K@V`sU_>gP!9{MJr3WqKX1SB*YixmA}IRQ?=pwDuAUQh**=F0qc2(TK`RnW z%W~y`s{?4>wV?L_!l${qz61ucxeQRUApXms%?QB%9#(0ed;FWP*z}Aalj zm}#UBlwu~HVlH3clQEN5ikb8xXmdSCL8dPVC`eLNgK7Xx-~=rNZU?NuYJd{eibbyk zrAm|mZN|G=0h{ENs<=?^GW`6fY%(ptL||#7gOL-;4LPSEWh~4!@#$@6?X-awhi}Bw z>@2)zT1=~DMm*qUMqcK7Y_g@6iQ4Qk5!gBycGnWiYDBNQ(avF?tR;D78^m>Y16i}x zqBpC-!O}2-GMAit2}OlIHF@HQdWXnxj@xN(kgwz@5qF+*Bzly^q||Z<9lP;aR;qQf zE>AV`3a>Dsx=-7wEQ^;R;_gOTga~9I{Le4Jl30TASy8I$3c<1;cnUZOJPjDGj1O3( zMUAXd!&Pr&QccMs2|9ShiwjlDnP0Ff?5yf*ljZrU$oG0|El?*HYy|cVGV%0A>{u=f zJ(hHhf;887EN`;OHB2+u_vct-y&BoVX#Xafuzl^*aOayg+L<@~#5+-z%36!E8B!5i z;T3_(a}L&w9_@_eI1$+1WT#GYE6s1|?Or4Af%hzO^1SXcT|eSpCWD8$U!Hl5r^}{S z`QEU9q2hi!laoi(3>2lrp}66i&O*|?sxk4nvmr49oz$e9&57&IIs3rEa1cdqh;Wy@ z6ysL&omaVsN&o9S77n)SZfrNQ+bA9huC#g3OXTuMu$_HHT<*&q9@4NmbsF*zl$l~U zR3Mr{+R_&EwgUIU`!>*AXkHB0#AN6&cZpxuIA!U6Zj-BC=gvqi`#*(!SPi$vS*^I< z+HAWeiaOD|?YVINw1eefj+@6caVz96?5Bdka8sRqEA(`vX_b^>>)lw;^B&`grhXP_ zSHs2AbViJL@hT=F_vCB;T{XHClhz`zdcJClOKE=rBgOz~a(GZHGK#t{vdJnt(qz;>9=>bekk^9*V&T>_B9tAOfIP<3-gTy+1EicF@8HJ6>bM0hC&A; zPiV(p38~?RbdjFl$tpx|exseTz4&@!<>ETToFzNd9B(eAHpZjz4~e7zjHsGpru`QvxV{2rb!NB^dG zPZjb@Xb3(m$&QGn$n(U9QtNPUqAgKiWAib7t4pD{q}NXDWEHiYkAR z^5a&S8RlLShxrXQRmIaXOxDIfVbKPj8SUn3vN_7R8 zDh#NEil(Vh)`utyqZPBTpfw=porD@<>oP- zE88PHTmCG@H_7@_Jirur>J-1vPUf_DYBCh#IY#m3876zidA6B7&i`zg`X!Gs+qGKy mD~BPkOdyEA{EEA$MtBS_rt13K^8d%AX8kn(u@*hU)&Bs>$&(@g