Rewrite from Python to Go for single-binary cross-platform builds
Replaces imapdown.py with a multi-file Go implementation using github.com/emersion/go-imap/v2. All features preserved: SSL/STARTTLS, incremental UID-based downloads, attachment extraction to zip, modified UTF-7 folder name decoding, and full-mode safety checks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,42 +4,56 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a single-file Python script (`imapdown.py`) that downloads all emails from an IMAP server into individual EML files, preserving the folder hierarchy. It uses only Python's standard library and has no external dependencies.
|
||||
This project downloads all emails from an IMAP server into individual EML files, preserving the folder hierarchy.
|
||||
|
||||
Built with Go as a single self-contained binary, fast and cross-platform.
|
||||
|
||||
## Development Environment
|
||||
|
||||
- Python 3.6+ required
|
||||
- Virtual environment is set up in `.venv` - activate it before running:
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
```
|
||||
- Go 1.21+ required
|
||||
- Dependencies: `github.com/emersion/go-imap/v2` (auto-installed via `go mod tidy`)
|
||||
- Build with: `make build` or `go build`
|
||||
- Cross-compile with: `make build-all`
|
||||
|
||||
## Running the Script
|
||||
## Running the Application
|
||||
|
||||
First, build the binary:
|
||||
```bash
|
||||
make build
|
||||
# Or cross-compile for all platforms:
|
||||
make build-all
|
||||
```
|
||||
|
||||
Basic usage (incremental mode - only downloads new emails):
|
||||
```bash
|
||||
./imapdown.py --server imap.example.com --email user@example.com --user user@example.com --password "password" --ssl
|
||||
./imapdown -server imap.example.com -email user@example.com -user user@example.com -password "password" -ssl
|
||||
```
|
||||
|
||||
Full download (ignores previous state, requires empty target directory):
|
||||
```bash
|
||||
./imapdown.py --server imap.example.com --email user@example.com --user user@example.com --password "password" --ssl --full
|
||||
./imapdown -server imap.example.com -email user@example.com -user user@example.com -password "password" -ssl -full
|
||||
```
|
||||
|
||||
Testing/debugging with limited emails:
|
||||
```bash
|
||||
./imapdown.py --server imap.example.com --email user@example.com --user user@example.com --password "password" --ssl --limit 10
|
||||
./imapdown -server imap.example.com -email user@example.com -user user@example.com -password "password" -ssl -limit 10
|
||||
```
|
||||
|
||||
Custom storage directory:
|
||||
```bash
|
||||
./imapdown.py --server imap.example.com --email user@example.com --user user@example.com --password "password" --ssl --output /path/to/backup
|
||||
./imapdown -server imap.example.com -email user@example.com -user user@example.com -password "password" -ssl -output /path/to/backup
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Single-File Design
|
||||
The entire application is contained in `imapdown.py` (13KB). This is intentional - no modules or packages.
|
||||
### Implementation Structure
|
||||
The code is organized into multiple files for clarity:
|
||||
- `main.go` - Entry point, CLI parsing, orchestration
|
||||
- `imap.go` - IMAP connection and folder operations
|
||||
- `email.go` - Email parsing and attachment extraction
|
||||
- `state.go` - State file management (JSON)
|
||||
- `filename.go` - Filename sanitization and Modified UTF-7 decoding
|
||||
- `Makefile` - Build targets
|
||||
|
||||
### State Tracking
|
||||
- The script maintains a `.imapdown_state.json` file in each email account's download folder
|
||||
@@ -62,15 +76,15 @@ The entire application is contained in `imapdown.py` (13KB). This is intentional
|
||||
|
||||
### Key Implementation Details
|
||||
|
||||
**Modified UTF-7 Decoding**: IMAP folder names use modified UTF-7 encoding (see `decode_modified_utf7()` at line 39). This is not standard base64 - it uses `,` instead of `/` and has special `&` handling.
|
||||
**Modified UTF-7 Decoding**: IMAP folder names use modified UTF-7 encoding. This is not standard base64 - it uses `,` instead of `/` and has special `&` handling. Implemented in `DecodeModifiedUTF7()` in `filename.go`.
|
||||
|
||||
**Filename Sanitization**: Two-stage process:
|
||||
- `sanitize_filename()`: Removes invalid filesystem characters, max 50 chars for subjects
|
||||
- `sanitize_folder_path()`: Converts IMAP folder separators (`.` or `/`) to OS path separators
|
||||
- `SanitizeFilename()`: Removes invalid filesystem characters, max 50 chars for subjects
|
||||
- `SanitizeFolderPath()`: Converts IMAP folder separators (`.` or `/`) to OS path separators
|
||||
|
||||
**UID-Based Incremental Updates**: Uses IMAP UIDs (not sequence numbers) because UIDs are persistent. The search `UID {last_uid + 1}:*` fetches only new messages. Some servers return the highest UID even when searching for higher UIDs, so there's additional filtering at line 251.
|
||||
**UID-Based Incremental Updates**: Uses IMAP UIDs (not sequence numbers) because UIDs are persistent. When `lastUID > 0`, searches for UIDs > lastUID. On first run (`lastUID == 0`), searches for all messages using an empty SearchCriteria. Some servers return the highest UID even when searching for higher UIDs, so there's additional filtering.
|
||||
|
||||
**Full Mode Safety**: `--full` mode checks if the download folder already contains `.eml` files and refuses to run (line 325). This prevents accidental duplicates. Users must delete the folder first.
|
||||
**Full Mode Safety**: `-full` mode checks if the download folder already contains `.eml` files and refuses to run. This prevents accidental duplicates. Users must delete the folder first.
|
||||
|
||||
**Attachment Handling**:
|
||||
- Walks message parts looking for `Content-Disposition: attachment` or `inline`
|
||||
@@ -79,22 +93,59 @@ The entire application is contained in `imapdown.py` (13KB). This is intentional
|
||||
|
||||
## Output Structure
|
||||
|
||||
Without `-output` flag (default: `./{email_address}`):
|
||||
```
|
||||
{output_dir}/ # default: ./download
|
||||
└── {email_address}/ # sanitized email address
|
||||
├── .imapdown_state.json
|
||||
├── INBOX/
|
||||
│ ├── 123_20240115_Meeting_notes.eml
|
||||
│ └── 124_20240116_Report.zip
|
||||
└── Sent/
|
||||
└── 456_20240114_RE_Question.eml
|
||||
{email_address}/ # sanitized email address in current directory
|
||||
├── .imapdown_state.json
|
||||
├── INBOX/
|
||||
│ ├── 123_20240115_Meeting_notes.eml
|
||||
│ └── 124_20240116_Report.zip
|
||||
└── Sent/
|
||||
└── 456_20240114_RE_Question.eml
|
||||
```
|
||||
|
||||
With `-output /path/to/backup`:
|
||||
```
|
||||
/path/to/backup/ # specified output directory used directly
|
||||
├── .imapdown_state.json
|
||||
├── INBOX/
|
||||
│ ├── 123_20240115_Meeting_notes.eml
|
||||
│ └── 124_20240116_Report.zip
|
||||
└── Sent/
|
||||
└── 456_20240114_RE_Question.eml
|
||||
```
|
||||
|
||||
## Building and Installing
|
||||
|
||||
Build for current platform:
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
Cross-compile for all platforms:
|
||||
```bash
|
||||
make build-all
|
||||
# Produces: imapdown-linux-amd64, imapdown-linux-arm64,
|
||||
# imapdown-darwin-amd64, imapdown-darwin-arm64,
|
||||
# imapdown-windows-amd64.exe
|
||||
```
|
||||
|
||||
Install to `$GOPATH/bin`:
|
||||
```bash
|
||||
make install
|
||||
```
|
||||
|
||||
Clean build artifacts:
|
||||
```bash
|
||||
make clean
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
No formal test suite exists. Manual testing approach:
|
||||
- Use `--limit 10` to download a small batch for verification
|
||||
- Use `-limit 10` to download a small batch for verification
|
||||
- Test SSL vs STARTTLS connections
|
||||
- Test incremental mode by running twice
|
||||
- Verify `.eml` files open correctly in email clients
|
||||
- Check that folders with special characters (non-ASCII) are handled correctly
|
||||
- Test first run (no state file) to ensure all messages are downloaded
|
||||
|
||||
Reference in New Issue
Block a user