This repository has been archived by the owner on Jun 11, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathfile.ex
265 lines (218 loc) · 6.71 KB
/
file.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
defmodule Helix.Software.Model.File do
use Ecto.Schema
use HELL.ID, field: :file_id, meta: [0x0020]
import Ecto.Changeset
import HELL.Macros
alias Ecto.Changeset
alias HELL.Constant
alias Helix.Software.Model.Software
alias Helix.Software.Model.Storage
alias __MODULE__, as: File
@type t :: t_of_type(Software.type)
@type t_of_type(type) :: %__MODULE__{
file_id: id,
name: name,
path: path,
full_path: full_path,
file_size: size,
type: Software.Type.t,
software_type: type,
storage_id: Storage.id,
storage: term,
inserted_at: NaiveDateTime.t,
updated_at: NaiveDateTime.t,
modules: modules | File.Module.schema,
crypto_version: crypto_version
}
@type extension :: String.t
@type path :: String.t
@type full_path :: path
@type name :: String.t
@type size :: pos_integer
@type type :: Software.type
@type crypto_version :: nil | pos_integer
@type modules :: File.Module.t
@type changeset :: %Changeset{data: %__MODULE__{}}
@type creation_params :: %{
name: name,
path: path,
file_size: size,
software_type: Software.type,
storage_id: Storage.idtb
}
@type module_params :: {File.Module.name, File.Module.Data.t}
@type update_params :: %{
optional(:name) => name,
optional(:path) => path,
optional(:crypto_version) => non_neg_integer | nil
}
@creation_fields ~w/name path storage_id file_size software_type/a
@update_fields ~w/crypto_version/a
@castable_fields ~w/name path/a
@required_fields ~w/name path file_size software_type storage_id/a
@software_types Software.Type.all()
schema "files" do
field :file_id, ID,
primary_key: true
field :name, :string
field :path, :string
field :software_type, Constant
field :file_size, :integer
field :storage_id, Storage.ID
field :crypto_version, :integer
field :full_path, :string
belongs_to :type, Software.Type,
foreign_key: :software_type,
references: :type,
define_field: false
belongs_to :storage, Storage,
foreign_key: :storage_id,
references: :storage_id,
define_field: false
has_many :modules, File.Module,
foreign_key: :file_id,
references: :file_id,
on_replace: :delete
timestamps()
end
@spec create_changeset(creation_params, [module_params]) ::
changeset
@doc """
Creates the `File` changeset, as well as its modules' associations.
"""
def create_changeset(params, modules_params) do
modules = Enum.map(modules_params, &create_module_assoc/1)
%__MODULE__{}
|> cast(params, @creation_fields)
|> put_assoc(:modules, modules)
|> validate_changeset(params)
end
@spec format(t) ::
t
@doc """
Formats the given `File`. Most notably, this function makes the File.Modules
interface more friendly.
"""
def format(file) do
formatted_modules =
Enum.reduce(file.modules, %{}, fn module, acc ->
module = File.Module.format(module)
Map.merge(acc, module)
end)
%{file| modules: formatted_modules}
end
@spec set_crypto_version(t | changeset, crypto_version) ::
changeset
def set_crypto_version(struct, version) do
struct
|> cast(%{crypto_version: version}, [:crypto_version])
|> put_change(:crypto_version, version)
|> validate_changeset(%{})
|> validate_number(:crypto_version, greater_than: 0)
end
@spec update_changeset(t | Changeset.t, update_params) ::
Changeset.t
def update_changeset(struct, params) do
struct
|> cast(params, @update_fields)
|> validate_changeset(params)
|> validate_number(:crypto_version, greater_than: 0)
end
defp validate_changeset(struct, params) do
struct
|> cast(params, @castable_fields)
|> validate_required(@required_fields)
|> validate_number(:file_size, greater_than: 0)
|> validate_inclusion(:software_type, @software_types)
|> unique_constraint(:full_path, name: :files_storage_id_full_path_index)
|> update_change(:path, &add_leading_slash/1)
|> update_change(:path, &remove_trailing_slash/1)
|> prepare_changes(&update_full_path/1)
end
@spec create_module_assoc(module_params) ::
File.Module.changeset
docp """
Helper/wrapper to `File.Module.create_changeset/1`
"""
defp create_module_assoc({name, data}) do
params = %{
name: name,
version: data.version
}
File.Module.create_changeset(params)
end
docp """
Path: Path from root dir (`/`) to file directory
Full path: `path` + `File.name` + `File.extension`
"""
defp update_full_path(changeset) do
path = get_field(changeset, :path)
name = get_field(changeset, :name)
software_type = get_field(changeset, :software_type)
extension = Software.Type.get(software_type).extension
full_path = path <> "/" <> name <> "." <> extension
put_change(changeset, :full_path, full_path)
end
defp add_leading_slash(path = "/" <> _),
do: path
defp add_leading_slash(path),
do: "/" <> path
docp """
Removes the trailing slash of a path, if any. Does not apply when "/" is the
actual path.
"""
defp remove_trailing_slash(path) do
path_size = (byte_size(path) - 1) * 8
case path do
<<path::bits-size(path_size)>> <> "/" ->
<<path::bits-size(path_size)>>
path ->
path
end
end
defmodule Query do
import Ecto.Query
import HELL.Macros
alias Ecto.Queryable
alias Helix.Software.Model.File
alias Helix.Software.Model.Storage
@spec by_file(Queryable.t, File.idtb) ::
Queryable.t
def by_file(query \\ File, id) do
query
|> where([f], f.file_id == ^id)
|> join_assoc_modules()
|> preload_modules()
end
@spec by_storage(Queryable.t, Storage.idtb) ::
Queryable.t
def by_storage(query \\ File, id),
do: where(query, [f], f.storage_id == ^id)
def by_version(query \\ File, storage, module) do
query
|> by_storage(storage)
|> join_modules()
|> by_module(module)
|> order_by_version()
|> select([fm], fm.file_id)
|> limit(1)
end
def by_module(query, module_name),
do: where(query, [..., fm], fm.name == ^module_name)
def order_by_version(query),
do: order_by(query, [..., fm], desc: fm.version)
@spec not_encrypted(Queryable.t) ::
Queryable.t
def not_encrypted(query \\ File),
do: where(query, [f], is_nil(f.crypto_version))
defp join_modules(query),
do: join(query, :left, [f], fm in File.Module, fm.file_id == f.file_id)
defp join_assoc_modules(query),
do: join(query, :left, [f], fm in assoc(f, :modules))
docp """
Preloads File.Modules into the schema
"""
defp preload_modules(query),
do: preload(query, [..., m], [modules: m])
end
end