#!/usr/bin/env python3

"""Match the classroom's panel to the machine it is on.

There is exactly one thing about the bar that depends on hardware: a room
with no optical drive should not be offered a disc player. This edits the
one marked line in the installed layout, and nothing else.

Run before the display manager by ekklesia-classroom-layout.service, and
again by udev when a drive is plugged in or pulled out. That ordering is
the whole point: the panel is built from this layout the moment a session
starts, and a room that is told about its hardware afterwards keeps the
wrong bar until the next login. It was being left to the provisioning
service, which runs at boot alongside the session rather than before it.

It writes the *layout*, not the running panel. Editing
plasma-org.kde.plasma.desktop-appletsrc underneath a live plasmashell was
tried and it destroyed the panel: the shell owns that file and rewrites
it, so an edit races with it. The layout is a file the package owns and
the shell only reads, and classroom-desktop-reset makes the shell read it
again at the next login.

DVD_ENABLED is gone. A drive is something the machine can be asked about,
and asking is more reliable than remembering to edit a file when a
computer moves between rooms.
"""

import glob
import os
import re
import sys

LAYOUT = (
    "/usr/share/plasma/look-and-feel/org.ekklesia.classroom.desktop"
    "/contents/layouts/org.kde.plasma.desktop-layout.js"
)

MARKER = "/* CLASSROOM-DVD-LAUNCHER */"

# The kernel names optical drives sr0, sr1, ... regardless of bus. Both
# views are checked: /dev is what anything opening the drive will use, and
# /sys/block is the kernel's own list, which cannot be left behind by a
# device node somebody or something created by hand.
DRIVE_GLOB = "/dev/sr[0-9]*"
SYSFS_GLOB = "/sys/block/sr[0-9]*"


def drives():
    return sorted(glob.glob(DRIVE_GLOB)), sorted(glob.glob(SYSFS_GLOB))


def has_optical_drive():
    nodes, blocks = drives()

    # Said out loud every time. When a room shows a disc player it has no
    # drive for, or hides one it has, this line in the journal is the
    # difference between an answer and an afternoon.
    print("classroom-desktop-layout: /dev "
          f"[{', '.join(nodes) or 'none'}], /sys/block "
          f"[{', '.join(blocks) or 'none'}]")

    return bool(nodes and blocks)


def rewrite(text, wanted):
    """Comment the DVD launcher in or out, idempotently."""
    line = re.compile(
        r"^(\s*)(//\s*)?(" + re.escape(MARKER) + r'\s*"applications:ekklesia-dvd\.desktop")',
        re.M,
    )

    def swap(match):
        indent, _, body = match.groups()
        return f"{indent}{'' if wanted else '// '}{body}"

    return line.sub(swap, text)


def main():
    path = sys.argv[1] if len(sys.argv) > 1 else LAYOUT

    try:
        with open(path, "r", encoding="utf-8") as handle:
            before = handle.read()
    except OSError as error:
        print(f"classroom-desktop-layout: {error}", file=sys.stderr)
        return 0

    if MARKER not in before:
        print("classroom-desktop-layout: no DVD marker in the layout",
              file=sys.stderr)
        return 0

    wanted = has_optical_drive()
    after = rewrite(before, wanted)

    if after == before:
        print(f"classroom-desktop-layout: DVD launcher already "
              f"{'present' if wanted else 'absent'}")
        return 0

    # Replace the file rather than edit in place, so a full disk or a
    # crash leaves the old layout intact rather than half of a new one.
    temporary = f"{path}.new"

    try:
        with open(temporary, "w", encoding="utf-8") as handle:
            handle.write(after)
        os.replace(temporary, path)
    except OSError as error:
        print(f"classroom-desktop-layout: {error}", file=sys.stderr)
        return 1

    print(f"classroom-desktop-layout: DVD launcher "
          f"{'added' if wanted else 'removed'}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
