move log shipment to own plugin

This commit is contained in:
Markus M. May 2018-07-25 22:00:58 +02:00
parent 60721c8395
commit c34104b906
11 changed files with 444 additions and 199 deletions

View file

@ -399,24 +399,6 @@ public class MainActivity extends AppCompatActivity {
return true; return true;
case R.id.nav_show_logcat: case R.id.nav_show_logcat:
LogDialog.showLogcat(this); LogDialog.showLogcat(this);
return true;
case R.id.nav_export_log:
String recipient = "mmay@gmx.net";
String logDirectory = LoggerUtils.getLogDirectory();
List<File> logs = LoggerUtils.getLogfiles(logDirectory, 2);
File zipDir = this.getExternalFilesDir("exports");
File zipFile = new File(zipDir, LoggerUtils.constructName());
log.debug("zipFile: {}", zipFile.getAbsolutePath());
File zip = LoggerUtils.zipLogs(zipFile, logs);
Uri attachementUri = FileProvider.getUriForFile(this, "info.nightscout.androidaps.fileprovider", zip);
Intent emailIntent = LoggerUtils.sendMail(attachementUri, recipient, "Log Export");
log.debug("sending emailIntent");
startActivity(emailIntent);
return true; return true;
case R.id.nav_about: case R.id.nav_about:
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog.Builder builder = new AlertDialog.Builder(this);

View file

@ -41,6 +41,7 @@ import info.nightscout.androidaps.plugins.Insulin.InsulinOrefFreePeakPlugin;
import info.nightscout.androidaps.plugins.Insulin.InsulinOrefRapidActingPlugin; import info.nightscout.androidaps.plugins.Insulin.InsulinOrefRapidActingPlugin;
import info.nightscout.androidaps.plugins.Insulin.InsulinOrefUltraRapidActingPlugin; import info.nightscout.androidaps.plugins.Insulin.InsulinOrefUltraRapidActingPlugin;
import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin;
import info.nightscout.androidaps.plugins.LogShipper.LogShipperPlugin;
import info.nightscout.androidaps.plugins.Loop.LoopPlugin; import info.nightscout.androidaps.plugins.Loop.LoopPlugin;
import info.nightscout.androidaps.plugins.NSClientInternal.NSClientPlugin; import info.nightscout.androidaps.plugins.NSClientInternal.NSClientPlugin;
import info.nightscout.androidaps.plugins.NSClientInternal.receivers.AckAlarmReceiver; import info.nightscout.androidaps.plugins.NSClientInternal.receivers.AckAlarmReceiver;
@ -190,6 +191,7 @@ public class MainApp extends Application {
pluginsList.add(StatuslinePlugin.initPlugin(this)); pluginsList.add(StatuslinePlugin.initPlugin(this));
pluginsList.add(PersistentNotificationPlugin.getPlugin()); pluginsList.add(PersistentNotificationPlugin.getPlugin());
pluginsList.add(NSClientPlugin.getPlugin()); pluginsList.add(NSClientPlugin.getPlugin());
pluginsList.add(LogShipperPlugin.initPlugin(this));
pluginsList.add(sConfigBuilder = ConfigBuilderPlugin.getPlugin()); pluginsList.add(sConfigBuilder = ConfigBuilderPlugin.getPlugin());

View file

@ -0,0 +1,38 @@
package info.nightscout.androidaps.plugins.LogShipper;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import info.nightscout.androidaps.R;
/**
*
*/
public class LogShipperFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.logshipper_fragment, container, false);
view.findViewById(R.id.log_send).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
LogShipperPlugin.getPlugin().sendLogs();
}
});
view.findViewById(R.id.log_delete).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
LogShipperPlugin.getPlugin().deleteLogs();
}
});
return view;
}
}

View file

@ -0,0 +1,276 @@
package info.nightscout.androidaps.plugins.LogShipper;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.v4.content.FileProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.interfaces.PluginBase;
import info.nightscout.androidaps.interfaces.PluginDescription;
import info.nightscout.androidaps.interfaces.PluginType;
import info.nightscout.utils.LoggerUtils;
import info.nightscout.utils.SP;
public class LogShipperPlugin extends PluginBase {
private static final Logger LOG = LoggerFactory.getLogger(LogShipperPlugin.class);
private final Context ctx;
private static LogShipperPlugin logShipperPlugin;
public static LogShipperPlugin getPlugin() {
return logShipperPlugin;
}
public static LogShipperPlugin initPlugin(Context ctx) {
if (logShipperPlugin == null) {
logShipperPlugin = new LogShipperPlugin(ctx);
}
return logShipperPlugin;
}
public LogShipperPlugin() {
// required for testing
super(null);
this.ctx = null;
}
LogShipperPlugin(Context ctx) {
super(new PluginDescription()
.mainType(PluginType.GENERAL)
.fragmentClass(LogShipperFragment.class.getName())
.alwayVisible(true)
.alwaysEnabled(true)
.pluginName(R.string.logshipper)
.shortName(R.string.logship_shortname)
.preferencesId(R.xml.pref_logshipper)
.description(R.string.description_logship)
);
this.ctx = ctx;
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onStop() {
}
public void sendLogs() {
String recipient = SP.getString("key_logshipper_email", "logs@androidaps.org");
int amount = SP.getInt("key_logshipper_amount", 2);
String logDirectory = LoggerUtils.getLogDirectory();
List<File> logs = this.getLogfiles(logDirectory, amount);
File zipDir = this.ctx.getExternalFilesDir("exports");
File zipFile = new File(zipDir, this.constructName());
LOG.debug("zipFile: {}", zipFile.getAbsolutePath());
File zip = this.zipLogs(zipFile, logs);
Uri attachementUri = FileProvider.getUriForFile(this.ctx, "info.nightscout.androidaps.fileprovider", zip);
Intent emailIntent = this.sendMail(attachementUri, recipient, "Log Export");
LOG.debug("sending emailIntent");
ctx.startActivity(emailIntent);
}
public void deleteLogs() {
String logDirectory = LoggerUtils.getLogDirectory();
File logDir = new File(logDirectory);
File[] files = logDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File file, String name) {
return name.startsWith("AndroidAPS")
&& name.endsWith(".zip");
}
});
Arrays.sort(files, new Comparator<File>() {
public int compare(File f1, File f2) {
return f1.getName().compareTo(f2.getName());
}
});
List<File> delFiles = Arrays.asList(files);
int amount = SP.getInt("key_logshipper_amount", 2);
int keepIndex = amount - 1;
if (keepIndex < delFiles.size()) {
delFiles = delFiles.subList(keepIndex, delFiles.size());
for (File file : delFiles) {
file.delete();
}
}
File exportDir = new File(logDirectory, "exports");
if (exportDir.exists()) {
File[] expFiles = exportDir.listFiles();
for (File file : expFiles) {
file.delete();
}
exportDir.delete();
}
}
/**
* returns a list of log files. The number of returned logs is given via the amount
* parameter. The log files are sorted by the name descending.
*
* @param directory
* @param amount
* @return
*/
public List<File> getLogfiles(String directory, int amount) {
LOG.debug("getting {} logs from directory {}", amount, directory);
File logDir = new File(directory);
File[] files = logDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File file, String name) {
return name.startsWith("AndroidAPS")
&& (name.endsWith(".log")
|| (name.endsWith(".zip") && !name.endsWith(LoggerUtils.SUFFIX)));
}
});
Arrays.sort(files, new Comparator<File>() {
public int compare(File f1, File f2) {
return f2.getName().compareTo(f1.getName());
}
});
List<File> result = Arrays.asList(files);
int toIndex = amount++;
if (toIndex > result.size()) {
toIndex = result.size();
}
LOG.debug("returning sublist 0 to {}", toIndex);
return result.subList(0, toIndex);
}
/**
* Zips the given files in a zipfile which is stored in the given zipDir using the givven
* name.
*
* @param zipFile
* @param files
* @return
*/
public File zipLogs(File zipFile, List<File> files) {
LOG.debug("creating zip {}", zipFile.getAbsolutePath());
try {
zip(zipFile, files);
} catch (IOException e) {
LOG.error("Cannot retrieve zip", e);
}
return zipFile;
}
/**
* construct the name of zip file which is used to export logs.
*
* The name is constructed using the following scheme:
* AndroidAPS_LOG_ + Long Time + .log.zip
*
* @return
*/
public String constructName() {
return "AndroidAPS_LOG_" + String.valueOf(new Date().getTime()) + LoggerUtils.SUFFIX;
}
/**
* This method stores all given files inside the given zipFile.
*
* @param zipFile
* @param files
* @throws IOException
*/
public void zip(File zipFile, List<File> files) throws IOException {
final int BUFFER_SIZE = 2048;
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));
for (File file : files) {
byte data[] = new byte[BUFFER_SIZE];
try(FileInputStream fileInputStream = new FileInputStream( file )) {
try(BufferedInputStream origin = new BufferedInputStream(fileInputStream, BUFFER_SIZE)) {
ZipEntry entry = new ZipEntry(file.getName());
out.putNextEntry(entry);
int count;
while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) {
out.write(data, 0, count);
}
}
}
}
out.close();
}
/**
* send a mail with the given file to the recipients with the given subject.
*
* the returned intent should be used to really send the mail using
*
* startActivity(Intent.createChooser(emailIntent , "Send email..."));
*
* @param attachementUri
* @param recipient
* @param subject
* @return
*/
public static Intent sendMail(Uri attachementUri, String recipient, String subject) {
LOG.debug("sending email to {} with subject {}", recipient, subject);
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("text/plain");
emailIntent.putExtra(Intent.EXTRA_EMAIL , new String[]{recipient});
emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
emailIntent.putExtra(Intent.EXTRA_TEXT, "");
LOG.debug("put path {}", attachementUri.toString());
emailIntent.putExtra(Intent.EXTRA_STREAM, attachementUri);
emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return emailIntent;
}
}

View file

@ -1,25 +1,8 @@
package info.nightscout.utils; package info.nightscout.utils;
import android.content.Intent;
import android.net.Uri;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.LoggerContext;
/** /**
@ -29,7 +12,7 @@ public class LoggerUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggerUtils.class); private static final Logger LOGGER = LoggerFactory.getLogger(LoggerUtils.class);
private static String SUFFIX = ".log.zip"; public static String SUFFIX = ".log.zip";
/** /**
* Returns the directory, in which the logs are stored on the system. This is configured in the * Returns the directory, in which the logs are stored on the system. This is configured in the
@ -42,136 +25,4 @@ public class LoggerUtils {
return lc.getProperty("EXT_FILES_DIR"); return lc.getProperty("EXT_FILES_DIR");
} }
/**
* returns a list of log files. The number of returned logs is given via the amount
* parameter. The log files are sorted by the name descending.
*
* @param directory
* @param amount
* @return
*/
public static List<File> getLogfiles(String directory, int amount) {
LOGGER.debug("getting {} logs from directory {}", amount, directory);
File logDir = new File(directory);
File[] files = logDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File file, String name) {
return name.startsWith("AndroidAPS")
&& (name.endsWith(".log")
|| (name.endsWith(".zip") && !name.endsWith(SUFFIX)));
}
});
Arrays.sort(files, new Comparator<File>() {
public int compare(File f1, File f2) {
return f2.getName().compareTo(f1.getName());
}
});
List<File> result = Arrays.asList(files);
int toIndex = amount++;
if (toIndex > result.size()) {
toIndex = result.size();
}
LOGGER.debug("returning sublist 0 to {}", toIndex);
return result.subList(0, toIndex);
}
/**
* Zips the given files in a zipfile which is stored in the given zipDir using the givven
* name.
*
* @param zipFile
* @param files
* @return
*/
public static File zipLogs(File zipFile, List<File> files) {
LOGGER.debug("creating zip {}", zipFile.getAbsolutePath());
try {
zip(zipFile, files);
} catch (IOException e) {
LOGGER.error("Cannot retrieve zip", e);
}
return zipFile;
}
/**
* construct the name of zip file which is used to export logs.
*
* The name is constructed using the following scheme:
* AndroidAPS_LOG_ + Long Time + .log.zip
*
* @return
*/
public static String constructName() {
return "AndroidAPS_LOG_" + String.valueOf(new Date().getTime()) + SUFFIX;
}
/**
* This method stores all given files inside the given zipFile.
*
* @param zipFile
* @param files
* @throws IOException
*/
public static void zip(File zipFile, List<File> files) throws IOException {
final int BUFFER_SIZE = 2048;
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));
for (File file : files) {
byte data[] = new byte[BUFFER_SIZE];
try(FileInputStream fileInputStream = new FileInputStream( file )) {
try(BufferedInputStream origin = new BufferedInputStream(fileInputStream, BUFFER_SIZE)) {
ZipEntry entry = new ZipEntry(file.getName());
out.putNextEntry(entry);
int count;
while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) {
out.write(data, 0, count);
}
}
}
}
out.close();
}
/**
* send a mail with the given file to the recipients with the given subject.
*
* the returned intent should be used to really send the mail using
*
* startActivity(Intent.createChooser(emailIntent , "Send email..."));
*
* @param attachementUri
* @param recipient
* @param subject
* @return
*/
public static Intent sendMail(Uri attachementUri, String recipient, String subject) {
LOGGER.debug("sending email to {} with subject {}", recipient, subject);
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("text/plain");
emailIntent.putExtra(Intent.EXTRA_EMAIL , new String[]{recipient});
emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
emailIntent.putExtra(Intent.EXTRA_TEXT, "");
LOGGER.debug("put path {}", attachementUri.toString());
emailIntent.putExtra(Intent.EXTRA_STREAM, attachementUri);
emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return emailIntent;
}
} }

View file

@ -0,0 +1,41 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="2dp"
tools:context="info.nightscout.androidaps.plugins.LogShipper.LogShipperFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:id="@+id/log_send"
style="?android:attr/buttonStyle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="3dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="3dp"
android:layout_weight="0.5"
android:text="@string/send_all_logs"
android:textColor="@color/colorTreatmentButton" />
<Button
android:id="@+id/log_delete"
style="?android:attr/buttonStyle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="3dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="3dp"
android:layout_weight="0.5"
android:text="@string/delete_logs"
android:textColor="@color/colorTreatmentButton" />
</LinearLayout>
</FrameLayout>

View file

@ -34,10 +34,6 @@
android:id="@+id/nav_show_logcat" android:id="@+id/nav_show_logcat"
app:showAsAction="never" app:showAsAction="never"
android:title="@string/nav_show_logcat" /> android:title="@string/nav_show_logcat" />
<item
android:id="@+id/nav_export_log"
app:showAsAction="never"
android:title="@string/nav_export_log" />
<item <item
android:id="@+id/nav_about" android:id="@+id/nav_about"
app:showAsAction="never" app:showAsAction="never"

View file

@ -1165,6 +1165,18 @@
<string name="storedsettingsfound">Stored settings found</string> <string name="storedsettingsfound">Stored settings found</string>
<string name="allow_hardware_pump_text">Attention: If you activate and connect to a hardware pump, AndroidAPS will copy the basal settings from the profile to the pump, overwriting the existing basal rate stored on the pump. Make sure you have the correct basal setting in AndroidAPS. If you are not sure or don\'t want to overwrite the basal settings on your pump, press cancel and repeat switching to the pump at a later time.</string> <string name="allow_hardware_pump_text">Attention: If you activate and connect to a hardware pump, AndroidAPS will copy the basal settings from the profile to the pump, overwriting the existing basal rate stored on the pump. Make sure you have the correct basal setting in AndroidAPS. If you are not sure or don\'t want to overwrite the basal settings on your pump, press cancel and repeat switching to the pump at a later time.</string>
<string name="error_adding_treatment_title">Treatment data incomplete</string> <string name="error_adding_treatment_title">Treatment data incomplete</string>
<string name="logshipper_settings">Log Shipment Settings</string>
<string name="logshipper_email">Email</string>
<string name="key_logshipper_email" translatable="false">email</string>
<string name="invalid_email_message">Invalid Email</string>
<string name="key_logshipper_amount" translatable="false">amount of logs to send</string>
<string name="logshipper_amount">No of Logs to send</string>
<string name="logshipper">Log Shipment</string>
<string name="logship_shortname">LOG</string>
<string name="description_logship">Send Logs via Email for support requests. Delete logs if wanted.</string>
<string name="send_all_logs">Send Logs by Email</string>
<string name="delete_logs">Delete Logs</string>
<!-- TODO convert to proper style --> <!-- TODO convert to proper style -->
<string name="error_adding_treatment_message" formatted="false">A treatment (insulin: %.2f, carbs: %d, at: %s) could not be added to treatments. Please check and manually add a record as appropriate.</string> <string name="error_adding_treatment_message" formatted="false">A treatment (insulin: %.2f, carbs: %d, at: %s) could not be added to treatments. Please check and manually add a record as appropriate.</string>
<string name="generated_ecarbs_note">Generated eCarbs with amount: %1$dg, duration: %2$dh, delay: %3$dm</string> <string name="generated_ecarbs_note">Generated eCarbs with amount: %1$dg, duration: %2$dh, delay: %3$dm</string>

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:validatingEditText="http://schemas.android.com/apk/res/info.nightscout.androidaps">
<PreferenceCategory
android:key="logshipper"
android:title="@string/logshipper_settings">
<com.andreabaccega.widget.ValidatingEditTextPreference
android:defaultValue="logs@androidaps.org"
android:dialogMessage="@string/logshipper_email"
android:inputType="textEmailAddress"
android:key="@string/key_logshipper_email"
android:selectAllOnFocus="true"
android:title="@string/nsclientinternal_url_title"
validatingEditText:emptyAllowed="false"
validatingEditText:testErrorString="@string/invalid_email_message"
validatingEditText:testType="email"/>
<EditTextPreference
android:defaultValue="2"
android:inputType="number"
android:key="@string/key_logshipper_amount"
android:title="@string/logshipper_amount">
</EditTextPreference>
</PreferenceCategory>
</PreferenceScreen>

View file

@ -0,0 +1,43 @@
package info.nightscout.androidaps.plugins.LogShipper;
import org.junit.Test;
import java.io.File;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class LogShipperPluginTest {
private LogShipperPlugin sut = new LogShipperPlugin();
@Test
public void getLogfilesTest() {
String logDirectory = "src/test/res/logger";
List<File> logs = sut.getLogfiles(logDirectory, 2);
assertEquals(2, logs.size());
assertEquals("AndroidAPS.log", logs.get(0).getName());
assertEquals("AndroidAPS.2018-01-03_01-01-00.1.zip", logs.get(1).getName());
logs = sut.getLogfiles(logDirectory, 10);
assertEquals(4, logs.size());
}
@Test
public void zipLogsTest() {
String logDirectory = "src/test/res/logger";
List<File> logs = sut.getLogfiles(logDirectory, 2);
String name = "AndroidAPS.log.zip";
File zipFile = new File("build/" + name);
zipFile = sut.zipLogs(zipFile, logs);
assertTrue(zipFile.exists());
assertTrue(zipFile.isFile());
}
}

View file

@ -10,32 +10,5 @@ import static org.junit.Assert.assertTrue;
public class LoggerUtilsTest { public class LoggerUtilsTest {
@Test
public void getLogfilesTest() {
String logDirectory = "src/test/res/logger";
List<File> logs = LoggerUtils.getLogfiles(logDirectory, 2);
assertEquals(2, logs.size());
assertEquals("AndroidAPS.log", logs.get(0).getName());
assertEquals("AndroidAPS.2018-01-03_01-01-00.1.zip", logs.get(1).getName());
logs = LoggerUtils.getLogfiles(logDirectory, 10);
assertEquals(4, logs.size());
}
@Test
public void zipLogsTest() {
String logDirectory = "src/test/res/logger";
List<File> logs = LoggerUtils.getLogfiles(logDirectory, 2);
String name = "AndroidAPS.log.zip";
File zipFile = new File("build/" + name);
zipFile = LoggerUtils.zipLogs(zipFile, logs);
assertTrue(zipFile.exists());
assertTrue(zipFile.isFile());
}
} }