mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-30 14:57:59 +01:00
198 lines
5.7 KiB
Elixir
198 lines
5.7 KiB
Elixir
defmodule Philomena.DuplicateReports do
|
|
@moduledoc """
|
|
The DuplicateReports context.
|
|
"""
|
|
|
|
import Ecto.Query, warn: false
|
|
alias Ecto.Multi
|
|
alias Philomena.Repo
|
|
|
|
alias Philomena.DuplicateReports.DuplicateReport
|
|
alias Philomena.ImageIntensities.ImageIntensity
|
|
alias Philomena.Images.Image
|
|
alias Philomena.Images
|
|
|
|
def generate_reports(source) do
|
|
source = Repo.preload(source, :intensity)
|
|
|
|
duplicates_of(source.intensity, source.image_aspect_ratio, 0.2, 0.05)
|
|
|> where([i, _it], i.id != ^source.id)
|
|
|> where([i, _it], i.duplication_checked != true)
|
|
|> Repo.all()
|
|
|> Enum.map(fn target ->
|
|
create_duplicate_report(source, target, %{}, %{
|
|
"reason" => "Automated Perceptual dedupe match"
|
|
})
|
|
end)
|
|
end
|
|
|
|
def duplicates_of(intensities, aspect_ratio, dist \\ 0.25, aspect_dist \\ 0.05) do
|
|
from i in Image,
|
|
inner_join: it in ImageIntensity,
|
|
on: it.image_id == i.id,
|
|
where: it.nw >= ^(intensities.nw - dist) and it.nw <= ^(intensities.nw + dist),
|
|
where: it.ne >= ^(intensities.ne - dist) and it.ne <= ^(intensities.ne + dist),
|
|
where: it.sw >= ^(intensities.sw - dist) and it.sw <= ^(intensities.sw + dist),
|
|
where: it.se >= ^(intensities.se - dist) and it.se <= ^(intensities.se + dist),
|
|
where:
|
|
i.image_aspect_ratio >= ^(aspect_ratio - aspect_dist) and
|
|
i.image_aspect_ratio <= ^(aspect_ratio + aspect_dist),
|
|
limit: 20
|
|
end
|
|
|
|
@doc """
|
|
Gets a single duplicate_report.
|
|
|
|
Raises `Ecto.NoResultsError` if the Duplicate report does not exist.
|
|
|
|
## Examples
|
|
|
|
iex> get_duplicate_report!(123)
|
|
%DuplicateReport{}
|
|
|
|
iex> get_duplicate_report!(456)
|
|
** (Ecto.NoResultsError)
|
|
|
|
"""
|
|
def get_duplicate_report!(id), do: Repo.get!(DuplicateReport, id)
|
|
|
|
@doc """
|
|
Creates a duplicate_report.
|
|
|
|
## Examples
|
|
|
|
iex> create_duplicate_report(%{field: value})
|
|
{:ok, %DuplicateReport{}}
|
|
|
|
iex> create_duplicate_report(%{field: bad_value})
|
|
{:error, %Ecto.Changeset{}}
|
|
|
|
"""
|
|
def create_duplicate_report(source, target, attribution, attrs \\ %{}) do
|
|
%DuplicateReport{image_id: source.id, duplicate_of_image_id: target.id}
|
|
|> DuplicateReport.creation_changeset(attrs, attribution)
|
|
|> Repo.insert()
|
|
end
|
|
|
|
# TODO: can we get this in a single transaction?
|
|
def accept_duplicate_report(%DuplicateReport{} = duplicate_report, user) do
|
|
changeset =
|
|
duplicate_report
|
|
|> DuplicateReport.accept_changeset(user)
|
|
|
|
Multi.new()
|
|
|> Multi.update(:duplicate_report, changeset)
|
|
|> Multi.run(:other_reports, fn repo, %{duplicate_report: duplicate_report} ->
|
|
{count, nil} =
|
|
DuplicateReport
|
|
|> where(
|
|
[dr],
|
|
(dr.image_id == ^duplicate_report.image_id and
|
|
dr.duplicate_of_image_id == ^duplicate_report.duplicate_of_image_id) or
|
|
(dr.image_id == ^duplicate_report.duplicate_of_image_id and
|
|
dr.duplicate_of_image_id == ^duplicate_report.image_id)
|
|
)
|
|
|> where([dr], dr.id != ^duplicate_report.id)
|
|
|> repo.update_all(set: [state: "rejected"])
|
|
|
|
{:ok, count}
|
|
end)
|
|
|> Repo.isolated_transaction(:serializable)
|
|
|> case do
|
|
{:ok, %{duplicate_report: duplicate_report}} ->
|
|
duplicate_report = Repo.preload(duplicate_report, [:image, :duplicate_of_image])
|
|
|
|
Images.merge_image(duplicate_report.image, duplicate_report.duplicate_of_image)
|
|
|
|
error ->
|
|
error
|
|
end
|
|
end
|
|
|
|
def accept_reverse_duplicate_report(%DuplicateReport{} = duplicate_report, user) do
|
|
{:ok, duplicate_report} = reject_duplicate_report(duplicate_report, user)
|
|
|
|
# Need a constraint for upsert, so have to do it the hard way
|
|
new_report =
|
|
DuplicateReport
|
|
|> where(duplicate_of_image_id: ^duplicate_report.image_id)
|
|
|> limit(1)
|
|
|> Repo.one()
|
|
|
|
new_report =
|
|
if new_report do
|
|
new_report
|
|
else
|
|
{:ok, duplicate_report} =
|
|
%DuplicateReport{
|
|
image_id: duplicate_report.duplicate_of_image_id,
|
|
duplicate_of_image_id: duplicate_report.image_id,
|
|
reason: Enum.join([duplicate_report.reason, "(Reverse accepted)"], "\n"),
|
|
user_id: user.id
|
|
}
|
|
|> DuplicateReport.changeset(%{})
|
|
|> Repo.insert()
|
|
|
|
duplicate_report
|
|
end
|
|
|
|
accept_duplicate_report(new_report, user)
|
|
end
|
|
|
|
def claim_duplicate_report(%DuplicateReport{} = duplicate_report, user) do
|
|
duplicate_report
|
|
|> DuplicateReport.claim_changeset(user)
|
|
|> Repo.update()
|
|
end
|
|
|
|
def unclaim_duplicate_report(%DuplicateReport{} = duplicate_report) do
|
|
duplicate_report
|
|
|> DuplicateReport.unclaim_changeset()
|
|
|> Repo.update()
|
|
end
|
|
|
|
def reject_duplicate_report(%DuplicateReport{} = duplicate_report, user) do
|
|
duplicate_report
|
|
|> DuplicateReport.reject_changeset(user)
|
|
|> Repo.update()
|
|
end
|
|
|
|
@doc """
|
|
Deletes a DuplicateReport.
|
|
|
|
## Examples
|
|
|
|
iex> delete_duplicate_report(duplicate_report)
|
|
{:ok, %DuplicateReport{}}
|
|
|
|
iex> delete_duplicate_report(duplicate_report)
|
|
{:error, %Ecto.Changeset{}}
|
|
|
|
"""
|
|
def delete_duplicate_report(%DuplicateReport{} = duplicate_report) do
|
|
Repo.delete(duplicate_report)
|
|
end
|
|
|
|
@doc """
|
|
Returns an `%Ecto.Changeset{}` for tracking duplicate_report changes.
|
|
|
|
## Examples
|
|
|
|
iex> change_duplicate_report(duplicate_report)
|
|
%Ecto.Changeset{source: %DuplicateReport{}}
|
|
|
|
"""
|
|
def change_duplicate_report(%DuplicateReport{} = duplicate_report) do
|
|
DuplicateReport.changeset(duplicate_report, %{})
|
|
end
|
|
|
|
def count_duplicate_reports(user) do
|
|
if Canada.Can.can?(user, :index, DuplicateReport) do
|
|
DuplicateReport
|
|
|> where(state: "open")
|
|
|> Repo.aggregate(:count, :id)
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
end
|