Logging function

9 views
Skip to first unread message

petr.m...@gmail.com

unread,
Oct 6, 2025, 4:05:06 AMOct 6
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.
Reply all
Reply to author
Forward
0 new messages