Class UploadStagingStore

java.lang.Object
com.iizix.server.upload.UploadStagingStore

public final class UploadStagingStore extends Object
The staging directory of the upload service: <root>/xfer/<rid>/ holds one reservation.json, and per file <fid>.part (the bytes as received, compressed if the transfer uses gzip) beside <fid>.json.

Every path is derived from the reservation ID and the file index only; no client string ever becomes a path. Sidecars are written atomically (temporary file, then move). The part is appended before its sidecar is rewritten, so the sidecar's hwm is always at most the part's length; openForAppend(String,int,long) truncates a longer part back to hwm, never the other way round.

This is a separate root from the file provider's per-session publish directory on purpose: a half-written, unverified upload must never sit in a publish root.

Thread-safety: callers serialize access per reservation (the registry's per-reservation lock); the sweeps take a directory snapshot and tolerate concurrent deletion.

Author:
Christopher Mindus
  • Field Details

    • XFER

      public static final String XFER
      The transfer subdirectory under the configured staging directory.
      See Also:
    • RESERVATION_JSON

      public static final String RESERVATION_JSON
      The reservation sidecar file name.
      See Also:
    • BUFFER_SIZE

      public static final int BUFFER_SIZE
      The buffer size used when streaming a body into a part.
      See Also:
  • Constructor Details

    • UploadStagingStore

      public UploadStagingStore(File stagingDirectory, long serverBoot) throws IOException
      Constructor. The root is created if absent.
      Parameters:
      stagingDirectory - The configured staging directory ($upload.dir).
      serverBoot - The server process start time in milliseconds.
      Throws:
      IOException - If the root cannot be created or canonicalized.
  • Method Details

    • getRoot

      public Path getRoot()
      Gets the canonical root (<staging>/xfer).
      Returns:
      The root.
    • getServerBoot

      public long getServerBoot()
      Gets the server process start time this store was created with.
      Returns:
      The boot time.
    • checkReservationID

      public static void checkReservationID(String rid)
      Validates a reservation ID: exactly 32 lower-case hex characters, so that it can only ever name a direct child of the root.
      Parameters:
      rid - The reservation ID.
      Throws:
      IllegalArgumentException - If the ID is not of that form.
    • reservationDirectory

      public Path reservationDirectory(String rid)
      Gets the directory of a reservation.
      Parameters:
      rid - The reservation ID.
      Returns:
      The directory, existing or not.
      Throws:
      IllegalArgumentException - If the ID is malformed.
    • partFile

      public Path partFile(String rid, int fid)
      Gets the part file of a file.
      Parameters:
      rid - The reservation ID.
      fid - The file index.
      Returns:
      The part path, existing or not.
    • sidecarFile

      public Path sidecarFile(String rid, int fid)
      Gets the sidecar file of a file.
      Parameters:
      rid - The reservation ID.
      fid - The file index.
      Returns:
      The sidecar path, existing or not.
    • createReservation

      public void createReservation(ReservationRecord reservation, List<FileRecord> files) throws IOException
      Creates the directory of a new reservation and writes its sidecar and every file sidecar.
      Parameters:
      reservation - The reservation record.
      files - The file records, in file index order.
      Throws:
      IOException - For file errors, including an already existing directory.
    • writeReservation

      public void writeReservation(ReservationRecord reservation) throws IOException
      Rewrites a reservation sidecar.
      Parameters:
      reservation - The record.
      Throws:
      IOException - For file errors.
    • writeFile

      public void writeFile(FileRecord file) throws IOException
      Rewrites a file sidecar.
      Parameters:
      file - The record.
      Throws:
      IOException - For file errors.
    • readReservation

      public ReservationRecord readReservation(String rid) throws IOException
      Reads a reservation sidecar.
      Parameters:
      rid - The reservation ID.
      Returns:
      The record, or null if absent or unreadable.
      Throws:
      IOException - For file errors.
    • readFile

      public FileRecord readFile(String rid, int fid) throws IOException
      Reads a file sidecar.
      Parameters:
      rid - The reservation ID.
      fid - The file index.
      Returns:
      The record, or null if absent or unreadable.
      Throws:
      IOException - For file errors.
    • partLength

      public long partLength(String rid, int fid) throws IOException
      Gets the current length of a part.
      Parameters:
      rid - The reservation ID.
      fid - The file index.
      Returns:
      The length in bytes, 0 if the part does not exist yet.
      Throws:
      IOException - For file errors.
    • openForAppend

      public FileChannel openForAppend(String rid, int fid, long hwm) throws IOException
      Opens a part for appending at the acknowledged high-water mark. A part longer than hwm (a crash between an append and its sidecar rewrite) is truncated to hwm; a part shorter than hwm cannot happen by the write order and is reported.
      Parameters:
      rid - The reservation ID.
      fid - The file index.
      hwm - The acknowledged length from the sidecar.
      Returns:
      A channel positioned at hwm; the caller closes it.
      Throws:
      IOException - For file errors.
      IllegalStateException - If the part is shorter than hwm.
    • deleteFile

      public void deleteFile(String rid, int fid) throws IOException
      Deletes a part and its sidecar (a failed or cancelled file).
      Parameters:
      rid - The reservation ID.
      fid - The file index.
      Throws:
      IOException - For file errors.
    • deleteReservation

      public boolean deleteReservation(String rid) throws IOException
      Deletes a reservation directory: every part and sidecar whose file is not UploadState.CONSUMED, then the directory itself if nothing is left. A consumed file's part is never touched; its sidecar is removed only together with the directory, i.e. never while the part is still there.
      Parameters:
      rid - The reservation ID.
      Returns:
      true if the directory is gone, false if consumed files keep it.
      Throws:
      IOException - For file errors.
    • readFiles

      public List<FileRecord> readFiles(String rid) throws IOException
      Reads every file sidecar of a reservation.
      Parameters:
      rid - The reservation ID.
      Returns:
      The records, in file index order; unreadable sidecars are skipped.
      Throws:
      IOException - For file errors.
    • listReservations

      public List<String> listReservations() throws IOException
      Lists the reservation IDs present on disk.
      Returns:
      The IDs of every well-formed subdirectory; malformed names are reported and deleted only by sweep(Predicate,long).
      Throws:
      IOException - For file errors.
    • sweep

      public List<String> sweep(Predicate<String> sessionAlive, long now) throws IOException
      Sweeps the root: every reservation directory whose record is unreadable, from a previous boot, cancelled, or open but no longer in flight, is deleted (consumed files excepted). A reservation is in flight when its record says so (ReservationRecord.isInFlight(long, long)) AND the caller's predicate confirms its application session is alive.

      At startup the predicate is irrelevant: no record can carry this boot's time yet, so every directory is a previous boot's and is swept.

      Parameters:
      sessionAlive - Answers whether an application session UUID is alive now.
      now - The current time in milliseconds.
      Returns:
      The IDs of the reservations that were swept.
      Throws:
      IOException - For file errors on the root itself; per-directory errors are logged and skipped.