From bcd3c1de795bf756e6d9c47c1171816beb07cdae Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Mon, 3 Jan 2022 10:52:40 +0530 Subject: [PATCH] Implement IDELogService --- .../com/itsaky/androidide/EditorActivity.java | 1 + .../com/itsaky/androidide/app/StudioApp.java | 46 +++--- .../androidide/fragments/IDELogFragment.java | 7 + .../com/itsaky/androidide/models/LogLine.java | 42 +++++- .../androidide/services/IDELogService.java | 69 +++++++++ .../androidide/app/BaseApplication.java | 6 +- .../itsaky/androidide/shell/ShellServer.java | 141 +++++++++--------- 7 files changed, 202 insertions(+), 110 deletions(-) create mode 100644 app/src/main/java/com/itsaky/androidide/services/IDELogService.java diff --git a/app/src/main/java/com/itsaky/androidide/EditorActivity.java b/app/src/main/java/com/itsaky/androidide/EditorActivity.java index 56f4dabed..7f7a57829 100755 --- a/app/src/main/java/com/itsaky/androidide/EditorActivity.java +++ b/app/src/main/java/com/itsaky/androidide/EditorActivity.java @@ -205,6 +205,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F mPagerAdapter = new EditorPagerAdapter (getSupportFragmentManager (), this.mProject); mFileTreeFragment = FileTreeFragment.newInstance (this.mProject).setFileActionListener (this); mDaemonStatusFragment = new TextSheetFragment ().setTextSelectable (true); + ActionBarDrawerToggle toggle = new ActionBarDrawerToggle (this, mBinding.editorDrawerLayout, mBinding.editorToolbar, R.string.app_name, R.string.app_name); mBinding.editorDrawerLayout.addDrawerListener (toggle); mBinding.startNav.setNavigationItemSelectedListener (this); diff --git a/app/src/main/java/com/itsaky/androidide/app/StudioApp.java b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java index e338a8f7d..c3fb9664f 100755 --- a/app/src/main/java/com/itsaky/androidide/app/StudioApp.java +++ b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java @@ -20,6 +20,7 @@ package com.itsaky.androidide.app; import com.google.firebase.messaging.FirebaseMessaging; import com.itsaky.androidide.language.xml.completion.XMLCompletionService; import com.itsaky.androidide.project.ProjectResourceFinder; +import com.itsaky.androidide.services.IDELogService; import com.itsaky.androidide.services.MessagingService; import com.itsaky.inflater.ILayoutInflater; import com.itsaky.inflater.IResourceFinder; @@ -41,7 +42,8 @@ public class StudioApp extends BaseApplication { private static ApiInfo mApiInfo; private static AttrInfo mAttrInfo; private static WidgetInfo mWidgetInfo; - + + private IDELogService ideLogService; private IResourceFinder mResFinder; private XMLCompletionService mXmlCompletionService; @@ -53,29 +55,15 @@ public class StudioApp extends BaseApplication { @Override public void onCreate() { - this.instance = this; + instance = this; super.onCreate(); - + + this.ideLogService = new IDELogService (); + FirebaseMessaging.getInstance().subscribeToTopic(MessagingService.TOPIC_UPDATE); FirebaseMessaging.getInstance().subscribeToTopic(MessagingService.TOPIC_DEV_MSGS); - - initializeApiInformation(); - } - - private void handleLog(CharSequence seq) { - if(seq == null) - return; - String line = seq.toString(); - line = line.endsWith("\n") ? line : line + "\n"; - File log = new File(FileUtil.getExternalStorageDir(), "ide_xlog/idelog.txt"); - try { - FileOutputStream os = new FileOutputStream(log, true); - os.write(line.getBytes()); - os.close(); - } catch (IOException e) { - // ignored - } + initializeApiInformation(); } public void createInflater (LayoutInflaterConfiguration config) { @@ -100,6 +88,10 @@ public class StudioApp extends BaseApplication { return this.mResFinder; } + public IDELogService getIdeLogService () { + return ideLogService; + } + /** * Reads API version information from api-versions.xml */ @@ -126,15 +118,15 @@ public class StudioApp extends BaseApplication { } public ApiInfo apiInfo() { - return this.mApiInfo; + return mApiInfo; } public AttrInfo attrInfo () { - return this.mAttrInfo; + return mAttrInfo; } public WidgetInfo widgetInfo () { - return this.mWidgetInfo; + return mWidgetInfo; } public boolean isXmlServiceStarted() { @@ -144,12 +136,8 @@ public class StudioApp extends BaseApplication { public void setStopGradleDaemon(boolean startGradleDaemon) { this.stopGradleDaemon = startGradleDaemon; } - - public boolean isStopGradleDaemon() { - return stopGradleDaemon; - } - - public static StudioApp getInstance() { + + public static StudioApp getInstance() { return instance; } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/IDELogFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/IDELogFragment.java index 5e14e9ef3..5d137ecfb 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/IDELogFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/IDELogFragment.java @@ -17,8 +17,15 @@ package com.itsaky.androidide.fragments; +import com.itsaky.androidide.app.StudioApp; + public class IDELogFragment extends LogViewFragment { public IDELogFragment () { + final var logService = StudioApp.getInstance ().getIdeLogService (); + if (!logService.isStarted ()) { + logService.start (); + } + logService.addLogListener (this::appendLog); } } diff --git a/app/src/main/java/com/itsaky/androidide/models/LogLine.java b/app/src/main/java/com/itsaky/androidide/models/LogLine.java index e4644c4b4..5f35bbbfc 100755 --- a/app/src/main/java/com/itsaky/androidide/models/LogLine.java +++ b/app/src/main/java/com/itsaky/androidide/models/LogLine.java @@ -18,6 +18,8 @@ package com.itsaky.androidide.models; import android.content.Context; + +import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import com.itsaky.androidide.R; import java.util.Locale; @@ -25,6 +27,8 @@ import io.github.rosemoe.editor.widget.EditorColorScheme; public class LogLine { + public String unformatted; + public String date; public String time; public String pid; @@ -35,21 +39,28 @@ public class LogLine { public int priority; + public boolean formatted; + // It makes things easier in LogLanguageImpl public static final int INFO = EditorColorScheme.LOG_INFO; public static final int DEBUG = EditorColorScheme.LOG_DEBUG; public static final int ERROR = EditorColorScheme.LOG_ERROR; public static final int WARNING = EditorColorScheme.LOG_WARNING; - public LogLine(LogLine src ) { + public LogLine(@NonNull LogLine src ) { this(src.date, src.time, src.pid, src.tid, src.priorityChar, src.tag, src.message); } - public LogLine(String date, String time, String pid, String tid, String priorityChar, String tag) { - this(date, time, pid, tid, priorityChar, tag, ""); + public LogLine (String date, String time, String pid, String tid, String priorityChar, String tag, String message) { + this (date, time, pid, tid, priorityChar, tag, message, true); } - - public LogLine(String date, String time, String pid, String tid, String priorityChar, String tag, String message) { + + public LogLine (String unformatted) { + this.unformatted = unformatted; + this.formatted = false; + } + + public LogLine(String date, String time, String pid, String tid, String priorityChar, @NonNull String tag, String message, boolean formatted) { this.date = date; this.time = time; this.pid = pid; @@ -58,12 +69,31 @@ public class LogLine { this.tag = tag; this.message = message; this.priority = parsePriority(priorityChar); + this.formatted = formatted; if(tag.length() > 25) { this.tag = "...".concat(tag.substring(tag.length() - 25)); } } + @NonNull + public static LogLine forLogString (@NonNull final String log) { + try { + final var split = log.split ("\\s"); + return new LogLine( + split[0], // date + split[1], // time + split[2], // process id + split[3], // thread id + split[4], // priority + split[5], // tag + split[6] // message + ); + } catch (Throwable th) { + return new LogLine (log); + } + } + private int parsePriority(String s) { if(s == null || s.trim().length() > 1) { return DEBUG; @@ -94,6 +124,6 @@ public class LogLine { @Override public String toString() { - return String.format("%-6s %-13s %-6s %-6s %-2s %-40s %s", date, time, pid, tid, priorityChar, tag, message); + return this.formatted ? String.format("%-6s %-13s %-6s %-6s %-2s %-40s %s", date, time, pid, tid, priorityChar, tag, message) : this.unformatted; } } diff --git a/app/src/main/java/com/itsaky/androidide/services/IDELogService.java b/app/src/main/java/com/itsaky/androidide/services/IDELogService.java new file mode 100644 index 000000000..41fa57b55 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/services/IDELogService.java @@ -0,0 +1,69 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.services; + +import androidx.annotation.NonNull; + +import com.itsaky.androidide.app.StudioApp; +import com.itsaky.androidide.models.LogLine; +import com.itsaky.androidide.shell.ShellServer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class IDELogService { + + private final ShellServer shell; + private final List logListeners; + + private boolean started; + + public IDELogService () { + this.shell = StudioApp.getInstance ().newShell (this::output); + this.logListeners = new ArrayList<> (); + } + + public void start () { + if (started) { + return; + } + + this.shell.bgAppend ("logcat -v threadtime"); + started = true; + } + + public boolean isStarted () { + return started; + } + + public void addLogListener (LogReceiver.LogListener listener) { + this.logListeners.add (Objects.requireNonNull (listener)); + } + + public void removeLogListener (LogReceiver.LogListener listener) { + this.logListeners.remove (Objects.requireNonNull (listener)); + } + + private void output (@NonNull String log) { + final var line = LogLine.forLogString (log); + for (final var listener : this.logListeners) { + listener.appendLogLine (line); + } + } +} diff --git a/common/src/main/java/com/itsaky/androidide/app/BaseApplication.java b/common/src/main/java/com/itsaky/androidide/app/BaseApplication.java index bb595cdf5..8642a7dbd 100755 --- a/common/src/main/java/com/itsaky/androidide/app/BaseApplication.java +++ b/common/src/main/java/com/itsaky/androidide/app/BaseApplication.java @@ -122,7 +122,11 @@ public abstract class BaseApplication extends MultiDexApplication { } public ShellServer newShell(ShellServer.Callback callback, boolean redirectErrors) { - ShellServer shellServer = new ShellServer(callback, "sh", Environment.mkdirIfNotExits(getRootDir()).getAbsolutePath(), Environment.getEnvironment(true), redirectErrors); + ShellServer shellServer = new ShellServer(callback, + "sh", + Environment.mkdirIfNotExits(getRootDir()).getAbsolutePath(), + Environment.getEnvironment(true), + redirectErrors); shellServer.start(); return shellServer; } diff --git a/common/src/main/java/com/itsaky/androidide/shell/ShellServer.java b/common/src/main/java/com/itsaky/androidide/shell/ShellServer.java index bcb74f902..cb104e32e 100755 --- a/common/src/main/java/com/itsaky/androidide/shell/ShellServer.java +++ b/common/src/main/java/com/itsaky/androidide/shell/ShellServer.java @@ -1,24 +1,28 @@ /************************************************************************************ * This file is part of AndroidIDE. * - * + * * * AndroidIDE is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * AndroidIDE is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with AndroidIDE. If not, see . * -**************************************************************************************/ + **************************************************************************************/ package com.itsaky.androidide.shell; +import androidx.annotation.NonNull; + +import com.itsaky.androidide.utils.Logger; + import java.io.BufferedReader; import java.io.Closeable; import java.io.File; @@ -28,14 +32,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class ShellServer extends Thread { @@ -43,119 +40,115 @@ public class ShellServer extends Thread { private BufferedReader output; private Process process; - public ShellServer(Callback callback, String command, String dirPath, Map env, boolean redirectErrors) { + private static final Logger LOG = Logger.instance ("ShellServer"); + + public ShellServer (Callback callback, String command, String dirPath, Map env, boolean redirectErrors) { this.callback = callback; - ProcessBuilder processBuilder = new ProcessBuilder(new String[]{command}); - processBuilder.directory(new File(dirPath)); - processBuilder.redirectErrorStream(redirectErrors); - processBuilder.environment().putAll(env); + ProcessBuilder processBuilder = new ProcessBuilder (command); + processBuilder.directory (new File (dirPath)); + processBuilder.redirectErrorStream (redirectErrors); + processBuilder.environment ().putAll (env); try { - this.process = processBuilder.start(); - this.output = new BufferedReader(new InputStreamReader(process.getInputStream())); + this.process = processBuilder.start (); + this.output = new BufferedReader (new InputStreamReader (process.getInputStream ())); } catch (Throwable th) { if (callback != null) { - String out = getFullStackTrace(th).concat("\n"); - callback.output(out); + String out = getFullStackTrace (th).concat ("\n"); + callback.output (out); } } } - public InputStream getProcessInputStream() { - return process.getInputStream(); + public InputStream getProcessInputStream () { + return process.getInputStream (); } - public OutputStream getProcessOutputStream() { - return process.getOutputStream(); + public OutputStream getProcessOutputStream () { + return process.getOutputStream (); } - - public void append(String str) { - append(str, true); + + public void append (String str) { + append (str, true); } - - public ShellServer bgAppend(String str) { - append(str, false); - return this; - } - - public ShellServer bgAppend(String... arr) { - for (String s : arr) - bgAppend(s); - return this; - } - - public void append(String str, boolean z) { + + public void bgAppend (String str) { + append (str, false); + } + + public void append (String str, boolean z) { if (z) { try { - if (str.endsWith("\n")) { - output(str); + if (str.endsWith ("\n")) { + output (str); } else { - output(str.concat("\n")); + output (str.concat ("\n")); } } catch (Throwable th) { - output(th.toString().concat("\n")); + output (th.toString ().concat ("\n")); return; } } try { - this.process.getOutputStream().write(str.concat("\n").getBytes()); - this.process.getOutputStream().flush(); - } catch (Throwable e) { - // Ignored - } + this.process.getOutputStream ().write (str.concat ("\n").getBytes ()); + this.process.getOutputStream ().flush (); + } catch (Throwable e) { + LOG.error ("Unable to write to shell server", e); + } } - - public void append(String... strArr) { + + public void append (@NonNull String... strArr) { for (String append : strArr) { - append(append); + append (append); } } - - public void exit() { - append("exit"); + + public void exit () { + append ("exit"); } - - public void output(String str) { + + public void output (String str) { if (this.callback != null) { - this.callback.output(str); + this.callback.output (str); } } - + @Override - public void run() { + public void run () { while (true) { try { - String readLine = this.output.readLine(); + String readLine = this.output.readLine (); if (readLine == null) { break; } - output(readLine.concat("\n")); + output (readLine.concat ("\n")); } catch (Throwable th) { - output(th.toString().concat("\n")); + output (th.toString ().concat ("\n")); } } - closeIOQuietly(new Closeable[]{this.output, this.process.getInputStream(), this.process.getErrorStream(), this.process.getOutputStream()}); - this.process.destroy(); + closeIOQuietly (this.output, this.process.getInputStream (), this.process.getErrorStream (), this.process.getOutputStream ()); + this.process.destroy (); } - private String getFullStackTrace (Throwable th) { + @NonNull + private String getFullStackTrace (@NonNull Throwable th) { StringWriter sw = new StringWriter (); - th.printStackTrace( new PrintWriter (sw)); - return sw.toString(); + th.printStackTrace (new PrintWriter (sw)); + return sw.toString (); } - private void closeIOQuietly (Closeable ... toClose) { + private void closeIOQuietly (@NonNull Closeable... toClose) { for (Closeable c : toClose) { try { - c.close(); + c.close (); } catch (IOException e) { // ignored } } } - - public interface Callback { - public void output(CharSequence charSequence); - } + + public interface Callback { + void output (String charSequence); + } }