Developer Referenceprinting
PrintQueue
The session's print queue: a CUPS scheduler run as this user under the
runtime directory, with one queue, Selkies, whose backend writes each job
into the spool. The scheduler's programs are the installed ones, reached
through a private tree that adds the backend, and its whole state lives
under root, so nothing under /etc/cups is touched and no privilege is
needed. Applications reach it with CUPS_SERVER set to socket.
Attributes
attributeroot= os.path.join(runtime, 'selkies-cups') if runtime else os.path.join(tempfile.gettempdir(), f'selkies-cups-{os.getuid()}')attributesocket= os.path.join(self.root, 'cups.sock')attributespool= spoolattributeprocessOptional[asyncio.subprocess.Process]= NoneFunctions
constructor__init__(spool) -> NoneSource Code
def __init__(self, spool: str) -> None:
runtime = os.environ.get("XDG_RUNTIME_DIR")
self.root = os.path.join(runtime, "selkies-cups") if runtime else \
os.path.join(tempfile.gettempdir(), f"selkies-cups-{os.getuid()}")
self.socket = os.path.join(self.root, "cups.sock")
self.spool = spool
self.process: Optional[asyncio.subprocess.Process] = NoneparamspoolstrReturns
Nonefuncprograms() -> Optional[Tuple[str, str, str]](cupsd, server bin, data dir) of the installed CUPS, or None.
Source Code
@staticmethod
def programs() -> Optional[Tuple[str, str, str]]:
"""`(cupsd, server bin, data dir)` of the installed CUPS, or None."""
path = os.environ.get("PATH", "") + ":/usr/sbin:/usr/local/sbin"
cupsd = shutil.which("cupsd", path=path)
if not cupsd:
return None
prefix = os.path.dirname(os.path.dirname(os.path.realpath(cupsd)))
for lib in ("lib", "lib64", "libexec"):
server_bin = os.path.join(prefix, lib, "cups")
if os.path.isfile(os.path.join(server_bin, "daemon", "cups-exec")) \
and os.path.isdir(os.path.join(server_bin, "filter")):
return cupsd, server_bin, os.path.join(prefix, "share", "cups")
return NoneReturns
typing.Optional[typing.Tuple[str, str, str]]func_prepare(server_bin, data_dir) -> NoneSource Code
def _prepare(self, server_bin: str, data_dir: str) -> None:
for sub in ("ppd", "state", "cache", "spool", "tmp", "bin/backend"):
os.makedirs(os.path.join(self.root, sub), exist_ok=True)
os.makedirs(self.spool, exist_ok=True)
for sub in ("filter", "daemon"):
link = os.path.join(self.root, "bin", sub)
if os.path.islink(link):
os.unlink(link)
os.symlink(os.path.join(server_bin, sub), link)
package = files("selkies") / "cups"
backend = os.path.join(self.root, "bin", "backend", "selkies")
with open(backend, "wb") as out:
out.write((package / "backend").read_bytes())
os.chmod(backend, 0o755)
with open(os.path.join(self.root, "ppd", "Selkies.ppd"), "wb") as out:
out.write((package / "selkies.ppd").read_bytes())
confs = {
"cups-files.conf": f"""ServerRoot {self.root}
ServerBin {os.path.join(self.root, "bin")}
DataDir {data_dir}
StateDir {os.path.join(self.root, "state")}
CacheDir {os.path.join(self.root, "cache")}
RequestRoot {os.path.join(self.root, "spool")}
TempDir {os.path.join(self.root, "tmp")}
LogFileGroup {os.getgid()}
AccessLog {os.path.join(self.root, "access.log")}
PageLog {os.path.join(self.root, "page.log")}
ErrorLog {os.path.join(self.root, "error.log")}
""",
"cupsd.conf": f"""Listen {self.socket}
LogLevel warn
Browsing Off
WebInterface No
""",
"printers.conf": f"""<DefaultPrinter Selkies>
Info Selkies printer
Location In the browser
DeviceURI selkies:{self.spool}
State Idle
Accepting Yes
Shared No
JobSheets none none
ErrorPolicy retry-job
</DefaultPrinter>
""",
}
for name, text in confs.items():
with open(os.path.join(self.root, name), "w") as out:
out.write(text)
if os.path.exists(self.socket):
os.unlink(self.socket)paramserver_binstrparamdata_dirstrReturns
Nonefuncstart() -> boolStart the scheduler; False where CUPS is not installed.
Source Code
async def start(self) -> bool:
"""Start the scheduler; False where CUPS is not installed."""
found = self.programs()
if found is None:
logger.info("No CUPS scheduler on this host: documents printed into the spool are "
"still handed over, but there is no Selkies queue to print to "
"(cups-daemon and cups-filters provide one)")
return False
cupsd, server_bin, data_dir = found
# The scheduler runs as a copy of the program: a distribution confines
# the system scheduler, by its path, to the system's directories and
# backends, which a queue under the runtime directory has neither of.
# The copy is renamed into place, which a scheduler still running the
# old copy (left by a server that died without stopping it) never
# blocks the way writing over its program would.
program = os.path.join(self.root, "cupsd")
try:
self._prepare(server_bin, data_dir)
shutil.copy2(cupsd, program + ".new")
os.replace(program + ".new", program)
except OSError as exc:
logger.warning("No print queue: cannot set the scheduler up under %s: %s", self.root, exc)
return False
args = ["-f", "-c", os.path.join(self.root, "cupsd.conf"), "-s", os.path.join(self.root, "cups-files.conf")]
spawn = dict(stdout=asyncio.subprocess.DEVNULL, stderr=asyncio.subprocess.DEVNULL,
preexec_fn=_die_with_parent)
try:
self.process = await asyncio.create_subprocess_exec(program, *args, **spawn)
except OSError:
self.process = await asyncio.create_subprocess_exec(cupsd, *args, **spawn)
for _ in range(100):
if os.path.exists(self.socket) or self.process.returncode is not None:
break
await asyncio.sleep(0.1)
if not os.path.exists(self.socket):
logger.warning("The print queue did not come up; its log is %s",
os.path.join(self.root, "error.log"))
await self.stop()
return False
logger.info("Print queue Selkies listening on %s, spooling to %s", self.socket, self.spool)
return TrueReturns
boolfuncstop() -> NoneSource Code
async def stop(self) -> None:
process, self.process = self.process, None
if process is None or process.returncode is not None:
return
process.terminate()
try:
await asyncio.wait_for(process.wait(), 5)
except asyncio.TimeoutError:
process.kill()
await process.wait()Returns
None