petr.m...@gmail.com
unread,Oct 6, 2025, 4:05:06 AMOct 6Sign in to reply to author
Sign in to forward
You do not have permission to delete messages in this group
Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message
to Epsilon Notes
# Logging file saving
## Analysis of saving in Epsilon Notes
### 1. Main mechanism
- **File:** `enotes1/l1/g.smali`
- Class **`com.wraith.enotes1.l1.g`**, which represents a note file.
- Method **`G(Context, String, boolean)`** is the one that **physically writes the content to disk** (using `FileWriter` + `BufferedWriter`).
- Before the actual writing, it calls **`c(g)`**, which decides whether it is a regular file or SAF (Storage Access Framework).
- `c(g)` returns `true` if the object `g` has an assigned **SAF-URI**, otherwise `false`.
- `false` → normal write using `FileWriter`
- `true` → redirected to `l1/h.d(...)` = write using SAF
Short smali excerpt:
```smali
.method public G(Landroid/content/Context;Ljava/lang/String;Z)V
.locals 5
invoke-static {p0}, Lcom/wraith/enotes1/l1/g;->c(Lcom/wraith/enotes1/l1/g;)Z
move-result v0
if-eqz v0, :cond_saf
new-instance v1, Ljava/io/FileWriter;
invoke-direct {v1, v0}, Ljava/io/FileWriter;-><init>(Ljava/io/File;)V
new-instance v2, Ljava/io/BufferedWriter;
invoke-direct {v2, v1}, Ljava/io/BufferedWriter;-><init>(Ljava/io/Writer;)V
invoke-virtual {v2, p2}, Ljava/io/BufferedWriter;->write(Ljava/lang/String;)V
...
invoke-virtual {v2}, Ljava/io/BufferedWriter;->close()V
return-void
:cond_saf
invoke-static {p1, p0, p2, p3}, Lcom/wraith/enotes1/l1/h;->d(Landroid/content/Context;Ljava/io/File;Ljava/lang/String;Z)V
return-void
.end method
```
➡️ **This is the place where the change log should be inserted.**
### 2. Manual saving (button/menu)
- **File:** `enotes1/edit/EditActivity.smali`
- In the activity `EditActivity`, there is a method `w(String)`, which is called when pressing Save.
- It creates a new object `l1/g` and calls `F(...)`, which only redirects to `G(...)`.
```smali
.method private w(Ljava/lang/String;)V
...
new-instance v0, Lcom/wraith/enotes1/l1/g;
...
invoke-virtual {v0, p0, p1}, Lcom/wraith/enotes1/l1/g;->F(Landroid/content/Context;Ljava/lang/String;)V
...
.end method
```
### 3. Automatic saving
- **File:** `enotes1/edit/EditActivity.smali`
- The methods `finish()` and `onPause()` call the private method `v()`.
- `v()` then calls `w(...)`, and thus the note is saved when closing the editor or when the app goes into the background.
### 4. Saving via SAF
- If the file is saved through system dialogs (e.g. SD card, cloud), class **`l1/h`** is used:
- Method `d(Context, File, String, boolean)`
- Method `e(Context, File, byte[], boolean)`
- Instead of `FileWriter`, these call `ContentResolver.openOutputStream()`.
- The calls to these methods originate directly inside `l1/g.G(...)`, if `c(g)` returns `true`.
```smali
.method static d(Landroid/content/Context;Ljava/io/File;Ljava/lang/String;Z)V
...
invoke-virtual {v0, v1}, Landroid/content/ContentResolver;->openOutputStream(Landroid/net/Uri;)Ljava/io/OutputStream;
...
.end method
```
### 5. Summary
- **Manual saving**
File `EditActivity.smali` → method `w()` → `l1/g.F()` → `l1/g.G()`
- **Automatic saving**
File `EditActivity.smali` → `onPause()/finish()` → `v()` → `w()` → `l1/g.F()` → `l1/g.G()`
- **Special cases (SAF)**
File `l1/g.smali` → `c(g)` → if `true` → calls `l1/h.d()` or `l1/h.e()`
## Recommended insertion for logging
It is enough to insert code for writing to the log into the **end of the method `g.G(...)`** (file `l1/g.smali`)
and optionally also into `h.d/e(...)` (file `l1/h.smali`).
Example smali insertion (writing to `changes.log`):
```smali
const-string v0, "/storage/emulated/0/Documents/EpsilonNotes/changes.log"
new-instance v1, Ljava/io/FileWriter;
invoke-direct {v1, v0, 0x1}, Ljava/io/FileWriter;-><init>(Ljava/lang/String;Z)V # append = true
new-instance v2, Ljava/io/BufferedWriter;
invoke-direct {v2, v1}, Ljava/io/BufferedWriter;-><init>(Ljava/io/Writer;)V
const-string v3, "NOTE SAVED at "
invoke-virtual {v2, v3}, Ljava/io/BufferedWriter;->write(Ljava/lang/String;)V
invoke-virtual {v2}, Ljava/io/BufferedWriter;->close()V
```
➡️ This block is inserted **before `return-void`**.
### 7. Result
- Every save (manual and automatic) will be recorded in `changes.log`.
- Thanks to the method `c(g)`, the log will be written regardless of whether it is a regular write or via SAF.
- This gives you a simple changelog that can be shared with children.
## Alternative solution: separate method for logging
Instead of inserting the logging code directly into `G(...)`, it is possible to create a **separate method** (e.g. `logChange`) in the class `l1/g` and call it from `G(...)`.
Advantage: the logging code is in one place and easy to modify or extend.
### Proposal for a new method in `l1/g.smali`
```smali
.method private static logChange(Ljava/lang/String;)V
.locals 4
:try_start
const-string v0, "/storage/emulated/0/Documents/EpsilonNotes/changes.log"
new-instance v1, Ljava/io/FileWriter;
const/4 v2, 0x1 # append = true
invoke-direct {v1, v0, v2}, Ljava/io/FileWriter;-><init>(Ljava/lang/String;Z)V
new-instance v3, Ljava/io/BufferedWriter;
invoke-direct {v3, v1}, Ljava/io/BufferedWriter;-><init>(Ljava/io/Writer;)V
# prefix
const-string v0, "SAVED: "
invoke-virtual {v3, v0}, Ljava/io/BufferedWriter;->write(Ljava/lang/String;)V
# write parameter (e.g. file path)
invoke-virtual {v3, p0}, Ljava/io/BufferedWriter;->write(Ljava/lang/String;)V
# line end
const-string v0, "\n"
invoke-virtual {v3, v0}, Ljava/io/BufferedWriter;->write(Ljava/lang/String;)V
invoke-virtual {v3}, Ljava/io/BufferedWriter;->close()V
:try_end
.catch Ljava/io/IOException; {:try_start .. :try_end} :catch
return-void
:catch
move-exception v0
# ignore exception
return-void
.end method
```
### Modification in `G(...)`
At the end of the method `G(...)`, before `return-void`, insert a simple call:
```smali
invoke-virtual {p0}, Ljava/io/File;->getAbsolutePath()Ljava/lang/String;
move-result-object v0
invoke-static {v0}, Lcom/wraith/enotes1/l1/g;->logChange(Ljava/lang/String;)V
```
### Advantages
- Logging is separate and clear.
- Possible changes (different message format, timestamp, etc.) are done only in the method `logChange()`.
## Extended version of logging: hierarchical log search
### Principle
1. Get the directory where the saved file is located (`p0` → `getParentFile()`, where `p0` is the saved file object).
2. Walk upward through parent directories toward the root:
- In each, check for a file **`epsilon-notes.log`**.
- If it exists → use it for writing and stop.
3. If no log is found in parent directories:
- Get *Epsilon Home* (`preferences/a->getHomePath()` - the new function created when implementing the `:en-home:` feature).
- If a log exists there → use it.
- If not → create it.
4. If *Home* is not found (e.g. empty setting) → fallback `/storage/emulated/0`.
### Modified proposal for the method `logChange`
```smali
.method private static logChange(Landroid/content/Context;Ljava/lang/String;)V
.locals 10
:try_start
# ----------------------------------------------------------------------
# 1. Get the directory of the file
# ----------------------------------------------------------------------
new-instance v0, Ljava/io/File;
invoke-direct {v0, p1}, Ljava/io/File;-><init>(Ljava/lang/String;)V
invoke-virtual {v0}, Ljava/io/File;->getParentFile()Ljava/io/File;
move-result-object v1
# Initialize variable for log file
const/4 v2, 0x0 # logFile = null
# ----------------------------------------------------------------------
# 2. Walk through parent directories
# ----------------------------------------------------------------------
:loop_dirs
if-eqz v1, :cond_check_home
new-instance v3, Ljava/io/File;
const-string v4, "epsilon-notes.log"
invoke-direct {v3, v1, v4}, Ljava/io/File;-><init>(Ljava/io/File;Ljava/lang/String;)V
invoke-virtual {v3}, Ljava/io/File;->exists()Z
move-result v5
if-eqz v5, :cond_parent
move-object v2, v3
goto :found_log
:cond_parent
invoke-virtual {v1}, Ljava/io/File;->getParentFile()Ljava/io/File;
move-result-object v1
goto :loop_dirs
# ----------------------------------------------------------------------
# 3. No log found – check HOME
# ----------------------------------------------------------------------
:cond_check_home
invoke-static {}, Lcom/wraith/enotes1/preferences/a;->U()Lcom/wraith/enotes1/preferences/a;
move-result-object v6
invoke-virtual {v6}, Lcom/wraith/enotes1/preferences/a;->getHomePath()Ljava/lang/String;
move-result-object v7
if-eqz v7, :cond_fallback
const-string v8, ""
invoke-virtual {v7, v8}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
move-result v9
if-nez v9, :cond_fallback
new-instance v2, Ljava/io/File;
const-string v8, "epsilon-notes.log"
invoke-direct {v2, v7, v8}, Ljava/io/File;-><init>(Ljava/lang/String;Ljava/lang/String;)V
goto :found_log
# ----------------------------------------------------------------------
# 4. Fallback: /storage/emulated/0/epsilon-notes.log
# ----------------------------------------------------------------------
:cond_fallback
new-instance v2, Ljava/io/File;
const-string v7, "/storage/emulated/0/epsilon-notes.log"
invoke-direct {v2, v7}, Ljava/io/File;-><init>(Ljava/lang/String;)V
# ----------------------------------------------------------------------
# 5. Write to the found/created log
# ----------------------------------------------------------------------
:found_log
new-instance v6, Ljava/io/FileWriter;
const/4 v7, 0x1 # append = true
invoke-direct {v6, v2, v7}, Ljava/io/FileWriter;-><init>(Ljava/io/File;Z)V
new-instance v8, Ljava/io/BufferedWriter;
invoke-direct {v8, v6}, Ljava/io/BufferedWriter;-><init>(Ljava/io/Writer;)V
const-string v9, "SAVED: "
invoke-virtual {v8, v9}, Ljava/io/BufferedWriter;->write(Ljava/lang/String;)V
invoke-virtual {v8, p1}, Ljava/io/BufferedWriter;->write(Ljava/lang/String;)V
const-string v9, "\n"
invoke-virtual {v8, v9}, Ljava/io/BufferedWriter;->write(Ljava/lang/String;)V
invoke-virtual {v8}, Ljava/io/BufferedWriter;->close()V
:try_end
.catch Ljava/io/IOException; {:try_start .. :try_end} :catch
return-void
:catch
move-exception v0
return-void
.end method
```
### Where to insert the function and how to call it
#### Placement of definition
- File: `enotes1/l1/g.smali`
- Class: `Lcom/wraith/enotes1/l1/g;`
- Insert the method at the **end of the file, just before `.end class`**:
```smali
# ... other methods of class g ...
.method private static logChange(Landroid/content/Context;Ljava/lang/String;)V
... (content above) ...
.end method
.end class
```
#### Call of the function from the method `G(...)`
The method `logChange(...)` must be called for each save.
Find in file `enotes1/l1/g.smali` the method:
```smali
.method public G(Landroid/content/Context;Ljava/lang/String;Z)V
```
Saving the file can be invoked from two places — the standard method and the SAF method.
Therefore, the call to the log function must be inserted twice, each time **before `return-void`** of each branch.
SAF (Storage Access Framework) – a system mechanism of Android used when
writing outside the app’s own directories, for example to an SD card, cloud or to a folder
chosen by the user as Epsilon Home.
To call `logChange(context, path)` you must first obtain the `path` of the saved
file, because `logChange` is a static method and has no access to `p0`.
#### Final section in the method `G(...)`
```smali
invoke-virtual {v2}, Ljava/io/BufferedWriter;->close()V
# <<< logging during standard saving >>>
invoke-virtual {p0}, Ljava/io/File;->getAbsolutePath()Ljava/lang/String;
move-result-object v0
invoke-static {p1, v0}, Lcom/wraith/enotes1/l1/g;->logChange(Landroid/content/Context;Ljava/lang/String;)V
# <<< end of call >>>
return-void
:cond_saf
invoke-static {p1, p0, p2, p3}, Lcom/wraith/enotes1/l1/h;->d(Landroid/content/Context;Ljava/io/File;Ljava/lang/String;Z)V
# <<< logging during SAF >>>
invoke-virtual {p0}, Ljava/io/File;->getAbsolutePath()Ljava/lang/String;
move-result-object v0
invoke-static {p1, v0}, Lcom/wraith/enotes1/l1/g;->logChange(Landroid/content/Context;Ljava/lang/String;)V
# <<< end of call >>>
return-void
```
### Logic summary
| Phase | Goal | Result |
|------|------|-----------|
| 1 | Get the directory of the currently saved file | e.g. `/storage/emulated/0/Notes/Diary/` |
| 2 | Go through parent folders (`Diary`, `Notes`, `/storage/emulated/0`) | use the first found `epsilon-notes.log` |
| 3 | If no log in the hierarchy | try `Epsilon Home` (`preferences.a.getHomePath()`) |
| 4 | If not there either | create `/storage/emulated/0/epsilon-notes.log` |
| 5 | Result | Changes are always written to the nearest available log |
### Advantages
- Each project or folder can have its own log file.
- If missing, the global log in *Epsilon Home* is used.
- Logging works universally even for files outside Home.
- The user can place an empty `epsilon-notes.log` in any folder to redirect writes there only.