Packages
fnord
0.9.6
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/memory/global.ex
defmodule Memory.Global do
@moduledoc """
Global memory storage implementation for the `Memory` behaviour.
Memories are stored as JSON files in `~/.fnord/memory`.
"""
# ----------------------------------------------------------------------------
# Behaviour Implementation
# ----------------------------------------------------------------------------
@behaviour Memory
@impl Memory
def init() do
with {:ok, _path} <- ensure_storage_path() do
drop_old_storage()
:ok
end
end
@impl Memory
def list() do
with {:ok, files} <- File.ls(storage_path()) do
titles =
files
|> Enum.filter(&String.ends_with?(&1, ".json"))
|> Enum.map(fn file ->
path = Path.join(storage_path(), file)
case read_file(path) do
{:ok, contents} ->
case Memory.unmarshal(contents) do
{:ok, mem} when is_map(mem) and is_binary(mem.title) -> mem.title
_ -> Memory.slug_to_title(Path.rootname(file))
end
{:error, _} ->
Memory.slug_to_title(Path.rootname(file))
end
end)
|> Enum.uniq()
|> Enum.sort()
{:ok, titles}
end
end
@impl Memory
def exists?(title) do
slug = Memory.title_to_slug(title)
storage = storage_path()
base = Path.join(storage, "#{slug}.json")
# If the base file exists, attempt to read and unmarshal it. If the file
# is unreadable or contains invalid memory data, we treat the slug as taken
# (conservative duplicate detection). If it contains a memory with the same
# title, it's a duplicate. Otherwise, we continue to check suffixed files.
if File.exists?(base) do
case File.read(base) do
{:ok, contents} ->
case Memory.unmarshal(contents) do
{:ok, mem} when is_map(mem) and mem.title == title ->
true
{:ok, _mem} ->
# Base file contains a different title; check suffixed files
wildcard_check(storage, slug, title)
{:error, _} ->
# Unreadable/invalid JSON — treat as occupied
true
end
{:error, _} ->
# Can't read the file; treat as occupied
true
end
else
wildcard_check(storage, slug, title)
end
end
@impl Memory
def read(title) do
with {:ok, path} <- find_file_path_by_title(title),
{:ok, content} <- read_file(path),
{:ok, memory} <- Memory.unmarshal(content) do
{:ok, memory}
else
{:error, :not_found} = err -> err
{:error, reason} -> {:error, reason}
end
end
@impl Memory
def save(%Memory{title: title} = memory) do
with {:ok, _} <- ensure_storage_path(),
{:ok, json} <- Memory.marshal(memory) do
lockfile = Path.join(storage_path(), ".alloc.lock")
case FileLock.with_lock(lockfile, fn ->
path = allocate_unique_path_for_title(title)
write_file(path, json)
end) do
{:ok, {:ok, :ok}} -> :ok
{:ok, {:ok, {:error, reason}}} -> {:error, reason}
{:ok, {:error, reason}} -> {:error, reason}
{:error, reason} -> {:error, reason}
end
end
end
@impl Memory
def forget(title) do
with {:ok, path} <- find_file_path_by_title(title) do
rm_path(path)
end
end
@impl Memory
def is_available?(), do: true
# ----------------------------------------------------------------------------
# Internals
# ----------------------------------------------------------------------------
defp storage_path() do
Store.store_home()
|> Path.join("memory")
end
defp old_storage_path() do
Store.store_home()
|> Path.join("memories")
end
defp ensure_storage_path() do
path = storage_path()
with false <- File.exists?(path),
:ok <- File.mkdir_p(path) do
{:ok, path}
else
true -> {:ok, path}
end
end
defp drop_old_storage() do
path = old_storage_path()
if File.exists?(path) do
UI.debug("memory:global", "Removing old global memory storage at #{path}")
File.rm_rf!(path)
end
end
defp read_file(path) do
case FileLock.with_lock(path, fn -> File.read(path) end) do
{:ok, {:ok, contents}} ->
{:ok, contents}
{:ok, {:error, reason}} ->
{:error, reason}
{:error, reason} ->
{:error, reason}
end
end
defp write_file(path, json) do
FileLock.with_lock(path, fn -> File.write(path, json) end)
end
defp rm_path(path) do
case FileLock.with_lock(path, fn -> File.rm(path) end) do
{:ok, :ok} -> :ok
{:ok, {:error, reason}} -> {:error, reason}
{:error, reason} -> {:error, reason}
{:callback_error, reason} -> {:error, reason}
end
end
# Helper functions for slug disambiguation and title-based lookup
defp find_file_path_by_title(title) do
slug = Memory.title_to_slug(title)
storage = storage_path()
base = Path.join(storage, "#{slug}.json")
# Attempt base file
base_match =
if File.exists?(base) do
case File.read(base) do
{:ok, contents} ->
case Memory.unmarshal(contents) do
{:ok, mem} when is_map(mem) and mem.title == title ->
{:ok, base}
{:error, _reason} ->
# Unreadable/invalid JSON — fallback to slug-based title match
if Memory.slug_to_title(Path.rootname(base)) == title do
{:ok, base}
end
_ ->
nil
end
{:error, _reason} ->
# Can't read file — fallback to slug-based title match
if Memory.slug_to_title(Path.rootname(base)) == title do
{:ok, base}
end
end
end
# Return base match if found, else scan suffix files
if base_match do
base_match
else
# Scan suffix files
pattern = Path.join(storage, "#{slug}-*.json")
pattern
|> Path.wildcard()
|> Enum.sort()
|> Enum.find_value({:error, :not_found}, fn path ->
case File.read(path) do
{:ok, contents} ->
case Memory.unmarshal(contents) do
{:ok, mem} when is_map(mem) and mem.title == title ->
{:ok, path}
{:error, _reason} ->
# Unreadable/invalid JSON — fallback to slug-based title match
if Memory.slug_to_title(Path.rootname(path)) == title do
{:ok, path}
end
_ ->
nil
end
{:error, _reason} ->
# Can't read file — fallback to slug-based title match
if Memory.slug_to_title(Path.rootname(path)) == title do
{:ok, path}
end
end
end)
end
end
defp allocate_unique_path_for_title(title) do
storage = storage_path()
allocate_unique_path_for_title(title, storage)
end
defp allocate_unique_path_for_title(title, storage) do
slug = Memory.title_to_slug(title)
base = Path.join(storage, "#{slug}.json")
if not File.exists?(base) do
base
else
case File.read(base) do
{:ok, contents} ->
case Memory.unmarshal(contents) do
{:ok, mem} when is_map(mem) and mem.title == title ->
# Overwrite the existing base file for the same title (migration/updates)
base
_ ->
# Find next available suffix index
wildcard = Path.join(storage, "#{slug}-*.json")
next_index =
wildcard
|> Path.wildcard()
|> Enum.map(&Path.basename(&1, ".json"))
|> Enum.map(fn name -> String.replace_prefix(name, "#{slug}-", "") end)
|> Enum.filter(&(&1 != ""))
|> Enum.map(fn s ->
case Integer.parse(s) do
{i, _} -> i
:error -> 0
end
end)
|> Enum.filter(&(&1 > 0))
|> case do
[] -> 1
numbers -> Enum.max(numbers) + 1
end
generate_suffixed_path(storage, slug, next_index)
end
{:error, _} ->
# Can't read existing base file; overwrite it
base
end
end
end
defp generate_suffixed_path(storage, slug, index) do
Path.join(storage, "#{slug}-#{index}.json")
end
defp wildcard_check(storage, slug, title) do
pattern = Path.join(storage, "#{slug}-*.json")
Path.wildcard(pattern)
|> Enum.any?(fn path ->
case File.read(path) do
{:ok, contents} ->
case Memory.unmarshal(contents) do
{:ok, mem} when is_map(mem) and mem.title == title -> true
_ -> false
end
{:error, _} ->
false
end
end)
end
end