PhoenixKit.Modules.Storage.Profiles (phoenix_kit v2.60.2)

Copy Markdown View Source

Storage profiles: where a library's bytes live (V205).

A library points at a profile (PhoenixKit.Modules.Storage.StorageProfile), and the profile lists its buckets with how it uses each (PhoenixKit.Modules.Storage.ProfileBucket: role, write priority, serve order, status) and how many copies an object gets, on local buckets and on cloud ones. A library with no profile uses the Default, seeded by V205 from the install's buckets and storage_redundancy_copies under a fixed uuid (default_uuid/0), so an install that never touches profiles behaves as it did before.

Every change to a profile or its buckets bumps its revision (bump_revision/1). A file records the profile and revision it was placed by (placed_profile_uuid / placed_revision, NULL meaning the Default at revision 1), which is how the reconciler finds the files that are not where their library's profile wants them.

Summary

Functions

Puts bucket into one of the site's profiles as a primary that stores everything, active, with no fixed write priority (the shuffled pool), served after the profile's other buckets, and bumps the profile's revision (its files are placed again). Change the role, order or status afterwards with put_bucket/4.

Puts a newly created bucket into the Default profile, the way every new bucket joined the pool before profiles: primary, active, no fixed write priority (it is in the shuffled pool), served after the Default's other buckets (a local one before the remote ones). The default of Storage.create_bucket/2; a caller that wants none or another profile says so there (:profile).

Which profiles use each of bucket_uuids, and how many libraries stand behind each one: %{bucket_uuid => [usage]}, a bucket no profile uses absent from the map. Every profile row counts, whatever its role or status (draining and read_only still hold or serve objects), and a user's own profile counts too — a site bucket may be one half of their backup.

Bumps a profile's revision: every file it placed is stale, and the reconciler is queued to bring them up to date.

The copies profile wants on one kind of bucket: :local (the server's disks) or :cloud. Every file, an original and what is made from it, gets the same.

What a profile's copy counts mean for the buckets it has now, for the screens that tell an admin and the one place that must agree with what they say.

How many copies of a file profile wants in all: local plus cloud.

Creates a profile with no buckets. opts (:actor_uuid, :mode) say who did it, for the history (Storage.Audit).

Creates the profile of a user's own storage, in one of two modes

Whether uuid is the Default profile's.

How many copies of an original the Default keeps (1 when there is no Default yet, as storage_redundancy_copies defaulted to).

The Default profile, with its buckets.

The uuid of the Default profile, fixed on every install.

Deletes a profile. The Default cannot be deleted ({:error, :default}), nor a profile a library uses ({:error, :in_use}).

Removes a user's own profile (V206) and the buckets that were in it and are in no profile now: what is left of a user's storage once their library is purged. A site profile, or one a library still uses, is left alone.

The counts profile can actually keep: each kind's count lowered to the writable buckets (active, enabled) it has of that kind, never raised. When that leaves nothing (it wants only a kind it has no bucket of), the total is split over the buckets it has instead (split_copies/2).

The profile a file's library uses.

The profile library uses, with its buckets (see profile_uuid_for/1). Falls back to the Default when the library's own is gone.

A profile with its buckets, or nil (also for anything not a uuid).

The key layout (Storage.KeyLayout) uploads into library get: its profile's key_levels. The default layout when the profile cannot be read; an upload must never fail for want of one.

How many libraries (trashed ones included) use profile_uuid. For the Default that is the libraries that name it and those that name no profile at all, which use it too (profile_uuid_for/1).

The names of the site libraries on profile_uuid (the Default's include those that name no profile), and how many user libraries are on it besides. A user's library is private to its owner, so it is counted, never named.

The site's profiles, the Default first, each with its buckets. A user's own profile (V206) is not here: the site's profile editor and pickers read this, and a user's storage is not something an admin assigns or edits.

The uuid of the profile library uses: its own, or the Default. Takes a library, a library uuid, or nil (a file with no library is in Media).

Adds bucket_uuid to profile, or changes how the profile uses it, and bumps the profile's revision.

Takes bucket_uuid out of profile and bumps the profile's revision.

Points library at profile_uuid (nil for the Default). Its files become stale unless the new profile is the one they were placed by.

total copies split over the profile's buckets, local first: the cloud share is what the local buckets cannot take, up to the cloud buckets it has (active and enabled); the rest is local. %{copies_local: n, copies_cloud: m} with n + m == total. How a count that knows no kinds (the old redundancy setting) becomes the two.

Updates a profile's name or copy counts; a real change bumps its revision.

What each of these libraries keeps its files on, for the ones on a user's own storage (V206): %{library_uuid => %{mode: :only | :backup, bucket: Bucket.t()}}. A library on the site's storage has no entry. One query.

Functions

add_bucket(profile_uuid, bucket, opts \\ [])

@spec add_bucket(term(), PhoenixKit.Modules.Storage.Bucket.t(), keyword()) ::
  :ok | {:error, :not_found | Ecto.Changeset.t()}

Puts bucket into one of the site's profiles as a primary that stores everything, active, with no fixed write priority (the shuffled pool), served after the profile's other buckets, and bumps the profile's revision (its files are placed again). Change the role, order or status afterwards with put_bucket/4.

{:error, :not_found} for a uuid that is no site profile: a user's own profile is never one an admin adds a site bucket to. opts are put_bucket/4's (:actor_uuid, audit: false).

add_to_default(bucket)

@spec add_to_default(PhoenixKit.Modules.Storage.Bucket.t()) :: :ok

Puts a newly created bucket into the Default profile, the way every new bucket joined the pool before profiles: primary, active, no fixed write priority (it is in the shuffled pool), served after the Default's other buckets (a local one before the remote ones). The default of Storage.create_bucket/2; a caller that wants none or another profile says so there (:profile).

bucket_usage(bucket_uuids)

@spec bucket_usage([term()]) :: %{optional(String.t()) => [map()]}

Which profiles use each of bucket_uuids, and how many libraries stand behind each one: %{bucket_uuid => [usage]}, a bucket no profile uses absent from the map. Every profile row counts, whatever its role or status (draining and read_only still hold or serve objects), and a user's own profile counts too — a site bucket may be one half of their backup.

A usage is %{profile_uuid, name, is_default, owner_uuid, role, status, libraries}, the Default first. libraries counts the libraries on the profile, trashed ones included, and for the Default those that name no profile. Callers that show it must not name a user's profile or library (owner_uuid is set on the first; count the second).

This is what refuses deleting or disabling a bucket (Storage.delete_bucket/2, Storage.update_bucket/3) and what the Buckets list shows.

bump_revision(profile_uuid)

@spec bump_revision(term()) :: :ok

Bumps a profile's revision: every file it placed is stale, and the reconciler is queued to bring them up to date.

copies(storage_profile, atom)

@spec copies(PhoenixKit.Modules.Storage.StorageProfile.t(), :local | :cloud) ::
  non_neg_integer()

The copies profile wants on one kind of bucket: :local (the server's disks) or :cloud. Every file, an original and what is made from it, gets the same.

copies_advice(profile)

@spec copies_advice(PhoenixKit.Modules.Storage.StorageProfile.t()) :: %{
  writable: non_neg_integer(),
  copies: non_neg_integer(),
  local: map(),
  cloud: map()
}

What a profile's copy counts mean for the buckets it has now, for the screens that tell an admin and the one place that must agree with what they say.

A file is written to the first copies_local writable local buckets and the first copies_cloud writable cloud buckets, each in role order — primaries, then replicas, then backups — so a count says how many of them get it. For each kind (local, cloud):

  • buckets — how many can take a new file (active in the profile, enabled);
  • primaries — how many of those are primaries;
  • copies — the profile's count for it;
  • idle — the names of the replicas and backups the count never reaches (it does not exceed the primaries), which hold nothing unless a write to a primary fails.

writable is the buckets of both kinds. Takes a profile with its buckets loaded.

copies_total(storage_profile)

How many copies of a file profile wants in all: local plus cloud.

create_profile(attrs, opts \\ [])

@spec create_profile(map(), keyword()) ::
  {:ok, PhoenixKit.Modules.Storage.StorageProfile.t()}
  | {:error, Ecto.Changeset.t()}

Creates a profile with no buckets. opts (:actor_uuid, :mode) say who did it, for the history (Storage.Audit).

create_user_profile(owner_uuid, bucket, mode)

@spec create_user_profile(
  String.t(),
  PhoenixKit.Modules.Storage.Bucket.t(),
  :only | :backup
) ::
  {:ok, PhoenixKit.Modules.Storage.StorageProfile.t()}
  | {:error, :no_site_storage | :foreign_bucket | term()}

Creates the profile of a user's own storage, in one of two modes:

  • :only — the user's bucket is the profile's one primary: everything the library stores lives there.
  • :backup — the site's buckets stay and the user's bucket is a backup of every file, including sizes and tiles. The site buckets are a snapshot of the Default profile taken now: a site bucket added later is not used by this library (removing or disabling one already reaches every profile). Every file gets a copy on each writable site bucket, counted per kind, plus one cloud copy for the user's backup. There are at most 5 copies in all, so the site rows are limited to four, active first. An upload succeeds on the Default's terms, counting the site's copies only; the reconciler makes a backup copy the write missed. A site with no writable bucket has nothing to back up: {:error, :no_site_storage}.

The profile is the user's (owner_uuid), named by its own uuid (the name is never shown), and bucket must be theirs. Returns {:error, :no_site_storage} for :backup on a site with no site bucket to back up.

default?(uuid)

@spec default?(term()) :: boolean()

Whether uuid is the Default profile's.

default_copies()

@spec default_copies() :: pos_integer()

How many copies of an original the Default keeps (1 when there is no Default yet, as storage_redundancy_copies defaulted to).

default_profile()

@spec default_profile() :: PhoenixKit.Modules.Storage.StorageProfile.t() | nil

The Default profile, with its buckets.

default_uuid()

@spec default_uuid() :: String.t()

The uuid of the Default profile, fixed on every install.

delete_profile(profile, opts \\ [])

@spec delete_profile(PhoenixKit.Modules.Storage.StorageProfile.t(), keyword()) ::
  {:ok, PhoenixKit.Modules.Storage.StorageProfile.t()}
  | {:error, :default | :in_use | Ecto.Changeset.t()}

Deletes a profile. The Default cannot be deleted ({:error, :default}), nor a profile a library uses ({:error, :in_use}).

delete_user_profile(profile_uuid)

@spec delete_user_profile(term()) :: :ok | {:error, term()}

Removes a user's own profile (V206) and the buckets that were in it and are in no profile now: what is left of a user's storage once their library is purged. A site profile, or one a library still uses, is left alone.

Returns :ok, or {:error, reason} for the first bucket that could not go (one that still holds file locations cannot be deleted).

fit_copies(profile)

@spec fit_copies(PhoenixKit.Modules.Storage.StorageProfile.t()) :: %{
  copies_local: non_neg_integer(),
  copies_cloud: non_neg_integer()
}

The counts profile can actually keep: each kind's count lowered to the writable buckets (active, enabled) it has of that kind, never raised. When that leaves nothing (it wants only a kind it has no bucket of), the total is split over the buckets it has instead (split_copies/2).

for_file(file)

The profile a file's library uses.

for_library(library)

The profile library uses, with its buckets (see profile_uuid_for/1). Falls back to the Default when the library's own is gone.

get_profile(uuid)

@spec get_profile(term()) :: PhoenixKit.Modules.Storage.StorageProfile.t() | nil

A profile with its buckets, or nil (also for anything not a uuid).

key_levels_for(library)

@spec key_levels_for(PhoenixKit.Modules.Storage.Library.t() | term()) :: 0..3

The key layout (Storage.KeyLayout) uploads into library get: its profile's key_levels. The default layout when the profile cannot be read; an upload must never fail for want of one.

libraries_using(profile_uuid)

@spec libraries_using(term()) :: non_neg_integer()

How many libraries (trashed ones included) use profile_uuid. For the Default that is the libraries that name it and those that name no profile at all, which use it too (profile_uuid_for/1).

library_names_using(profile_uuid)

@spec library_names_using(term()) :: %{
  names: [String.t()],
  user_libraries: non_neg_integer()
}

The names of the site libraries on profile_uuid (the Default's include those that name no profile), and how many user libraries are on it besides. A user's library is private to its owner, so it is counted, never named.

list_profiles()

@spec list_profiles() :: [PhoenixKit.Modules.Storage.StorageProfile.t()]

The site's profiles, the Default first, each with its buckets. A user's own profile (V206) is not here: the site's profile editor and pickers read this, and a user's storage is not something an admin assigns or edits.

profile_uuid_for(library_uuid)

@spec profile_uuid_for(PhoenixKit.Modules.Storage.Library.t() | term()) :: String.t()

The uuid of the profile library uses: its own, or the Default. Takes a library, a library uuid, or nil (a file with no library is in Media).

put_bucket(profile, bucket_uuid, attrs, opts \\ [])

Adds bucket_uuid to profile, or changes how the profile uses it, and bumps the profile's revision.

remove_bucket(profile, bucket_uuid, opts \\ [])

@spec remove_bucket(PhoenixKit.Modules.Storage.StorageProfile.t(), term(), keyword()) ::
  :ok | {:error, term()}

Takes bucket_uuid out of profile and bumps the profile's revision.

set_library_profile(library, profile_uuid, opts \\ [])

@spec set_library_profile(PhoenixKit.Modules.Storage.Library.t(), term(), keyword()) ::
  {:ok, PhoenixKit.Modules.Storage.Library.t()}
  | {:error, Ecto.Changeset.t() | :not_found | :user_storage_locked}

Points library at profile_uuid (nil for the Default). Its files become stale unless the new profile is the one they were placed by.

split_copies(storage_profile, total)

@spec split_copies(PhoenixKit.Modules.Storage.StorageProfile.t(), pos_integer()) :: %{
  copies_local: non_neg_integer(),
  copies_cloud: non_neg_integer()
}

total copies split over the profile's buckets, local first: the cloud share is what the local buckets cannot take, up to the cloud buckets it has (active and enabled); the rest is local. %{copies_local: n, copies_cloud: m} with n + m == total. How a count that knows no kinds (the old redundancy setting) becomes the two.

update_profile(profile, attrs, opts \\ [])

Updates a profile's name or copy counts; a real change bumps its revision.

user_storage_for(libraries)

@spec user_storage_for([PhoenixKit.Modules.Storage.Library.t()]) :: %{
  required(String.t()) => %{
    mode: :only | :backup,
    bucket: PhoenixKit.Modules.Storage.Bucket.t()
  }
}

What each of these libraries keeps its files on, for the ones on a user's own storage (V206): %{library_uuid => %{mode: :only | :backup, bucket: Bucket.t()}}. A library on the site's storage has no entry. One query.