Skip to content

About

Reader and writer for classic FTSC-style 190-byte MSG areas

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

golded-ftn-msg

Repository: golded-ftn-msg-python. The distribution remains golded-ftn-msg; imports use golded_ftn_msg. The source is public on GitHub. Version 1.2.0 is available on PyPI.

Install with Python 3.12 or newer:

python -m pip install golded-ftn-msg==1.2.0

Read and write explicit FTSC and Opus .MSG areas with a 190-byte header. The local 1.3.0 development version adds Opus writing; the published 1.2.0 version writes FTSC only. Python 3.12 or newer. MIT licensed.

The public API exports MsgReader, MsgWriter and MsgSession. Message values, options and protocols come from golded-ftn>=1.2.0,<2.

from pathlib import Path

from golded_ftn import MessagePatch, OutgoingMessage
from golded_ftn_msg import MsgReader, MsgWriter

area = Path("messages")
writer = MsgWriter()
writer.create(area)
with writer.open(area) as session:
    added = session.append(
        OutgoingMessage(
            from_name="Alice", to_name="Bob", subject="Hello", body_text="First line"
        )
    )
    current = session.read(added.identity.msgno)
    session.update(current.identity, MessagePatch(subject="Revised"), current.revision)
assert list(MsgReader().read(area))[-1].subject == "Revised"

Installation

For local development, keep the two repositories beside each other:

golded-dev/
  golded-ftn-python/
  golded-ftn-msg-python/
cd golded-ftn-msg-python
uv sync --locked
uv run pytest

uv uses the sibling core checkout during development. Wheel and sdist dependency metadata contains the version constraint only. To install local built wheels:

uv build ../golded-ftn-python --out-dir /tmp/golded-wheels
uv build --out-dir /tmp/golded-wheels
uv pip install /tmp/golded-wheels/*.whl

Behaviour and limits

MsgReader(header_format="ftsc") is the default. Use MsgReader("opus") explicitly for Opus areas; there is no automatic header detection. Opus bytes 176–183 contain DOS written/arrived timestamps rather than zone/point words. The written timestamp supplies a naive date with two-second precision, using 1980–2107; zero or invalid written timestamps fall back to the textual date. Addresses use header net/node plus INTL/FMPT/TOPT, without treating timestamp bits as address metadata. Opus provenance uses source_type="opus"; FTSC provenance remains "msg". Use MsgWriter().create(path, header_format="opus") and MsgWriter().open(path, header_format="opus") for Opus writing. Selection is explicit on each operation; it does not detect or convert an existing variant. write(..., header_format="opus") uses the same selection.

New Opus posted_at values must be naive, 1980–2069, without microseconds and with even seconds. The DOS fields can encode 1980–2107, but the textual MSG date and GoldED's read path impose the narrower writer range. Odd seconds are rejected, not rounded. None writes zero DOS written words and an empty textual date. New arrived words are zero because OutgoingMessage has no arrival date field. Updates preserve arrived words and unknown header bytes; only a posted_at patch changes written words and textual date. Existing invalid/zero written words retain the reader's textual fallback. Zones/points use INTL/FMPT/TOPT. A nonzero zone requires both addresses or a consistent INTL declaration; incomplete representation is rejected before mutation. GoldED interoperability is still unverified.

The reader accepts positive numeric filenames with case-insensitive .msg extensions. It sorts numerically and rejects duplicate message numbers. Filesystem errors identify missing or invalid area paths. Malformed message files raise ParserException with the file path and original cause.

Text decoding is strict, using the core charset helpers and CP850 fallback. Kludges remain in the normalized body; mojibake is never repaired automatically. Dates use English month names and the 1970–2069 two-digit year window. Invalid reader dates become None. Header addresses and INTL/FMPT/TOPT must agree. Provenance records the actual file path and message number; offsets are unknown.

MsgWriter.create(path) initializes a new area. A context-managed open(path) session provides read, append, update and delete. Updates and deletes require the revision returned by a consistent session read. Unrelated message changes do not invalidate that revision. Unknown header bytes and controls survive updates; attribute-only updates preserve the original text bytes. The revision contains format/base/message identity, the physical filename number and SHA-256 over the raw file. Omitted patch fields stay unchanged; explicit None clears only representable optional values. control_lines replaces general controls; omitted MSGID, addresses and routing retain their structured fields. Body-only changes preserve controls and routing. Conflicting metadata is rejected.

Appends publish a complete temporary file without replacing an existing number. Updates replace the complete file; deletes remove it. A sidecar lock serializes Python sessions and retains the highest allocated number. Each operation rolls back I/O failures where possible; a failed rollback poisons the session. The legacy write convenience method returns the number of completed appends. Earlier operations remain committed if a later one fails.

Only offline FTSC editing is supported. Opus editing and concurrent=True are rejected. GoldED read/write interoperability is pending. POSIX publication uses a hard link; Windows uses a non-replacing rename. Windows runtime behaviour has not been verified. Linux execution has not been exercised here either. Process death and power loss are outside the rollback guarantee; a controlled exit test observes an unpublished temporary file after interrupted append.

Header names allow 35 encoded bytes; subjects allow 71. Encoding is strict. Header fields reject nulls and line breaks. Dates must be naive, have no microseconds, and fall within 1970–2069. Address components and attributes must fit unsigned 16-bit fields. Domain addresses are unsupported.

The writer preserves body kludges, quoting and routing, and adds missing declared control lines, charset and address kludges. Conflicting metadata is rejected. An external MSGID may be supplied; synthetic hash IDs cannot become MSGID. MSGID is never generated automatically. Provenance is not serialized. Body lines use CR and end with one null byte.

Development

uv sync --locked
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run mypy
uv run python -m mypy.stubtest golded_ftn_msg
uv build
uv run twine check dist/*
uv run python scripts/verify_distribution.py

See writer source notes for the original GoldED layout, locking limits and the distinction between source evidence and build tests.

Archive mode

Strict reading remains the default. For damaged archives, opt in explicitly:

from golded_ftn import ReaderIssue, ReaderOptions
from golded_ftn_msg import MsgReader

issues: list[ReaderIssue] = []
options = ReaderOptions(archive_mode=True, on_issue=issues.append)
# Replace "messages" with the actual archive directory.
messages = list(MsgReader("opus").read("messages", options))

Archive mode requires a callback. It skips malformed message files with a record_parse_error issue and continues to later files. If declared ASCII cannot decode a message, it tries the configured fallback strictly and reports ascii_decode_fallback after successful parsing. Invalid UTF-8 and address conflicts are skipped, never repaired. Original charset controls remain unchanged. Duplicate numeric filenames make identity ambiguous: the reader reports duplicate_message_number with action stopped before reading any messages. Callback exceptions propagate. ReaderIssue carries the source identity, action, code and a description without message contents. Recovery includes the failed ASCII byte offset; record errors without a known location use None.

This package does not discover areas, provide a database, or integrate with Nornir.

See CONTRIBUTING.md, SECURITY.md, and the release checklist.

About

Reader and writer for classic FTSC-style 190-byte MSG areas

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages