mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-11-30 04:53:24 +00:00
Compare commits
66 Commits
v1.65.19
...
tracking-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27b5694885 | ||
|
|
4093f28cee | ||
|
|
08aaf2f2dd | ||
|
|
df955ff8b0 | ||
|
|
4dc74022b4 | ||
|
|
c2b7d07208 | ||
|
|
21e76a6f05 | ||
|
|
89c0d6fad6 | ||
|
|
b74d15b1ee | ||
|
|
10313438bf | ||
|
|
a764217948 | ||
|
|
d81d6fb937 | ||
|
|
4c0f7ab7f9 | ||
|
|
1c48945a96 | ||
|
|
850901f62f | ||
|
|
4822854e30 | ||
|
|
f580538331 | ||
|
|
0d70c555e6 | ||
|
|
c5f6cf0080 | ||
|
|
6ff7b3bc9a | ||
|
|
346c2c65b0 | ||
|
|
a6445fd500 | ||
|
|
358e43e508 | ||
|
|
20c5ba6b63 | ||
|
|
661658a6e8 | ||
|
|
0a6f224ed3 | ||
|
|
e7bb29693f | ||
|
|
32dfd50461 | ||
|
|
bfec385dce | ||
|
|
04278f99d7 | ||
|
|
9d3db19dc1 | ||
|
|
3953e33f37 | ||
|
|
611fdd56d0 | ||
|
|
36a4fd0f35 | ||
|
|
488984a988 | ||
|
|
1b183d6e58 | ||
|
|
3dcb6d30b5 | ||
|
|
1d11788f89 | ||
|
|
d3822128ab | ||
|
|
6ac7836505 | ||
|
|
5796fccae4 | ||
|
|
22ab6e544c | ||
|
|
5e735ab8bd | ||
|
|
450139402d | ||
|
|
1e0d4f1fde | ||
|
|
7e9b1af3a3 | ||
|
|
30b90cd4be | ||
|
|
f28affa222 | ||
|
|
2ae7b187b8 | ||
|
|
ad037be1f4 | ||
|
|
9e31542c5f | ||
|
|
aae6ed0cb3 | ||
|
|
7ea2ecb8e9 | ||
|
|
852fc28896 | ||
|
|
fcdab79802 | ||
|
|
bb0e06589f | ||
|
|
d65b72c4dd | ||
|
|
24a3d5b3de | ||
|
|
2814b46941 | ||
|
|
6ffc25448d | ||
|
|
d9a82f7c9f | ||
|
|
9a8106947e | ||
|
|
e21ca79ea1 | ||
|
|
dd579caeac | ||
|
|
ddf8a4b9fb | ||
|
|
3c04f4194e |
173
CHANGELOG.md
173
CHANGELOG.md
@@ -2,6 +2,179 @@
|
||||
|
||||
<!-- changelog -->
|
||||
|
||||
## [v1.66.15](https://github.com/wanderer-industries/wanderer/compare/v1.66.14...v1.66.15) (2025-06-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Added back arm docker image build
|
||||
|
||||
## [v1.66.14](https://github.com/wanderer-industries/wanderer/compare/v1.66.13...v1.66.14) (2025-06-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: fixed online updates
|
||||
|
||||
## [v1.66.13](https://github.com/wanderer-industries/wanderer/compare/v1.66.12...v1.66.13) (2025-06-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: fixed location tracking issues
|
||||
|
||||
## [v1.66.12](https://github.com/wanderer-industries/wanderer/compare/v1.66.11...v1.66.12) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.66.11](https://github.com/wanderer-industries/wanderer/compare/v1.66.10...v1.66.11) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: fixed refresh character tokens
|
||||
|
||||
## [v1.66.10](https://github.com/wanderer-industries/wanderer/compare/v1.66.9...v1.66.10) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.66.9](https://github.com/wanderer-industries/wanderer/compare/v1.66.8...v1.66.9) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.66.8](https://github.com/wanderer-industries/wanderer/compare/v1.66.7...v1.66.8) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* fixed disable detailed kills env check
|
||||
|
||||
## [v1.66.7](https://github.com/wanderer-industries/wanderer/compare/v1.66.6...v1.66.7) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* fixed disable detailed kills env check
|
||||
|
||||
## [v1.66.6](https://github.com/wanderer-industries/wanderer/compare/v1.66.5...v1.66.6) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* respect error limits for ESI APIs
|
||||
|
||||
## [v1.66.5](https://github.com/wanderer-industries/wanderer/compare/v1.66.4...v1.66.5) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* respect error limits for ESI APIs
|
||||
|
||||
## [v1.66.4](https://github.com/wanderer-industries/wanderer/compare/v1.66.3...v1.66.4) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* respect error limits for ESI APIs
|
||||
|
||||
## [v1.66.3](https://github.com/wanderer-industries/wanderer/compare/v1.66.2...v1.66.3) (2025-06-05)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.66.2](https://github.com/wanderer-industries/wanderer/compare/v1.66.1...v1.66.2) (2025-06-05)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.66.1](https://github.com/wanderer-industries/wanderer/compare/v1.66.0...v1.66.1) (2025-06-05)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* remove bugs with signature deletion
|
||||
|
||||
## [v1.66.0](https://github.com/wanderer-industries/wanderer/compare/v1.65.24...v1.66.0) (2025-06-05)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* show deleted signatures during undo timer
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* remove callbacks from effect dependencies
|
||||
|
||||
## [v1.65.24](https://github.com/wanderer-industries/wanderer/compare/v1.65.23...v1.65.24) (2025-06-04)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed errors duration check to reduce requests amount to ESI
|
||||
|
||||
## [v1.65.23](https://github.com/wanderer-industries/wanderer/compare/v1.65.22...v1.65.23) (2025-06-04)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Added back arm docker image build
|
||||
|
||||
## [v1.65.22](https://github.com/wanderer-industries/wanderer/compare/v1.65.21...v1.65.22) (2025-06-04)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fix character tracking issues
|
||||
|
||||
## [v1.65.21](https://github.com/wanderer-industries/wanderer/compare/v1.65.20...v1.65.21) (2025-06-01)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fix connection pool errors
|
||||
|
||||
## [v1.65.20](https://github.com/wanderer-industries/wanderer/compare/v1.65.19...v1.65.20) (2025-06-01)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: fix waypoint set timeout errors
|
||||
|
||||
## [v1.65.19](https://github.com/wanderer-industries/wanderer/compare/v1.65.18...v1.65.19) (2025-06-01)
|
||||
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { useCallback } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { useAutoAnimate } from '@formkit/auto-animate/react';
|
||||
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { CharacterTypeRaw } from '@/hooks/Mapper/types';
|
||||
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import classes from './Characters.module.scss';
|
||||
import { isDocked } from '@/hooks/Mapper/helpers/isDocked.ts';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { CharacterTypeRaw } from '@/hooks/Mapper/types';
|
||||
import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { useAutoAnimate } from '@formkit/auto-animate/react';
|
||||
import clsx from 'clsx';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import classes from './Characters.module.scss';
|
||||
interface CharactersProps {
|
||||
data: CharacterTypeRaw[];
|
||||
}
|
||||
@@ -17,13 +16,22 @@ export const Characters = ({ data }: CharactersProps) => {
|
||||
const [parent] = useAutoAnimate();
|
||||
|
||||
const {
|
||||
outCommand,
|
||||
data: { mainCharacterEveId, followingCharacterEveId },
|
||||
} = useMapRootState();
|
||||
|
||||
const handleSelect = useCallback((character: CharacterTypeRaw) => {
|
||||
const handleSelect = useCallback(async (character: CharacterTypeRaw) => {
|
||||
if (!character) {
|
||||
return;
|
||||
}
|
||||
|
||||
await outCommand({
|
||||
type: OutCommand.startTracking,
|
||||
data: { character_eve_id: character.eve_id },
|
||||
});
|
||||
emitMapEvent({
|
||||
name: Commands.centerSystem,
|
||||
data: character?.location?.solar_system_id?.toString(),
|
||||
data: character.location?.solar_system_id?.toString(),
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -37,14 +45,26 @@ export const Characters = ({ data }: CharactersProps) => {
|
||||
className={clsx(
|
||||
'overflow-hidden relative',
|
||||
'flex w-[35px] h-[35px] rounded-[4px] border-[1px] border-solid bg-transparent cursor-pointer',
|
||||
'transition-colors duration-250',
|
||||
'transition-colors duration-250 hover:bg-stone-300/90',
|
||||
{
|
||||
['border-stone-800/90']: !character.online,
|
||||
['border-lime-600/70']: character.online,
|
||||
},
|
||||
)}
|
||||
title={character.name}
|
||||
title={character.tracking_paused ? `${character.name} - Tracking Paused (click to resume)` : character.name}
|
||||
>
|
||||
{character.tracking_paused && (
|
||||
<>
|
||||
<span
|
||||
className={clsx(
|
||||
'absolute top-[2px] left-[2px] w-[9px] h-[9px]',
|
||||
'text-yellow-500 text-[9px] rounded-[1px] z-10 hover:hidden',
|
||||
'pi',
|
||||
PrimeIcons.PAUSE,
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{mainCharacterEveId === character.eve_id && (
|
||||
<span
|
||||
className={clsx(
|
||||
@@ -55,6 +75,7 @@ export const Characters = ({ data }: CharactersProps) => {
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{followingCharacterEveId === character.eve_id && (
|
||||
<span
|
||||
className={clsx(
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import {
|
||||
CommandCharacterAdded,
|
||||
CommandCharacterRemoved,
|
||||
@@ -7,6 +6,7 @@ import {
|
||||
CommandCharacterUpdated,
|
||||
CommandPresentCharacters,
|
||||
} from '@/hooks/Mapper/types';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
export const useCommandsCharacters = () => {
|
||||
const { update } = useMapState();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useReactFlow } from 'reactflow';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandInit } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { convertConnection2Edge, convertSystem2Node } from '../../helpers';
|
||||
import { MapData, useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
import { CommandInit } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useReactFlow } from 'reactflow';
|
||||
import { convertConnection2Edge, convertSystem2Node } from '../../helpers';
|
||||
|
||||
export const useMapInit = () => {
|
||||
const rf = useReactFlow();
|
||||
|
||||
@@ -12,10 +12,10 @@ import {
|
||||
SIGNATURE_SETTING_STORE_KEY,
|
||||
SIGNATURE_WINDOW_ID,
|
||||
SignatureSettingsType,
|
||||
SIGNATURES_DELETION_TIMING,
|
||||
SIGNATURE_DELETION_TIMEOUTS,
|
||||
getDeletionTimeoutMs,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
|
||||
|
||||
/**
|
||||
* Custom hook for managing pending signature deletions and undo countdown.
|
||||
@@ -27,16 +27,32 @@ function useSignatureUndo(
|
||||
) {
|
||||
const [countdown, setCountdown] = useState<number>(0);
|
||||
const [pendingIds, setPendingIds] = useState<Set<string>>(new Set());
|
||||
const [deletedSignatures, setDeletedSignatures] = useState<ExtendedSystemSignature[]>([]);
|
||||
const intervalRef = useRef<number | null>(null);
|
||||
|
||||
const addDeleted = useCallback((ids: string[]) => {
|
||||
const addDeleted = useCallback((signatures: ExtendedSystemSignature[]) => {
|
||||
const newIds = signatures.map(sig => sig.eve_id);
|
||||
setPendingIds(prev => {
|
||||
const next = new Set(prev);
|
||||
ids.forEach(id => next.add(id));
|
||||
newIds.forEach(id => next.add(id));
|
||||
return next;
|
||||
});
|
||||
setDeletedSignatures(prev => [...prev, ...signatures]);
|
||||
}, []);
|
||||
|
||||
// Clear deleted signatures when system changes
|
||||
useEffect(() => {
|
||||
if (systemId) {
|
||||
setDeletedSignatures([]);
|
||||
setPendingIds(new Set());
|
||||
setCountdown(0);
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
}
|
||||
}, [systemId]);
|
||||
|
||||
// kick off or clear countdown whenever pendingIds changes
|
||||
useEffect(() => {
|
||||
// clear any existing timer
|
||||
@@ -47,13 +63,13 @@ function useSignatureUndo(
|
||||
|
||||
if (pendingIds.size === 0) {
|
||||
setCountdown(0);
|
||||
setDeletedSignatures([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// determine timeout from settings
|
||||
const timingKey = Number(settings[SETTINGS_KEYS.DELETION_TIMING] ?? SIGNATURES_DELETION_TIMING.DEFAULT);
|
||||
const timeoutMs =
|
||||
Number(SIGNATURE_DELETION_TIMEOUTS[timingKey as keyof typeof SIGNATURE_DELETION_TIMEOUTS]) || 10000;
|
||||
const timeoutMs = getDeletionTimeoutMs(settings);
|
||||
|
||||
setCountdown(Math.ceil(timeoutMs / 1000));
|
||||
|
||||
// start new interval
|
||||
@@ -63,6 +79,7 @@ function useSignatureUndo(
|
||||
clearInterval(intervalRef.current!);
|
||||
intervalRef.current = null;
|
||||
setPendingIds(new Set());
|
||||
setDeletedSignatures([]);
|
||||
return 0;
|
||||
}
|
||||
return prev - 1;
|
||||
@@ -75,7 +92,7 @@ function useSignatureUndo(
|
||||
intervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [pendingIds, settings[SETTINGS_KEYS.DELETION_TIMING]]);
|
||||
}, [pendingIds, settings]);
|
||||
|
||||
// undo handler
|
||||
const handleUndo = useCallback(async () => {
|
||||
@@ -85,6 +102,7 @@ function useSignatureUndo(
|
||||
data: { system_id: systemId, eve_ids: Array.from(pendingIds) },
|
||||
});
|
||||
setPendingIds(new Set());
|
||||
setDeletedSignatures([]);
|
||||
setCountdown(0);
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
@@ -95,6 +113,7 @@ function useSignatureUndo(
|
||||
return {
|
||||
pendingIds,
|
||||
countdown,
|
||||
deletedSignatures,
|
||||
addDeleted,
|
||||
handleUndo,
|
||||
};
|
||||
@@ -118,7 +137,11 @@ export const SystemSignatures = () => {
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
const isSystemSelected = useMemo(() => selectedSystems.length === 1, [selectedSystems.length]);
|
||||
const { pendingIds, countdown, addDeleted, handleUndo } = useSignatureUndo(systemId, currentSettings, outCommand);
|
||||
const { pendingIds, countdown, deletedSignatures, addDeleted, handleUndo } = useSignatureUndo(
|
||||
systemId,
|
||||
currentSettings,
|
||||
outCommand,
|
||||
);
|
||||
|
||||
useHotkey(true, ['z', 'Z'], (event: KeyboardEvent) => {
|
||||
if (pendingIds.size > 0 && countdown > 0) {
|
||||
@@ -175,6 +198,7 @@ export const SystemSignatures = () => {
|
||||
<SystemSignaturesContent
|
||||
systemId={systemId}
|
||||
settings={currentSettings}
|
||||
deletedSignatures={deletedSignatures}
|
||||
onLazyDeleteChange={handleLazyDeleteToggle}
|
||||
onCountChange={handleCountChange}
|
||||
onSignatureDeleted={addDeleted}
|
||||
|
||||
@@ -58,7 +58,8 @@ interface SystemSignaturesContentProps {
|
||||
onLazyDeleteChange?: (value: boolean) => void;
|
||||
onCountChange?: (count: number) => void;
|
||||
filterSignature?: (signature: SystemSignature) => boolean;
|
||||
onSignatureDeleted?: (deletedIds: string[]) => void;
|
||||
onSignatureDeleted?: (deletedSignatures: ExtendedSystemSignature[]) => void;
|
||||
deletedSignatures?: ExtendedSystemSignature[];
|
||||
}
|
||||
|
||||
export const SystemSignaturesContent = ({
|
||||
@@ -71,6 +72,7 @@ export const SystemSignaturesContent = ({
|
||||
onCountChange,
|
||||
filterSignature,
|
||||
onSignatureDeleted,
|
||||
deletedSignatures = [],
|
||||
}: SystemSignaturesContentProps) => {
|
||||
const [selectedSignatureForDialog, setSelectedSignatureForDialog] = useState<SystemSignature | null>(null);
|
||||
const [showSignatureSettings, setShowSignatureSettings] = useState(false);
|
||||
@@ -126,10 +128,8 @@ export const SystemSignaturesContent = ({
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (onSignatureDeleted && selectedSignatures.length > 0) {
|
||||
const deletedIds = selectedSignatures.map(s => s.eve_id);
|
||||
onSignatureDeleted(deletedIds);
|
||||
}
|
||||
|
||||
// Delete key should always immediately delete, never show pending deletions
|
||||
handleDeleteSelected();
|
||||
});
|
||||
|
||||
@@ -158,9 +158,16 @@ export const SystemSignaturesContent = ({
|
||||
|
||||
const handleSelectSignatures = useCallback(
|
||||
(e: { value: SystemSignature[] }) => {
|
||||
selectable ? onSelect?.(e.value[0]) : setSelectedSignatures(e.value as ExtendedSystemSignature[]);
|
||||
// Filter out deleted signatures from selection
|
||||
const selectableSignatures = e.value.filter(
|
||||
sig => !deletedSignatures.some(deleted => deleted.eve_id === sig.eve_id),
|
||||
);
|
||||
|
||||
selectable
|
||||
? onSelect?.(selectableSignatures[0])
|
||||
: setSelectedSignatures(selectableSignatures as ExtendedSystemSignature[]);
|
||||
},
|
||||
[onSelect, selectable, setSelectedSignatures],
|
||||
[onSelect, selectable, setSelectedSignatures, deletedSignatures],
|
||||
);
|
||||
|
||||
const { showDescriptionColumn, showUpdatedColumn, showCharacterColumn, showCharacterPortrait } = useMemo(
|
||||
@@ -174,7 +181,11 @@ export const SystemSignaturesContent = ({
|
||||
);
|
||||
|
||||
const filteredSignatures = useMemo<ExtendedSystemSignature[]>(() => {
|
||||
return signatures.filter(sig => {
|
||||
// Get the set of deleted signature IDs for quick lookup
|
||||
const deletedIds = new Set(deletedSignatures.map(sig => sig.eve_id));
|
||||
|
||||
// Common filter function
|
||||
const shouldShowSignature = (sig: ExtendedSystemSignature): boolean => {
|
||||
if (filterSignature && !filterSignature(sig)) {
|
||||
return false;
|
||||
}
|
||||
@@ -203,9 +214,27 @@ export const SystemSignaturesContent = ({
|
||||
return true;
|
||||
}
|
||||
|
||||
return settings[sig.kind];
|
||||
return settings[sig.kind] as boolean;
|
||||
};
|
||||
|
||||
// Filter active signatures, excluding any that are in the deleted list
|
||||
const activeSignatures = signatures.filter(sig => {
|
||||
// Skip if this signature is in the deleted list
|
||||
if (deletedIds.has(sig.eve_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return shouldShowSignature(sig);
|
||||
});
|
||||
}, [signatures, hideLinkedSignatures, settings, filterSignature]);
|
||||
|
||||
// Add deleted signatures with pending deletion flag, applying the same filters
|
||||
const deletedWithPendingFlag = deletedSignatures.filter(shouldShowSignature).map(sig => ({
|
||||
...sig,
|
||||
pendingDeletion: true,
|
||||
}));
|
||||
|
||||
return [...activeSignatures, ...deletedWithPendingFlag];
|
||||
}, [signatures, hideLinkedSignatures, settings, filterSignature, deletedSignatures]);
|
||||
|
||||
const onRowMouseEnter = useCallback((e: DataTableRowMouseEvent) => {
|
||||
setHoveredSignature(e.data as SystemSignature);
|
||||
@@ -249,7 +278,8 @@ export const SystemSignaturesContent = ({
|
||||
{hasUnsupportedLanguage && (
|
||||
<div className="w-full flex justify-center items-center text-amber-500 text-xs p-1 bg-amber-950/20 border-b border-amber-800/30">
|
||||
<i className={PrimeIcons.EXCLAMATION_TRIANGLE + ' mr-1'} />
|
||||
Non-English signatures detected. Some signatures may not display correctly. Double-click to edit signature details.
|
||||
Non-English signatures detected. Some signatures may not display correctly. Double-click to edit signature
|
||||
details.
|
||||
</div>
|
||||
)}
|
||||
<DataTable
|
||||
|
||||
@@ -148,7 +148,8 @@ export enum SIGNATURES_DELETION_TIMING {
|
||||
EXTENDED,
|
||||
}
|
||||
|
||||
export type SignatureDeletionTimingType = { [key in SIGNATURES_DELETION_TIMING]?: unknown };
|
||||
// Now use a stricter type: every timing key maps to a number
|
||||
export type SignatureDeletionTimingType = Record<SIGNATURES_DELETION_TIMING, number>;
|
||||
|
||||
export const SIGNATURE_SETTINGS = {
|
||||
filterFlags: [
|
||||
@@ -219,12 +220,28 @@ export const SETTINGS_VALUES: SignatureSettingsType = {
|
||||
[SETTINGS_KEYS.COMBAT_SITE]: true,
|
||||
};
|
||||
|
||||
// Now this map is strongly typed as “number” for each timing enum
|
||||
export const SIGNATURE_DELETION_TIMEOUTS: SignatureDeletionTimingType = {
|
||||
[SIGNATURES_DELETION_TIMING.DEFAULT]: 10_000,
|
||||
[SIGNATURES_DELETION_TIMING.IMMEDIATE]: 0,
|
||||
[SIGNATURES_DELETION_TIMING.DEFAULT]: 10_000,
|
||||
[SIGNATURES_DELETION_TIMING.EXTENDED]: 30_000,
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to extract the deletion timeout in milliseconds from settings
|
||||
*/
|
||||
export function getDeletionTimeoutMs(settings: SignatureSettingsType): number {
|
||||
const raw = settings[SETTINGS_KEYS.DELETION_TIMING];
|
||||
const timing =
|
||||
raw && typeof raw === 'object' && 'value' in raw
|
||||
? (raw as { value: SIGNATURES_DELETION_TIMING }).value
|
||||
: (raw as SIGNATURES_DELETION_TIMING | undefined);
|
||||
|
||||
const validTiming = typeof timing === 'number' ? timing : SIGNATURES_DELETION_TIMING.DEFAULT;
|
||||
|
||||
return SIGNATURE_DELETION_TIMEOUTS[validTiming];
|
||||
}
|
||||
|
||||
// Replace the flat structure with a nested structure by language
|
||||
export const LANGUAGE_TYPE_MAPPINGS = {
|
||||
EN: {
|
||||
|
||||
@@ -5,7 +5,10 @@ import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import useRefState from 'react-usestateref';
|
||||
|
||||
import { SETTINGS_KEYS } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import {
|
||||
SETTINGS_KEYS,
|
||||
getDeletionTimeoutMs,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { getActualSigs } from '../helpers';
|
||||
import { UseSystemSignaturesDataProps } from './types';
|
||||
@@ -20,7 +23,7 @@ export const useSystemSignaturesData = ({
|
||||
onLazyDeleteChange,
|
||||
onSignatureDeleted,
|
||||
}: Omit<UseSystemSignaturesDataProps, 'deletionTiming'> & {
|
||||
onSignatureDeleted?: (deletedIds: string[]) => void;
|
||||
onSignatureDeleted?: (deletedSignatures: ExtendedSystemSignature[]) => void;
|
||||
}) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
const [signatures, setSignatures, signaturesRef] = useRefState<ExtendedSystemSignature[]>([]);
|
||||
@@ -74,9 +77,16 @@ export const useSystemSignaturesData = ({
|
||||
|
||||
if (removed.length > 0) {
|
||||
await processRemovedSignatures(removed, added, updated);
|
||||
if (onSignatureDeleted) {
|
||||
const deletedIds = removed.map(sig => sig.eve_id);
|
||||
onSignatureDeleted(deletedIds);
|
||||
|
||||
// Only show pending deletions if:
|
||||
// 1. Lazy deletion is enabled AND
|
||||
// 2. Deletion timing is not immediate (> 0)
|
||||
if (onSignatureDeleted && lazyDeleteValue) {
|
||||
const timeoutMs = getDeletionTimeoutMs(settings);
|
||||
|
||||
if (timeoutMs > 0) {
|
||||
onSignatureDeleted(removed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,11 +112,18 @@ export const useSystemSignaturesData = ({
|
||||
|
||||
const handleDeleteSelected = useCallback(async () => {
|
||||
if (!selectedSignatures.length) return;
|
||||
|
||||
const selectedIds = selectedSignatures.map(s => s.eve_id);
|
||||
const finalList = signatures.filter(s => !selectedIds.includes(s.eve_id));
|
||||
|
||||
// IMPORTANT: Send deletion to server BEFORE updating local state
|
||||
// Otherwise signaturesRef.current will be updated and getActualSigs won't detect removals
|
||||
await handleUpdateSignatures(finalList, false, true);
|
||||
|
||||
// Update local state after server call
|
||||
setSignatures(finalList);
|
||||
setSelectedSignatures([]);
|
||||
}, [handleUpdateSignatures, selectedSignatures, signatures]);
|
||||
}, [handleUpdateSignatures, selectedSignatures, signatures, setSignatures]);
|
||||
|
||||
const handleSelectAll = useCallback(() => {
|
||||
setSelectedSignatures(signatures);
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Characters } from '../characters/Characters';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMemo } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { sortOnlineFunc } from '@/hooks/Mapper/components/hooks/useGetOwnOnlineCharacters.ts';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
|
||||
import { Button } from 'primereact/button';
|
||||
import clsx from 'clsx';
|
||||
import { useMemo } from 'react';
|
||||
import { Characters } from '../characters/Characters';
|
||||
|
||||
const Topbar = ({ children }: WithChildren) => {
|
||||
const {
|
||||
data: { characters, userCharacters, pings },
|
||||
data: { characters, userCharacters },
|
||||
} = useMapRootState();
|
||||
|
||||
const charsToShow = useMemo(() => {
|
||||
|
||||
@@ -33,6 +33,7 @@ export type CharacterTypeRaw = {
|
||||
corporation_id: number;
|
||||
corporation_name: string;
|
||||
corporation_ticker: string;
|
||||
tracking_paused: boolean;
|
||||
};
|
||||
|
||||
export interface TrackingCharacter {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { SolarSystemRawType, SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types/system.ts';
|
||||
import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
|
||||
import { WormholeDataRaw } from '@/hooks/Mapper/types/wormholes.ts';
|
||||
import { ActivitySummary, CharacterTypeRaw, TrackingCharacter } from '@/hooks/Mapper/types/character.ts';
|
||||
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
|
||||
import { DetailedKill, Kill } from '@/hooks/Mapper/types/kills.ts';
|
||||
import { CommentType, PingData, SystemSignature, UserPermissions } from '@/hooks/Mapper/types';
|
||||
import { ActivitySummary, CharacterTypeRaw, TrackingCharacter } from '@/hooks/Mapper/types/character.ts';
|
||||
import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
|
||||
import { DetailedKill, Kill } from '@/hooks/Mapper/types/kills.ts';
|
||||
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
|
||||
import { SolarSystemRawType, SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types/system.ts';
|
||||
import { WormholeDataRaw } from '@/hooks/Mapper/types/wormholes.ts';
|
||||
|
||||
export enum Commands {
|
||||
init = 'init',
|
||||
@@ -260,6 +260,7 @@ export enum OutCommand {
|
||||
updateMainCharacter = 'updateMainCharacter',
|
||||
addPing = 'add_ping',
|
||||
cancelPing = 'cancel_ping',
|
||||
startTracking = 'startTracking',
|
||||
|
||||
// Only UI commands
|
||||
openSettings = 'open_settings',
|
||||
|
||||
@@ -53,16 +53,6 @@ public_api_disabled =
|
||||
|> get_var_from_path_or_env("WANDERER_PUBLIC_API_DISABLED", "false")
|
||||
|> String.to_existing_atom()
|
||||
|
||||
character_api_disabled =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("WANDERER_CHARACTER_API_DISABLED", "true")
|
||||
|> String.to_existing_atom()
|
||||
|
||||
zkill_preload_disabled =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("WANDERER_ZKILL_PRELOAD_DISABLED", "false")
|
||||
|> String.to_existing_atom()
|
||||
|
||||
map_subscriptions_enabled =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("WANDERER_MAP_SUBSCRIPTIONS_ENABLED", "false")
|
||||
@@ -128,8 +118,10 @@ config :wanderer_app,
|
||||
corp_id: System.get_env("WANDERER_CORP_ID", "-1") |> String.to_integer(),
|
||||
corp_wallet: System.get_env("WANDERER_CORP_WALLET", ""),
|
||||
public_api_disabled: public_api_disabled,
|
||||
character_api_disabled: character_api_disabled,
|
||||
zkill_preload_disabled: zkill_preload_disabled,
|
||||
character_api_disabled:
|
||||
System.get_env("WANDERER_CHARACTER_API_DISABLED", "true") |> String.to_existing_atom(),
|
||||
zkill_preload_disabled:
|
||||
System.get_env("WANDERER_ZKILL_PRELOAD_DISABLED", "false") |> String.to_existing_atom(),
|
||||
map_subscriptions_enabled: map_subscriptions_enabled,
|
||||
map_connection_auto_expire_hours: map_connection_auto_expire_hours,
|
||||
map_connection_auto_eol_hours: map_connection_auto_eol_hours,
|
||||
|
||||
@@ -20,9 +20,9 @@ defmodule WandererApp.Application do
|
||||
pools: %{
|
||||
default: [
|
||||
# number of connections per pool
|
||||
size: 25,
|
||||
size: 50,
|
||||
# number of pools (so total 50 connections)
|
||||
count: 2
|
||||
count: 4
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -54,18 +54,26 @@ defmodule WandererApp.Character do
|
||||
end
|
||||
end
|
||||
|
||||
def get_map_character(map_id, character_id) do
|
||||
def get_map_character(map_id, character_id, opts \\ []) do
|
||||
case get_character(character_id) do
|
||||
{:ok, character} ->
|
||||
# If we are forcing the character to not be present, we merge the character state with map settings
|
||||
character_is_present =
|
||||
if opts |> Keyword.get(:not_present, false) do
|
||||
false
|
||||
else
|
||||
WandererApp.Character.TrackerManager.Impl.character_is_present(map_id, character_id)
|
||||
end
|
||||
|
||||
{:ok,
|
||||
character
|
||||
|> maybe_merge_map_character_settings(
|
||||
map_id,
|
||||
WandererApp.Character.TrackerManager.Impl.character_is_present(map_id, character_id)
|
||||
character_is_present
|
||||
)}
|
||||
|
||||
_ ->
|
||||
{:ok, nil}
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -183,7 +191,7 @@ defmodule WandererApp.Character do
|
||||
{:ok, result} ->
|
||||
{:ok, result |> prepare_search_results()}
|
||||
|
||||
{:error, error} ->
|
||||
error ->
|
||||
Logger.warning("#{__MODULE__} failed search: #{inspect(error)}")
|
||||
{:ok, []}
|
||||
end
|
||||
@@ -243,13 +251,22 @@ defmodule WandererApp.Character do
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_merge_map_character_settings(character, map_id, true), do: character
|
||||
defp maybe_merge_map_character_settings(%{id: character_id} = character, map_id, true) do
|
||||
{:ok, tracking_paused} =
|
||||
WandererApp.Cache.lookup("character:#{character_id}:tracking_paused", false)
|
||||
|
||||
character
|
||||
|> Map.merge(%{tracking_paused: tracking_paused})
|
||||
end
|
||||
|
||||
defp maybe_merge_map_character_settings(
|
||||
%{id: character_id} = character,
|
||||
map_id,
|
||||
_character_is_present
|
||||
) do
|
||||
{:ok, tracking_paused} =
|
||||
WandererApp.Cache.lookup("character:#{character_id}:tracking_paused", false)
|
||||
|
||||
WandererApp.MapCharacterSettingsRepo.get(map_id, character_id)
|
||||
|> case do
|
||||
{:ok, settings} when not is_nil(settings) ->
|
||||
@@ -262,6 +279,7 @@ defmodule WandererApp.Character do
|
||||
|> Map.put(:online, false)
|
||||
|> Map.merge(@default_character_tracking_data)
|
||||
end
|
||||
|> Map.merge(%{tracking_paused: tracking_paused})
|
||||
end
|
||||
|
||||
defp prepare_search_results(result) do
|
||||
|
||||
@@ -33,8 +33,16 @@ defmodule WandererApp.Character.Tracker do
|
||||
status: binary()
|
||||
}
|
||||
|
||||
@online_error_timeout :timer.minutes(3)
|
||||
@forbidden_ttl :timer.minutes(1)
|
||||
@pause_tracking_timeout :timer.minutes(60 * 24)
|
||||
@offline_timeout :timer.minutes(5)
|
||||
@online_error_timeout :timer.minutes(2)
|
||||
@ship_error_timeout :timer.minutes(2)
|
||||
@location_error_timeout :timer.minutes(2)
|
||||
@online_forbidden_ttl :timer.seconds(7)
|
||||
@online_limit_ttl :timer.seconds(7)
|
||||
@forbidden_ttl :timer.seconds(5)
|
||||
@limit_ttl :timer.seconds(5)
|
||||
@location_limit_ttl :timer.seconds(1)
|
||||
@pubsub_client Application.compile_env(:wanderer_app, :pubsub_client)
|
||||
|
||||
def new(), do: __struct__()
|
||||
@@ -49,6 +57,90 @@ defmodule WandererApp.Character.Tracker do
|
||||
|> new()
|
||||
end
|
||||
|
||||
def check_offline(character_id) do
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:last_online_time")
|
||||
|> case do
|
||||
nil ->
|
||||
pause_tracking(character_id)
|
||||
:ok
|
||||
|
||||
last_online_time ->
|
||||
duration = DateTime.diff(DateTime.utc_now(), last_online_time, :millisecond)
|
||||
|
||||
if duration >= @offline_timeout do
|
||||
pause_tracking(character_id)
|
||||
|
||||
:ok
|
||||
else
|
||||
:skip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_online_errors(character_id),
|
||||
do: check_tracking_errors(character_id, "online", @online_error_timeout)
|
||||
|
||||
def check_ship_errors(character_id),
|
||||
do: check_tracking_errors(character_id, "ship", @ship_error_timeout)
|
||||
|
||||
def check_location_errors(character_id),
|
||||
do: check_tracking_errors(character_id, "location", @location_error_timeout)
|
||||
|
||||
defp check_tracking_errors(character_id, type, timeout) do
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:#{type}_error_time")
|
||||
|> case do
|
||||
nil ->
|
||||
:skip
|
||||
|
||||
error_time ->
|
||||
duration = DateTime.diff(DateTime.utc_now(), error_time, :millisecond)
|
||||
|
||||
if duration >= timeout do
|
||||
pause_tracking(character_id)
|
||||
|
||||
:ok
|
||||
else
|
||||
:skip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp pause_tracking(character_id) do
|
||||
if not WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused") do
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_forbidden")
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_error_time")
|
||||
WandererApp.Cache.delete("character:#{character_id}:ship_error_time")
|
||||
WandererApp.Cache.delete("character:#{character_id}:location_error_time")
|
||||
WandererApp.Character.update_character(character_id, %{online: false})
|
||||
|
||||
WandererApp.Character.update_character_state(character_id, %{
|
||||
is_online: false
|
||||
})
|
||||
|
||||
Logger.warning("[CharacterTracker] paused for #{character_id}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:tracking_paused",
|
||||
true,
|
||||
ttl: @pause_tracking_timeout
|
||||
)
|
||||
|
||||
{:ok, %{solar_system_id: solar_system_id}} =
|
||||
WandererApp.Character.get_character(character_id)
|
||||
|
||||
{:ok, %{active_maps: active_maps}} =
|
||||
WandererApp.Character.get_character_state(character_id)
|
||||
|
||||
active_maps
|
||||
|> Enum.each(fn map_id ->
|
||||
WandererApp.Cache.put(
|
||||
"map:#{map_id}:character:#{character_id}:start_solar_system_id",
|
||||
solar_system_id
|
||||
)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
def update_settings(character_id, track_settings) do
|
||||
{:ok, character_state} = WandererApp.Character.get_character_state(character_id)
|
||||
|
||||
@@ -61,8 +153,142 @@ defmodule WandererApp.Character.Tracker do
|
||||
|> maybe_start_ship_tracking(track_settings)}
|
||||
end
|
||||
|
||||
def update_online(character_id) when is_binary(character_id),
|
||||
do:
|
||||
character_id
|
||||
|> WandererApp.Character.get_character_state!()
|
||||
|> update_online()
|
||||
|
||||
def update_online(%{track_online: true, character_id: character_id} = character_state) do
|
||||
case WandererApp.Character.get_character(character_id) do
|
||||
{:ok, %{eve_id: eve_id, access_token: access_token}}
|
||||
when not is_nil(access_token) ->
|
||||
(WandererApp.Cache.has_key?("character:#{character_id}:online_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused"))
|
||||
|> case do
|
||||
true ->
|
||||
{:error, :skipped}
|
||||
|
||||
_ ->
|
||||
case WandererApp.Esi.get_character_online(eve_id,
|
||||
access_token: access_token,
|
||||
character_id: character_id
|
||||
) do
|
||||
{:ok, online} ->
|
||||
online = get_online(online)
|
||||
|
||||
if online.online == true do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:last_online_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
else
|
||||
WandererApp.Cache.delete("character:#{character_id}:last_online_time")
|
||||
end
|
||||
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_forbidden")
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_error_time")
|
||||
WandererApp.Cache.delete("character:#{character_id}:ship_error_time")
|
||||
WandererApp.Cache.delete("character:#{character_id}:location_error_time")
|
||||
WandererApp.Cache.delete("character:#{character_id}:info_forbidden")
|
||||
WandererApp.Cache.delete("character:#{character_id}:ship_forbidden")
|
||||
WandererApp.Cache.delete("character:#{character_id}:location_forbidden")
|
||||
WandererApp.Cache.delete("character:#{character_id}:wallet_forbidden")
|
||||
WandererApp.Character.update_character(character_id, online)
|
||||
|
||||
update = %{
|
||||
character_state
|
||||
| is_online: online.online,
|
||||
track_ship: online.online,
|
||||
track_location: online.online
|
||||
}
|
||||
|
||||
WandererApp.Character.update_character_state(character_id, update)
|
||||
|
||||
:ok
|
||||
|
||||
{:error, error} when error in [:forbidden, :not_found, :timeout] ->
|
||||
Logger.warning("#{__MODULE__} failed to update_online: #{inspect(error)}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:online_forbidden",
|
||||
true,
|
||||
ttl: @online_forbidden_ttl
|
||||
)
|
||||
|
||||
if is_nil(
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:online_error_time")
|
||||
) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:online_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
{:error, :skipped}
|
||||
|
||||
{:error, :error_limited, headers} ->
|
||||
reset_timeout = get_reset_timeout(headers)
|
||||
|
||||
Logger.warning(
|
||||
"#{__MODULE__} failed to update_online: #{inspect(:error_limited)}"
|
||||
)
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:online_forbidden",
|
||||
true,
|
||||
ttl: reset_timeout
|
||||
)
|
||||
|
||||
{:error, :skipped}
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("#{__MODULE__} failed to update_online: #{inspect(error)}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:online_forbidden",
|
||||
true,
|
||||
ttl: @online_forbidden_ttl
|
||||
)
|
||||
|
||||
if is_nil(
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:online_error_time")
|
||||
) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:online_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
{:error, :skipped}
|
||||
|
||||
_ ->
|
||||
{:error, :skipped}
|
||||
end
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, :skipped}
|
||||
end
|
||||
end
|
||||
|
||||
def update_online(_), do: {:error, :skipped}
|
||||
|
||||
defp get_reset_timeout(_headers, _default_timeout \\ @limit_ttl)
|
||||
|
||||
defp get_reset_timeout(
|
||||
%{"x-esi-error-limit-remain" => ["0"], "x-esi-error-limit-reset" => [reset_seconds]},
|
||||
_default_timeout
|
||||
)
|
||||
when is_binary(reset_seconds),
|
||||
do: :timer.seconds((reset_seconds |> String.to_integer()) + 1)
|
||||
|
||||
defp get_reset_timeout(_headers, default_timeout), do: default_timeout
|
||||
|
||||
def update_info(character_id) do
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:info_forbidden")
|
||||
(WandererApp.Cache.has_key?("character:#{character_id}:online_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:info_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused"))
|
||||
|> case do
|
||||
true ->
|
||||
{:error, :skipped}
|
||||
@@ -79,8 +305,8 @@ defmodule WandererApp.Character.Tracker do
|
||||
|
||||
:ok
|
||||
|
||||
{:error, :forbidden} ->
|
||||
Logger.warning("#{__MODULE__} failed to get_character_info: forbidden")
|
||||
{:error, error} when error in [:forbidden, :not_found, :timeout] ->
|
||||
Logger.warning("#{__MODULE__} failed to get_character_info: #{inspect(error)}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:info_forbidden",
|
||||
@@ -88,20 +314,44 @@ defmodule WandererApp.Character.Tracker do
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
{:error, :forbidden}
|
||||
{:error, error}
|
||||
|
||||
{:error, :error_limited, headers} ->
|
||||
reset_timeout = get_reset_timeout(headers)
|
||||
|
||||
Logger.warning(
|
||||
"#{__MODULE__} failed to get_character_info: #{inspect(:error_limited)}"
|
||||
)
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:info_forbidden",
|
||||
true,
|
||||
ttl: reset_timeout
|
||||
)
|
||||
|
||||
{:error, :error_limited}
|
||||
|
||||
{:error, error} ->
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:info_forbidden",
|
||||
true,
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
Logger.error("#{__MODULE__} failed to get_character_info: #{inspect(error)}")
|
||||
{:error, error}
|
||||
|
||||
_ ->
|
||||
{:error, :skipped}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_ship(character_id) when is_binary(character_id) do
|
||||
character_id
|
||||
|> WandererApp.Character.get_character_state!()
|
||||
|> update_ship()
|
||||
end
|
||||
def update_ship(character_id) when is_binary(character_id),
|
||||
do:
|
||||
character_id
|
||||
|> WandererApp.Character.get_character_state!()
|
||||
|> update_ship()
|
||||
|
||||
def update_ship(
|
||||
%{character_id: character_id, track_ship: true, is_online: true} = character_state
|
||||
@@ -110,7 +360,9 @@ defmodule WandererApp.Character.Tracker do
|
||||
|> WandererApp.Character.get_character()
|
||||
|> case do
|
||||
{:ok, %{eve_id: eve_id, access_token: access_token}} when not is_nil(access_token) ->
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:ship_forbidden")
|
||||
(WandererApp.Cache.has_key?("character:#{character_id}:online_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:ship_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused"))
|
||||
|> case do
|
||||
true ->
|
||||
{:error, :skipped}
|
||||
@@ -118,16 +370,15 @@ defmodule WandererApp.Character.Tracker do
|
||||
_ ->
|
||||
case WandererApp.Esi.get_character_ship(eve_id,
|
||||
access_token: access_token,
|
||||
character_id: character_id,
|
||||
refresh_token?: true
|
||||
character_id: character_id
|
||||
) do
|
||||
{:ok, ship} ->
|
||||
{:ok, ship} when is_non_struct_map(ship) ->
|
||||
character_state |> maybe_update_ship(ship)
|
||||
|
||||
:ok
|
||||
|
||||
{:error, :forbidden} ->
|
||||
Logger.warning("#{__MODULE__} failed to update_ship: forbidden")
|
||||
{:error, error} when error in [:forbidden, :not_found, :timeout] ->
|
||||
Logger.warning("#{__MODULE__} failed to update_ship: #{inspect(error)}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:ship_forbidden",
|
||||
@@ -135,7 +386,27 @@ defmodule WandererApp.Character.Tracker do
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
{:error, :forbidden}
|
||||
if is_nil(WandererApp.Cache.lookup!("character:#{character_id}:ship_error_time")) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:ship_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
{:error, error}
|
||||
|
||||
{:error, :error_limited, headers} ->
|
||||
reset_timeout = get_reset_timeout(headers)
|
||||
|
||||
Logger.warning("#{__MODULE__} failed to update_ship: #{inspect(:error_limited)}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:ship_forbidden",
|
||||
true,
|
||||
ttl: reset_timeout
|
||||
)
|
||||
|
||||
{:error, :error_limited}
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("#{__MODULE__} failed to update_ship: #{inspect(error)}")
|
||||
@@ -146,7 +417,32 @@ defmodule WandererApp.Character.Tracker do
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
if is_nil(WandererApp.Cache.lookup!("character:#{character_id}:ship_error_time")) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:ship_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
{:error, error}
|
||||
|
||||
_ ->
|
||||
Logger.error("#{__MODULE__} failed to update_ship: wrong response")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:ship_forbidden",
|
||||
true,
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
if is_nil(WandererApp.Cache.lookup!("character:#{character_id}:ship_error_time")) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:ship_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
{:error, :skipped}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -157,18 +453,19 @@ defmodule WandererApp.Character.Tracker do
|
||||
|
||||
def update_ship(_), do: {:error, :skipped}
|
||||
|
||||
def update_location(character_id) when is_binary(character_id) do
|
||||
character_id
|
||||
|> WandererApp.Character.get_character_state!()
|
||||
|> update_location()
|
||||
end
|
||||
def update_location(character_id) when is_binary(character_id),
|
||||
do:
|
||||
character_id
|
||||
|> WandererApp.Character.get_character_state!()
|
||||
|> update_location()
|
||||
|
||||
def update_location(
|
||||
%{track_location: true, is_online: true, character_id: character_id} = character_state
|
||||
) do
|
||||
case WandererApp.Character.get_character(character_id) do
|
||||
{:ok, %{eve_id: eve_id, access_token: access_token}} when not is_nil(access_token) ->
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:location_forbidden")
|
||||
(WandererApp.Cache.has_key?("character:#{character_id}:location_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused"))
|
||||
|> case do
|
||||
true ->
|
||||
{:error, :skipped}
|
||||
@@ -176,36 +473,70 @@ defmodule WandererApp.Character.Tracker do
|
||||
_ ->
|
||||
case WandererApp.Esi.get_character_location(eve_id,
|
||||
access_token: access_token,
|
||||
character_id: character_id,
|
||||
refresh_token?: true
|
||||
character_id: character_id
|
||||
) do
|
||||
{:ok, location} ->
|
||||
{:ok, location} when is_non_struct_map(location) ->
|
||||
character_state
|
||||
|> maybe_update_location(location)
|
||||
|
||||
:ok
|
||||
|
||||
{:error, :forbidden} ->
|
||||
Logger.warning("#{__MODULE__} failed to update_location: forbidden")
|
||||
{:error, error} when error in [:forbidden, :not_found, :timeout] ->
|
||||
Logger.warning("#{__MODULE__} failed to update_location: #{inspect(error)}")
|
||||
|
||||
if is_nil(
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:location_error_time")
|
||||
) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:location_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
{:error, :skipped}
|
||||
|
||||
{:error, :error_limited, headers} ->
|
||||
Logger.warning(
|
||||
"#{__MODULE__} failed to update_location: #{inspect(:error_limited)}"
|
||||
)
|
||||
|
||||
reset_timeout = get_reset_timeout(headers, @location_limit_ttl)
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:location_forbidden",
|
||||
true,
|
||||
ttl: @forbidden_ttl
|
||||
ttl: reset_timeout
|
||||
)
|
||||
|
||||
{:error, :forbidden}
|
||||
{:error, :error_limited}
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("#{__MODULE__} failed to update_location: #{inspect(error)}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:location_forbidden",
|
||||
true,
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
if is_nil(
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:location_error_time")
|
||||
) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:location_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
{:error, error}
|
||||
{:error, :skipped}
|
||||
|
||||
_ ->
|
||||
Logger.error("#{__MODULE__} failed to update_location: wrong response")
|
||||
|
||||
if is_nil(
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:location_error_time")
|
||||
) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:location_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
{:error, :skipped}
|
||||
end
|
||||
|
||||
_ ->
|
||||
@@ -219,146 +550,6 @@ defmodule WandererApp.Character.Tracker do
|
||||
|
||||
def update_location(_), do: {:error, :skipped}
|
||||
|
||||
def update_online(character_id) when is_binary(character_id) do
|
||||
character_id
|
||||
|> WandererApp.Character.get_character_state!()
|
||||
|> update_online()
|
||||
end
|
||||
|
||||
def update_online(%{track_online: true, character_id: character_id} = character_state) do
|
||||
case WandererApp.Character.get_character(character_id) do
|
||||
{:ok, %{eve_id: eve_id, access_token: access_token}}
|
||||
when not is_nil(access_token) ->
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:online_forbidden")
|
||||
|> case do
|
||||
true ->
|
||||
{:error, :skipped}
|
||||
|
||||
_ ->
|
||||
case WandererApp.Esi.get_character_online(eve_id,
|
||||
access_token: access_token,
|
||||
character_id: character_id,
|
||||
refresh_token?: true
|
||||
) do
|
||||
{:ok, online} ->
|
||||
online = get_online(online)
|
||||
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_forbidden")
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_error_time")
|
||||
WandererApp.Character.update_character(character_id, online)
|
||||
|
||||
if not online.online do
|
||||
WandererApp.Cache.delete("character:#{character_id}:location_started")
|
||||
WandererApp.Cache.delete("character:#{character_id}:start_solar_system_id")
|
||||
end
|
||||
|
||||
update = %{
|
||||
character_state
|
||||
| is_online: online.online,
|
||||
track_ship: online.online,
|
||||
track_location: online.online
|
||||
}
|
||||
|
||||
WandererApp.Character.update_character_state(character_id, update)
|
||||
|
||||
:ok
|
||||
|
||||
{:error, :forbidden} ->
|
||||
Logger.warning("#{__MODULE__} failed to update_online: forbidden")
|
||||
|
||||
if not WandererApp.Cache.lookup!(
|
||||
"character:#{character_id}:online_forbidden",
|
||||
false
|
||||
) do
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:online_forbidden",
|
||||
true,
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
if is_nil(
|
||||
WandererApp.Cache.lookup("character:#{character_id}:online_error_time")
|
||||
) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:online_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
:ok
|
||||
|
||||
{:error, :error_limited} ->
|
||||
Logger.warning("#{__MODULE__} failed to update_online: :error_limited")
|
||||
|
||||
if not WandererApp.Cache.lookup!(
|
||||
"character:#{character_id}:online_forbidden",
|
||||
false
|
||||
) do
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:online_forbidden",
|
||||
true,
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
if is_nil(
|
||||
WandererApp.Cache.lookup("character:#{character_id}:online_error_time")
|
||||
) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:online_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
:ok
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("#{__MODULE__} failed to update_online: #{inspect(error)}")
|
||||
|
||||
if is_nil(WandererApp.Cache.lookup("character:#{character_id}:online_error_time")) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:online_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, :skipped}
|
||||
end
|
||||
end
|
||||
|
||||
def update_online(_), do: {:error, :skipped}
|
||||
|
||||
def check_online_errors(character_id) do
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:online_error_time")
|
||||
|> case do
|
||||
nil ->
|
||||
:skip
|
||||
|
||||
error_time ->
|
||||
duration = DateTime.diff(DateTime.utc_now(), error_time, :second)
|
||||
|
||||
if duration >= @online_error_timeout do
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_forbidden")
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_error_time")
|
||||
WandererApp.Character.update_character(character_id, %{online: false})
|
||||
|
||||
WandererApp.Character.update_character_state(character_id, %{
|
||||
is_online: false
|
||||
})
|
||||
|
||||
:ok
|
||||
else
|
||||
:skip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_wallet(character_id) do
|
||||
character_id
|
||||
|> WandererApp.Character.get_character()
|
||||
@@ -369,7 +560,9 @@ defmodule WandererApp.Character.Tracker do
|
||||
|> WandererApp.Character.can_track_wallet?()
|
||||
|> case do
|
||||
true ->
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:wallet_forbidden")
|
||||
(WandererApp.Cache.has_key?("character:#{character_id}:online_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:wallet_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused"))
|
||||
|> case do
|
||||
true ->
|
||||
{:error, :skipped}
|
||||
@@ -378,8 +571,7 @@ defmodule WandererApp.Character.Tracker do
|
||||
case WandererApp.Esi.get_character_wallet(eve_id,
|
||||
params: %{datasource: "tranquility"},
|
||||
access_token: access_token,
|
||||
character_id: character_id,
|
||||
refresh_token?: true
|
||||
character_id: character_id
|
||||
) do
|
||||
{:ok, result} ->
|
||||
{:ok, state} = WandererApp.Character.get_character_state(character_id)
|
||||
@@ -387,8 +579,8 @@ defmodule WandererApp.Character.Tracker do
|
||||
|
||||
:ok
|
||||
|
||||
{:error, :forbidden} ->
|
||||
Logger.warning("#{__MODULE__} failed to _update_wallet: forbidden")
|
||||
{:error, error} when error in [:forbidden, :not_found, :timeout] ->
|
||||
Logger.warning("#{__MODULE__} failed to update_wallet: #{inspect(error)}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:wallet_forbidden",
|
||||
@@ -396,11 +588,44 @@ defmodule WandererApp.Character.Tracker do
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
{:error, :forbidden}
|
||||
{:error, :skipped}
|
||||
|
||||
{:error, :error_limited, headers} ->
|
||||
reset_timeout = get_reset_timeout(headers)
|
||||
|
||||
Logger.warning(
|
||||
"#{__MODULE__} failed to update_wallet: #{inspect(:error_limited)}"
|
||||
)
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:wallet_forbidden",
|
||||
true,
|
||||
ttl: reset_timeout
|
||||
)
|
||||
|
||||
{:error, :skipped}
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("#{__MODULE__} failed to _update_wallet: #{inspect(error)}")
|
||||
{:error, error}
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:wallet_forbidden",
|
||||
true,
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
{:error, :skipped}
|
||||
|
||||
error ->
|
||||
Logger.error("#{__MODULE__} failed to _update_wallet: #{inspect(error)}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:wallet_forbidden",
|
||||
true,
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
{:error, :skipped}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -414,83 +639,99 @@ defmodule WandererApp.Character.Tracker do
|
||||
end
|
||||
|
||||
defp update_alliance(%{character_id: character_id} = state, alliance_id) do
|
||||
alliance_id
|
||||
|> WandererApp.Esi.get_alliance_info()
|
||||
(WandererApp.Cache.has_key?("character:#{character_id}:online_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused"))
|
||||
|> case do
|
||||
{:ok, %{"name" => alliance_name, "ticker" => alliance_ticker}} ->
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
|
||||
character_update = %{
|
||||
alliance_id: alliance_id,
|
||||
alliance_name: alliance_name,
|
||||
alliance_ticker: alliance_ticker
|
||||
}
|
||||
|
||||
{:ok, _character} =
|
||||
WandererApp.Api.Character.update_alliance(character, character_update)
|
||||
|
||||
WandererApp.Character.update_character(character_id, character_update)
|
||||
|
||||
@pubsub_client.broadcast(
|
||||
WandererApp.PubSub,
|
||||
"character:#{character_id}:alliance",
|
||||
{:character_alliance, {character_id, character_update}}
|
||||
)
|
||||
|
||||
true ->
|
||||
state
|
||||
|
||||
_error ->
|
||||
Logger.error("Failed to get alliance info for #{alliance_id}")
|
||||
state
|
||||
_ ->
|
||||
alliance_id
|
||||
|> WandererApp.Esi.get_alliance_info()
|
||||
|> case do
|
||||
{:ok, %{"name" => alliance_name, "ticker" => alliance_ticker}} ->
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
|
||||
character_update = %{
|
||||
alliance_id: alliance_id,
|
||||
alliance_name: alliance_name,
|
||||
alliance_ticker: alliance_ticker
|
||||
}
|
||||
|
||||
{:ok, _character} =
|
||||
WandererApp.Api.Character.update_alliance(character, character_update)
|
||||
|
||||
WandererApp.Character.update_character(character_id, character_update)
|
||||
|
||||
@pubsub_client.broadcast(
|
||||
WandererApp.PubSub,
|
||||
"character:#{character_id}:alliance",
|
||||
{:character_alliance, {character_id, character_update}}
|
||||
)
|
||||
|
||||
state
|
||||
|
||||
_error ->
|
||||
Logger.error("Failed to get alliance info for #{alliance_id}")
|
||||
state
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp update_corporation(%{character_id: character_id} = state, corporation_id) do
|
||||
corporation_id
|
||||
|> WandererApp.Esi.get_corporation_info()
|
||||
(WandererApp.Cache.has_key?("character:#{character_id}:online_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused"))
|
||||
|> case do
|
||||
{:ok, %{"name" => corporation_name, "ticker" => corporation_ticker} = corporation_info} ->
|
||||
alliance_id = Map.get(corporation_info, "alliance_id")
|
||||
true ->
|
||||
state
|
||||
|
||||
{:ok, character} =
|
||||
WandererApp.Character.get_character(character_id)
|
||||
_ ->
|
||||
corporation_id
|
||||
|> WandererApp.Esi.get_corporation_info()
|
||||
|> case do
|
||||
{:ok, %{"name" => corporation_name, "ticker" => corporation_ticker} = corporation_info} ->
|
||||
alliance_id = Map.get(corporation_info, "alliance_id")
|
||||
|
||||
character_update = %{
|
||||
corporation_id: corporation_id,
|
||||
corporation_name: corporation_name,
|
||||
corporation_ticker: corporation_ticker,
|
||||
alliance_id: alliance_id
|
||||
}
|
||||
{:ok, character} =
|
||||
WandererApp.Character.get_character(character_id)
|
||||
|
||||
{:ok, _character} =
|
||||
WandererApp.Api.Character.update_corporation(character, character_update)
|
||||
|
||||
WandererApp.Character.update_character(character_id, character_update)
|
||||
|
||||
@pubsub_client.broadcast(
|
||||
WandererApp.PubSub,
|
||||
"character:#{character_id}:corporation",
|
||||
{:character_corporation,
|
||||
{character_id,
|
||||
%{
|
||||
character_update = %{
|
||||
corporation_id: corporation_id,
|
||||
corporation_name: corporation_name,
|
||||
corporation_ticker: corporation_ticker
|
||||
}}}
|
||||
)
|
||||
corporation_ticker: corporation_ticker,
|
||||
alliance_id: alliance_id
|
||||
}
|
||||
|
||||
state
|
||||
|> Map.merge(%{alliance_id: alliance_id, corporation_id: corporation_id})
|
||||
|> maybe_update_alliance()
|
||||
{:ok, _character} =
|
||||
WandererApp.Api.Character.update_corporation(character, character_update)
|
||||
|
||||
error ->
|
||||
Logger.warning(
|
||||
"Failed to get corporation info for character #{character_id}: #{inspect(error)}",
|
||||
character_id: character_id,
|
||||
corporation_id: corporation_id
|
||||
)
|
||||
WandererApp.Character.update_character(character_id, character_update)
|
||||
|
||||
state
|
||||
@pubsub_client.broadcast(
|
||||
WandererApp.PubSub,
|
||||
"character:#{character_id}:corporation",
|
||||
{:character_corporation,
|
||||
{character_id,
|
||||
%{
|
||||
corporation_id: corporation_id,
|
||||
corporation_name: corporation_name,
|
||||
corporation_ticker: corporation_ticker
|
||||
}}}
|
||||
)
|
||||
|
||||
state
|
||||
|> Map.merge(%{alliance_id: alliance_id, corporation_id: corporation_id})
|
||||
|> maybe_update_alliance()
|
||||
|
||||
error ->
|
||||
Logger.warning(
|
||||
"Failed to get corporation info for character #{character_id}: #{inspect(error)}",
|
||||
character_id: character_id,
|
||||
corporation_id: corporation_id
|
||||
)
|
||||
|
||||
state
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -500,7 +741,8 @@ defmodule WandererApp.Character.Tracker do
|
||||
} =
|
||||
state,
|
||||
ship
|
||||
) do
|
||||
)
|
||||
when is_non_struct_map(ship) do
|
||||
ship_type_id = Map.get(ship, "ship_type_id")
|
||||
ship_name = Map.get(ship, "ship_name")
|
||||
|
||||
@@ -524,6 +766,12 @@ defmodule WandererApp.Character.Tracker do
|
||||
state
|
||||
end
|
||||
|
||||
defp maybe_update_ship(
|
||||
state,
|
||||
_ship
|
||||
),
|
||||
do: state
|
||||
|
||||
defp maybe_update_location(
|
||||
%{
|
||||
character_id: character_id
|
||||
@@ -533,32 +781,12 @@ defmodule WandererApp.Character.Tracker do
|
||||
) do
|
||||
location = get_location(location)
|
||||
|
||||
if not is_location_started?(character_id) do
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:start_solar_system_id", nil)
|
||||
|> case do
|
||||
nil ->
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:start_solar_system_id",
|
||||
location.solar_system_id
|
||||
)
|
||||
|
||||
start_solar_system_id ->
|
||||
if location.solar_system_id != start_solar_system_id do
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:location_started",
|
||||
true
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
{:ok,
|
||||
%{solar_system_id: solar_system_id, structure_id: structure_id, station_id: station_id} =
|
||||
character} =
|
||||
WandererApp.Character.get_character(character_id)
|
||||
|
||||
(not is_location_started?(character_id) ||
|
||||
is_location_updated?(location, solar_system_id, structure_id, station_id))
|
||||
is_location_updated?(location, solar_system_id, structure_id, station_id)
|
||||
|> case do
|
||||
true ->
|
||||
{:ok, _character} = WandererApp.Api.Character.update_location(character, location)
|
||||
@@ -573,13 +801,6 @@ defmodule WandererApp.Character.Tracker do
|
||||
state
|
||||
end
|
||||
|
||||
defp is_location_started?(character_id),
|
||||
do:
|
||||
WandererApp.Cache.lookup!(
|
||||
"character:#{character_id}:location_started",
|
||||
false
|
||||
)
|
||||
|
||||
defp is_location_updated?(
|
||||
%{
|
||||
solar_system_id: new_solar_system_id,
|
||||
@@ -717,16 +938,31 @@ defmodule WandererApp.Character.Tracker do
|
||||
defp maybe_update_active_maps(
|
||||
%{character_id: character_id, active_maps: active_maps} =
|
||||
state,
|
||||
%{map_id: map_id, track: true} = _track_settings
|
||||
%{map_id: map_id, track: true} = track_settings
|
||||
) do
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:map:#{map_id}:tracking_start_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
if not Enum.member?(active_maps, map_id) do
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:map:#{map_id}:tracking_start_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
|
||||
WandererApp.Cache.take("character:#{character_id}:last_active_time")
|
||||
WandererApp.Cache.put(
|
||||
"map:#{map_id}:character:#{character_id}:start_solar_system_id",
|
||||
track_settings |> Map.get(:solar_system_id)
|
||||
)
|
||||
|
||||
%{state | active_maps: [map_id | active_maps] |> Enum.uniq()}
|
||||
WandererApp.Cache.delete("map:#{map_id}:character:#{character_id}:solar_system_id")
|
||||
WandererApp.Cache.delete("map:#{map_id}:character:#{character_id}:station_id")
|
||||
WandererApp.Cache.delete("map:#{map_id}:character:#{character_id}:structure_id")
|
||||
|
||||
WandererApp.Cache.take("character:#{character_id}:last_active_time")
|
||||
|
||||
%{state | active_maps: [map_id | active_maps]}
|
||||
else
|
||||
WandererApp.Cache.take("character:#{character_id}:last_active_time")
|
||||
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_update_active_maps(
|
||||
|
||||
@@ -77,8 +77,6 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
Logger.debug(fn -> "Shutting down character tracker: #{inspect(character_id)}" end)
|
||||
|
||||
WandererApp.Cache.delete("character:#{character_id}:last_active_time")
|
||||
WandererApp.Cache.delete("character:#{character_id}:location_started")
|
||||
WandererApp.Cache.delete("character:#{character_id}:start_solar_system_id")
|
||||
WandererApp.Character.delete_character_state(character_id)
|
||||
|
||||
tracked_characters =
|
||||
@@ -189,7 +187,7 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
end,
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task,
|
||||
timeout: :timer.seconds(15)
|
||||
timeout: :timer.seconds(60)
|
||||
)
|
||||
|> Enum.map(fn result ->
|
||||
case result do
|
||||
@@ -214,6 +212,10 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
|> Task.async_stream(
|
||||
fn {map_id, character_id} ->
|
||||
if not character_is_present(map_id, character_id) do
|
||||
WandererApp.Cache.delete("map:#{map_id}:character:#{character_id}:solar_system_id")
|
||||
WandererApp.Cache.delete("map:#{map_id}:character:#{character_id}:station_id")
|
||||
WandererApp.Cache.delete("map:#{map_id}:character:#{character_id}:structure_id")
|
||||
|
||||
{:ok, character_state} =
|
||||
WandererApp.Character.Tracker.update_settings(character_id, %{
|
||||
map_id: map_id,
|
||||
|
||||
@@ -16,7 +16,7 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
@registry :tracker_pool_registry
|
||||
@unique_registry :unique_tracker_pool_registry
|
||||
|
||||
@update_location_interval :timer.seconds(2)
|
||||
@update_location_interval :timer.seconds(1)
|
||||
@update_online_interval :timer.seconds(5)
|
||||
@check_online_errors_interval :timer.seconds(30)
|
||||
@update_ship_interval :timer.seconds(2)
|
||||
@@ -24,6 +24,8 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
@update_wallet_interval :timer.minutes(1)
|
||||
@inactive_character_timeout :timer.minutes(5)
|
||||
|
||||
@pause_tracking_timeout :timer.minutes(60 * 24)
|
||||
|
||||
@logger Application.compile_env(:wanderer_app, :logger)
|
||||
|
||||
def new(), do: __struct__()
|
||||
@@ -49,7 +51,15 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
# end)
|
||||
|
||||
tracked_ids
|
||||
|> Enum.each(fn id -> Cachex.put(@cache, id, uuid) end)
|
||||
|> Enum.each(fn id ->
|
||||
# WandererApp.Cache.put(
|
||||
# "character:#{id}:tracking_paused",
|
||||
# true,
|
||||
# ttl: @pause_tracking_timeout
|
||||
# )
|
||||
|
||||
Cachex.put(@cache, id, uuid)
|
||||
end)
|
||||
|
||||
state =
|
||||
%{
|
||||
@@ -75,10 +85,15 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
[tracked_id | r_tracked_ids]
|
||||
end)
|
||||
|
||||
# WandererApp.Cache.put(
|
||||
# "character:#{tracked_id}:tracking_paused",
|
||||
# true,
|
||||
# ttl: @pause_tracking_timeout
|
||||
# )
|
||||
|
||||
# Cachex.get_and_update(@cache, :tracked_characters, fn ids ->
|
||||
# {:commit, ids ++ [tracked_id]}
|
||||
# end)
|
||||
|
||||
Cachex.put(@cache, tracked_id, uuid)
|
||||
|
||||
{:noreply, %{state | characters: [tracked_id | characters]}}
|
||||
@@ -96,7 +111,7 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
# Cachex.get_and_update(@cache, :tracked_characters, fn ids ->
|
||||
# {:commit, ids |> Enum.reject(fn id -> id == tracked_id end)}
|
||||
# end)
|
||||
|
||||
#
|
||||
Cachex.del(@cache, tracked_id)
|
||||
|
||||
{:noreply, %{state | characters: characters |> Enum.reject(fn id -> id == tracked_id end)}}
|
||||
@@ -155,12 +170,20 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
) do
|
||||
Process.send_after(self(), :update_online, @update_online_interval)
|
||||
|
||||
characters
|
||||
|> Enum.map(fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_online, [
|
||||
character_id
|
||||
])
|
||||
end)
|
||||
try do
|
||||
characters
|
||||
|> Enum.map(fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_online, [
|
||||
character_id
|
||||
])
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("""
|
||||
[Tracker Pool] update_online => exception: #{Exception.message(e)}
|
||||
#{Exception.format_stacktrace(__STACKTRACE__)}
|
||||
""")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
@@ -174,14 +197,22 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
) do
|
||||
Process.send_after(self(), :update_online, @update_online_interval)
|
||||
|
||||
characters
|
||||
|> Enum.each(fn character_id ->
|
||||
WandererApp.Character.update_character(character_id, %{online: false})
|
||||
try do
|
||||
characters
|
||||
|> Enum.each(fn character_id ->
|
||||
WandererApp.Character.update_character(character_id, %{online: false})
|
||||
|
||||
WandererApp.Character.update_character_state(character_id, %{
|
||||
is_online: false
|
||||
})
|
||||
end)
|
||||
WandererApp.Character.update_character_state(character_id, %{
|
||||
is_online: false
|
||||
})
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("""
|
||||
[Tracker Pool] update_online => exception: #{Exception.message(e)}
|
||||
#{Exception.format_stacktrace(__STACKTRACE__)}
|
||||
""")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
@@ -195,21 +226,33 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
) do
|
||||
Process.send_after(self(), :check_online_errors, @check_online_errors_interval)
|
||||
|
||||
characters
|
||||
|> Task.async_stream(
|
||||
fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :check_online_errors, [
|
||||
character_id
|
||||
])
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> @logger.error("Error in check_online_errors: #{inspect(reason)}")
|
||||
end)
|
||||
try do
|
||||
characters
|
||||
|> Task.async_stream(
|
||||
fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(
|
||||
WandererApp.Character.Tracker,
|
||||
:check_online_errors,
|
||||
[
|
||||
character_id
|
||||
]
|
||||
)
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> @logger.error("Error in check_online_errors: #{inspect(reason)}")
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("""
|
||||
[Tracker Pool] check_online_errors => exception: #{Exception.message(e)}
|
||||
#{Exception.format_stacktrace(__STACKTRACE__)}
|
||||
""")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
@@ -224,12 +267,20 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
) do
|
||||
Process.send_after(self(), :update_location, @update_location_interval)
|
||||
|
||||
characters
|
||||
|> Enum.map(fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_location, [
|
||||
character_id
|
||||
])
|
||||
end)
|
||||
try do
|
||||
characters
|
||||
|> Enum.map(fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_location, [
|
||||
character_id
|
||||
])
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("""
|
||||
[Tracker Pool] update_location => exception: #{Exception.message(e)}
|
||||
#{Exception.format_stacktrace(__STACKTRACE__)}
|
||||
""")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
@@ -253,12 +304,20 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
) do
|
||||
Process.send_after(self(), :update_ship, @update_ship_interval)
|
||||
|
||||
characters
|
||||
|> Enum.map(fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_ship, [
|
||||
character_id
|
||||
])
|
||||
end)
|
||||
try do
|
||||
characters
|
||||
|> Enum.map(fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_ship, [
|
||||
character_id
|
||||
])
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("""
|
||||
[Tracker Pool] update_ship => exception: #{Exception.message(e)}
|
||||
#{Exception.format_stacktrace(__STACKTRACE__)}
|
||||
""")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
@@ -282,21 +341,29 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
) do
|
||||
Process.send_after(self(), :update_info, @update_info_interval)
|
||||
|
||||
characters
|
||||
|> Task.async_stream(
|
||||
fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_info, [
|
||||
character_id
|
||||
])
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> @logger.error("Error in update_info: #{inspect(reason)}")
|
||||
end)
|
||||
try do
|
||||
characters
|
||||
|> Task.async_stream(
|
||||
fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_info, [
|
||||
character_id
|
||||
])
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> Logger.error("Error in update_info: #{inspect(reason)}")
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("""
|
||||
[Tracker Pool] update_info => exception: #{Exception.message(e)}
|
||||
#{Exception.format_stacktrace(__STACKTRACE__)}
|
||||
""")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
@@ -320,21 +387,29 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
) do
|
||||
Process.send_after(self(), :update_wallet, @update_wallet_interval)
|
||||
|
||||
characters
|
||||
|> Task.async_stream(
|
||||
fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_wallet, [
|
||||
character_id
|
||||
])
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> @logger.error("Error in update_wallet: #{inspect(reason)}")
|
||||
end)
|
||||
try do
|
||||
characters
|
||||
|> Task.async_stream(
|
||||
fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_wallet, [
|
||||
character_id
|
||||
])
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> Logger.error("Error in update_wallet: #{inspect(reason)}")
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("""
|
||||
[Tracker Pool] update_wallet => exception: #{Exception.message(e)}
|
||||
#{Exception.format_stacktrace(__STACKTRACE__)}
|
||||
""")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@@ -7,7 +7,7 @@ defmodule WandererApp.Character.TrackerPoolDynamicSupervisor do
|
||||
@cache :tracked_characters
|
||||
@registry :tracker_pool_registry
|
||||
@unique_registry :unique_tracker_pool_registry
|
||||
@tracker_pool_limit 100
|
||||
@tracker_pool_limit 50
|
||||
|
||||
@name __MODULE__
|
||||
|
||||
|
||||
@@ -145,13 +145,15 @@ defmodule WandererApp.Character.TransactionsTracker.Impl do
|
||||
end
|
||||
|
||||
defp get_wallet_journal(
|
||||
%{corporation_id: corporation_id, access_token: access_token} = _character,
|
||||
%{id: character_id, corporation_id: corporation_id, access_token: access_token} =
|
||||
_character,
|
||||
division
|
||||
)
|
||||
when not is_nil(access_token) do
|
||||
case WandererApp.Esi.get_corporation_wallet_journal(corporation_id, division,
|
||||
params: %{datasource: "tranquility"},
|
||||
access_token: access_token
|
||||
access_token: access_token,
|
||||
character_id: character_id
|
||||
) do
|
||||
{:ok, result} ->
|
||||
{:corporation_wallet_journal, result}
|
||||
@@ -160,7 +162,7 @@ defmodule WandererApp.Character.TransactionsTracker.Impl do
|
||||
Logger.warning("#{__MODULE__} failed to get_wallet_journal: forbidden")
|
||||
{:error, :forbidden}
|
||||
|
||||
{:error, :error_limited} ->
|
||||
{:error, :error_limited, _headers} ->
|
||||
Logger.warning("#{__MODULE__} failed to get_wallet_journal: error_limited")
|
||||
{:error, :error_limited}
|
||||
|
||||
@@ -173,12 +175,14 @@ defmodule WandererApp.Character.TransactionsTracker.Impl do
|
||||
defp get_wallet_journal(_character, _division), do: {:error, :skipped}
|
||||
|
||||
defp update_corp_wallets(
|
||||
%{corporation_id: corporation_id, access_token: access_token} = _character
|
||||
%{id: character_id, corporation_id: corporation_id, access_token: access_token} =
|
||||
_character
|
||||
)
|
||||
when not is_nil(access_token) do
|
||||
case WandererApp.Esi.get_corporation_wallets(corporation_id,
|
||||
params: %{datasource: "tranquility"},
|
||||
access_token: access_token
|
||||
access_token: access_token,
|
||||
character_id: character_id
|
||||
) do
|
||||
{:ok, result} ->
|
||||
{:corporation_wallets, result}
|
||||
@@ -187,7 +191,7 @@ defmodule WandererApp.Character.TransactionsTracker.Impl do
|
||||
Logger.warning("#{__MODULE__} failed to update_corp_wallets: forbidden")
|
||||
{:error, :forbidden}
|
||||
|
||||
{:error, :error_limited} ->
|
||||
{:error, :error_limited, _headers} ->
|
||||
Logger.warning("#{__MODULE__} failed to update_corp_wallets: error_limited")
|
||||
{:error, :error_limited}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
@base_url "https://esi.evetech.net/latest"
|
||||
@wanderrer_user_agent "(wanderer-industries@proton.me; +https://github.com/wanderer-industries/wanderer)"
|
||||
|
||||
@req_esi Req.new(base_url: @base_url, finch: WandererApp.Finch)
|
||||
|
||||
@get_link_pairs_advanced_params [
|
||||
:include_mass_crit,
|
||||
:include_eol,
|
||||
@@ -35,8 +37,8 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
@default_avoid_systems [@zarzakh_system]
|
||||
|
||||
@cache_opts [cache: true]
|
||||
@retry_opts [max_retries: 0, retry_log_level: :warning]
|
||||
@timeout_opts [pool_timeout: 15_000, receive_timeout: :timer.seconds(30)]
|
||||
@retry_opts [retry: false, retry_log_level: :warning]
|
||||
@timeout_opts [pool_timeout: 15_000, receive_timeout: :timer.minutes(1)]
|
||||
@api_retry_count 1
|
||||
|
||||
@logger Application.compile_env(:wanderer_app, :logger)
|
||||
@@ -47,7 +49,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
do:
|
||||
post_esi(
|
||||
"/ui/autopilot/waypoint",
|
||||
opts
|
||||
get_auth_opts(opts)
|
||||
|> Keyword.merge(
|
||||
params: %{
|
||||
add_to_beginning: add_to_beginning,
|
||||
@@ -60,8 +62,8 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
def post_characters_affiliation(character_eve_ids, _opts)
|
||||
when is_list(character_eve_ids),
|
||||
do:
|
||||
post(
|
||||
"#{@base_url}/characters/affiliation/",
|
||||
post_esi(
|
||||
"/characters/affiliation/",
|
||||
json: character_eve_ids,
|
||||
params: %{
|
||||
datasource: "tranquility"
|
||||
@@ -218,8 +220,6 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
{:ok, result}
|
||||
|
||||
{:error, _error} ->
|
||||
@logger.error("Error getting custom routes for #{inspect(origin)}: #{inspect(hubs)}")
|
||||
|
||||
@logger.error(
|
||||
"Error getting custom routes for #{inspect(origin)}: #{inspect(params)}"
|
||||
)
|
||||
@@ -275,10 +275,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
"success" => true
|
||||
}
|
||||
|
||||
{:error, :not_found} ->
|
||||
%{"origin" => origin, "destination" => destination, "systems" => [], "success" => false}
|
||||
|
||||
{:error, error} ->
|
||||
error ->
|
||||
Logger.warning("Error getting routes: #{inspect(error)}")
|
||||
%{"origin" => origin, "destination" => destination, "systems" => [], "success" => false}
|
||||
end
|
||||
@@ -293,6 +290,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
case _get_alliance_info(eve_id, "", opts) do
|
||||
{:ok, result} -> {:ok, result |> Map.put("eve_id", eve_id)}
|
||||
{:error, error} -> {:error, error}
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -314,6 +312,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
case _get_corporation_info(eve_id, "", opts) do
|
||||
{:ok, result} -> {:ok, result |> Map.put("eve_id", eve_id)}
|
||||
{:error, error} -> {:error, error}
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -330,6 +329,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
) do
|
||||
{:ok, result} -> {:ok, result |> Map.put("eve_id", eve_id)}
|
||||
{:error, error} -> {:error, error}
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -463,10 +463,10 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
get(
|
||||
path,
|
||||
auth_opts,
|
||||
opts
|
||||
opts |> with_refresh_token()
|
||||
)
|
||||
else
|
||||
get_retry(path, auth_opts, opts)
|
||||
get_retry(path, auth_opts, opts |> with_refresh_token())
|
||||
end
|
||||
end
|
||||
|
||||
@@ -485,7 +485,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
"/corporations/#{corporation_eve_id}/#{info_path}",
|
||||
[params: opts[:params] || []] ++
|
||||
(opts |> get_auth_opts()),
|
||||
opts ++ @cache_opts
|
||||
(opts |> with_refresh_token()) ++ @cache_opts
|
||||
)
|
||||
|
||||
defp with_user_agent_opts(opts) do
|
||||
@@ -495,17 +495,14 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
)
|
||||
end
|
||||
|
||||
defp with_refresh_token(opts) do
|
||||
opts |> Keyword.merge(refresh_token?: true)
|
||||
end
|
||||
|
||||
defp with_cache_opts(opts) do
|
||||
opts |> Keyword.merge(@cache_opts) |> Keyword.merge(cache_dir: System.tmp_dir!())
|
||||
end
|
||||
|
||||
defp post_esi(path, opts),
|
||||
do:
|
||||
post(
|
||||
"#{@base_url}#{path}",
|
||||
[params: opts[:params] || []] ++ (opts |> get_auth_opts())
|
||||
)
|
||||
|
||||
defp get(path, api_opts \\ [], opts \\ []) do
|
||||
case Cachex.get(:api_cache, path) do
|
||||
{:ok, cached_data} when not is_nil(cached_data) ->
|
||||
@@ -519,8 +516,9 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
defp do_get_request(path, api_opts \\ [], opts \\ []) do
|
||||
try do
|
||||
case Req.get(
|
||||
"#{@base_url}#{path}",
|
||||
@req_esi,
|
||||
api_opts
|
||||
|> Keyword.merge(url: path)
|
||||
|> with_user_agent_opts()
|
||||
|> with_cache_opts()
|
||||
|> Keyword.merge(@retry_opts)
|
||||
@@ -537,11 +535,12 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
{:ok, %{status: 404}} ->
|
||||
{:error, :not_found}
|
||||
|
||||
{:ok, %{status: 403} = _error} ->
|
||||
get_retry(path, api_opts, opts)
|
||||
{:ok, %{status: 420, headers: headers} = _error} ->
|
||||
Logger.warning("#{path} error_limited error: #{inspect(headers)}")
|
||||
{:error, :error_limited, headers}
|
||||
|
||||
{:ok, %{status: 420} = _error} ->
|
||||
get_retry(path, api_opts, opts, :error_limited)
|
||||
{:ok, %{status: status} = _error} when status in [401, 403] ->
|
||||
get_retry(path, api_opts, opts)
|
||||
|
||||
{:ok, %{status: status}} ->
|
||||
{:error, "Unexpected status: #{status}"}
|
||||
@@ -551,7 +550,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
end
|
||||
rescue
|
||||
e ->
|
||||
@logger.error(Exception.message(e))
|
||||
Logger.error(Exception.message(e))
|
||||
|
||||
{:error, "Request failed"}
|
||||
end
|
||||
@@ -593,8 +592,48 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
{:ok, %{status: 403}} ->
|
||||
{:error, :forbidden}
|
||||
|
||||
{:ok, %{status: 420}} ->
|
||||
{:error, :error_limited}
|
||||
{:ok, %{status: 420, headers: headers} = _error} ->
|
||||
Logger.warning("#{url} error_limited error: #{inspect(headers)}")
|
||||
{:error, :error_limited, headers}
|
||||
|
||||
{:ok, %{status: status}} ->
|
||||
{:error, "Unexpected status: #{status}"}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
rescue
|
||||
e ->
|
||||
@logger.error(Exception.message(e))
|
||||
|
||||
{:error, "Request failed"}
|
||||
end
|
||||
end
|
||||
|
||||
defp post_esi(url, opts) do
|
||||
try do
|
||||
req_opts =
|
||||
(opts |> with_user_agent_opts() |> Keyword.merge(@retry_opts)) ++
|
||||
[params: opts[:params] || []]
|
||||
|
||||
Req.new(
|
||||
[base_url: @base_url, finch: WandererApp.Finch] ++
|
||||
req_opts
|
||||
)
|
||||
|> Req.post(url: url)
|
||||
|> case do
|
||||
{:ok, %{status: status, body: body}} when status in [200, 201] ->
|
||||
{:ok, body}
|
||||
|
||||
{:ok, %{status: 504}} ->
|
||||
{:error, :timeout}
|
||||
|
||||
{:ok, %{status: 403}} ->
|
||||
{:error, :forbidden}
|
||||
|
||||
{:ok, %{status: 420, headers: headers} = _error} ->
|
||||
Logger.warning("#{url} error_limited error: #{inspect(headers)}")
|
||||
{:error, :error_limited, headers}
|
||||
|
||||
{:ok, %{status: status}} ->
|
||||
{:error, "Unexpected status: #{status}"}
|
||||
|
||||
@@ -96,7 +96,9 @@ defmodule WandererApp.Map do
|
||||
map_id
|
||||
|> get_map!()
|
||||
|> Map.get(:characters, [])
|
||||
|> Enum.map(fn character_id -> WandererApp.Character.get_map_character!(map_id, character_id) end)
|
||||
|> Enum.map(fn character_id ->
|
||||
WandererApp.Character.get_map_character!(map_id, character_id)
|
||||
end)
|
||||
|
||||
def list_systems(map_id),
|
||||
do: {:ok, map_id |> get_map!() |> Map.get(:systems, Map.new()) |> Map.values()}
|
||||
@@ -155,56 +157,61 @@ defmodule WandererApp.Map do
|
||||
|
||||
case not (characters |> Enum.member?(character_id)) do
|
||||
true ->
|
||||
{:ok,
|
||||
%{
|
||||
alliance_id: alliance_id,
|
||||
corporation_id: corporation_id,
|
||||
solar_system_id: solar_system_id,
|
||||
structure_id: structure_id,
|
||||
station_id: station_id,
|
||||
ship: ship_type_id,
|
||||
ship_name: ship_name
|
||||
}} = WandererApp.Character.get_character(character_id)
|
||||
WandererApp.Character.get_map_character(map_id, character_id)
|
||||
|> case do
|
||||
{:ok,
|
||||
%{
|
||||
alliance_id: alliance_id,
|
||||
corporation_id: corporation_id,
|
||||
solar_system_id: solar_system_id,
|
||||
structure_id: structure_id,
|
||||
station_id: station_id,
|
||||
ship: ship_type_id,
|
||||
ship_name: ship_name
|
||||
}} ->
|
||||
map_id
|
||||
|> update_map(%{characters: [character_id | characters]})
|
||||
|
||||
map_id
|
||||
|> update_map(%{characters: [character_id | characters]})
|
||||
WandererApp.Cache.insert(
|
||||
"map:#{map_id}:character:#{character_id}:alliance_id",
|
||||
alliance_id
|
||||
)
|
||||
|
||||
WandererApp.Cache.insert(
|
||||
"map:#{map_id}:character:#{character_id}:alliance_id",
|
||||
alliance_id
|
||||
)
|
||||
WandererApp.Cache.insert(
|
||||
"map:#{map_id}:character:#{character_id}:corporation_id",
|
||||
corporation_id
|
||||
)
|
||||
|
||||
WandererApp.Cache.insert(
|
||||
"map:#{map_id}:character:#{character_id}:corporation_id",
|
||||
corporation_id
|
||||
)
|
||||
# WandererApp.Cache.insert(
|
||||
# "map:#{map_id}:character:#{character_id}:solar_system_id",
|
||||
# solar_system_id
|
||||
# )
|
||||
|
||||
WandererApp.Cache.insert(
|
||||
"map:#{map_id}:character:#{character_id}:solar_system_id",
|
||||
solar_system_id
|
||||
)
|
||||
# WandererApp.Cache.insert(
|
||||
# "map:#{map_id}:character:#{character_id}:structure_id",
|
||||
# structure_id
|
||||
# )
|
||||
|
||||
WandererApp.Cache.insert(
|
||||
"map:#{map_id}:character:#{character_id}:structure_id",
|
||||
structure_id
|
||||
)
|
||||
# WandererApp.Cache.insert(
|
||||
# "map:#{map_id}:character:#{character_id}:station_id",
|
||||
# station_id
|
||||
# )
|
||||
|
||||
WandererApp.Cache.insert(
|
||||
"map:#{map_id}:character:#{character_id}:station_id",
|
||||
station_id
|
||||
)
|
||||
# WandererApp.Cache.insert(
|
||||
# "map:#{map_id}:character:#{character_id}:ship_type_id",
|
||||
# ship_type_id
|
||||
# )
|
||||
|
||||
WandererApp.Cache.insert(
|
||||
"map:#{map_id}:character:#{character_id}:ship_type_id",
|
||||
ship_type_id
|
||||
)
|
||||
# WandererApp.Cache.insert(
|
||||
# "map:#{map_id}:character:#{character_id}:ship_name",
|
||||
# ship_name
|
||||
# )
|
||||
|
||||
WandererApp.Cache.insert(
|
||||
"map:#{map_id}:character:#{character_id}:ship_name",
|
||||
ship_name
|
||||
)
|
||||
:ok
|
||||
|
||||
:ok
|
||||
error ->
|
||||
error
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, :already_exists}
|
||||
|
||||
@@ -14,13 +14,16 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
map_id: map_id,
|
||||
tracked: track_character
|
||||
}),
|
||||
{:ok, character} <- WandererApp.Character.get_map_character(map_id, character_id) do
|
||||
{:ok, character} <- WandererApp.Character.get_character(character_id) do
|
||||
Impl.broadcast!(map_id, :character_added, character)
|
||||
:telemetry.execute([:wanderer_app, :map, :character, :added], %{count: 1})
|
||||
:ok
|
||||
else
|
||||
{:error, :not_found} ->
|
||||
:ok
|
||||
|
||||
_error ->
|
||||
{:ok, character} = WandererApp.Character.get_map_character(map_id, character_id)
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
Impl.broadcast!(map_id, :character_added, character)
|
||||
:ok
|
||||
end
|
||||
@@ -207,15 +210,21 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
end
|
||||
|
||||
def update_characters(%{map_id: map_id} = state) do
|
||||
{:ok, presence_character_ids} =
|
||||
WandererApp.Cache.lookup("map_#{map_id}:presence_character_ids", [])
|
||||
|
||||
WandererApp.Cache.lookup!("maps:#{map_id}:tracked_characters", [])
|
||||
|> Enum.filter(fn character_id -> character_id in presence_character_ids end)
|
||||
|> Enum.map(fn character_id ->
|
||||
Task.start_link(fn ->
|
||||
character_updates =
|
||||
maybe_update_online(map_id, character_id) ++
|
||||
maybe_update_location(map_id, character_id) ++
|
||||
maybe_update_ship(map_id, character_id) ++
|
||||
maybe_update_alliance(map_id, character_id) ++
|
||||
maybe_update_corporation(map_id, character_id)
|
||||
maybe_update_tracking_status(map_id, character_id)
|
||||
|
||||
maybe_update_location(map_id, character_id) ++
|
||||
maybe_update_ship(map_id, character_id) ++
|
||||
maybe_update_alliance(map_id, character_id) ++
|
||||
maybe_update_corporation(map_id, character_id)
|
||||
|
||||
character_updates
|
||||
|> Enum.filter(fn update -> update != :skip end)
|
||||
@@ -238,6 +247,9 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
{:character_online, _info} ->
|
||||
:broadcast
|
||||
|
||||
{:character_tracking, _info} ->
|
||||
:broadcast
|
||||
|
||||
{:character_alliance, _info} ->
|
||||
WandererApp.Cache.insert_or_update(
|
||||
"map_#{map_id}:invalidate_character_ids",
|
||||
@@ -292,38 +304,47 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
old_location,
|
||||
%{map: map, map_id: map_id, rtree_name: rtree_name, map_opts: map_opts} = _state
|
||||
) do
|
||||
start_solar_system_id =
|
||||
WandererApp.Cache.take("map:#{map_id}:character:#{character_id}:start_solar_system_id")
|
||||
|
||||
case is_nil(old_location.solar_system_id) and
|
||||
is_nil(start_solar_system_id) and
|
||||
ConnectionsImpl.can_add_location(map.scope, location.solar_system_id) do
|
||||
true ->
|
||||
:ok = SystemsImpl.maybe_add_system(map_id, location, nil, rtree_name, map_opts)
|
||||
|
||||
_ ->
|
||||
ConnectionsImpl.is_connection_valid(
|
||||
map.scope,
|
||||
old_location.solar_system_id,
|
||||
location.solar_system_id
|
||||
)
|
||||
|> case do
|
||||
true ->
|
||||
:ok =
|
||||
SystemsImpl.maybe_add_system(map_id, location, old_location, rtree_name, map_opts)
|
||||
|
||||
:ok =
|
||||
SystemsImpl.maybe_add_system(map_id, old_location, location, rtree_name, map_opts)
|
||||
|
||||
if is_character_in_space?(location) do
|
||||
if is_nil(start_solar_system_id) || start_solar_system_id == old_location.solar_system_id do
|
||||
ConnectionsImpl.is_connection_valid(
|
||||
map.scope,
|
||||
old_location.solar_system_id,
|
||||
location.solar_system_id
|
||||
)
|
||||
|> case do
|
||||
true ->
|
||||
:ok =
|
||||
ConnectionsImpl.maybe_add_connection(
|
||||
map_id,
|
||||
location,
|
||||
old_location,
|
||||
character_id,
|
||||
false
|
||||
)
|
||||
end
|
||||
SystemsImpl.maybe_add_system(map_id, location, old_location, rtree_name, map_opts)
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
:ok =
|
||||
SystemsImpl.maybe_add_system(map_id, old_location, location, rtree_name, map_opts)
|
||||
|
||||
if is_character_in_space?(location) do
|
||||
:ok =
|
||||
ConnectionsImpl.maybe_add_connection(
|
||||
map_id,
|
||||
location,
|
||||
old_location,
|
||||
character_id,
|
||||
false
|
||||
)
|
||||
end
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
else
|
||||
# skip adding connection or system if character just started tracking on the map
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -333,7 +354,9 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
end
|
||||
|
||||
defp track_character(map_id, character_id) do
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
{:ok, character} =
|
||||
WandererApp.Character.get_map_character(map_id, character_id, not_present: true)
|
||||
|
||||
add_character(%{map_id: map_id}, character, true)
|
||||
|
||||
WandererApp.Character.TrackerManager.update_track_settings(character_id, %{
|
||||
@@ -341,7 +364,8 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
track: true,
|
||||
track_online: true,
|
||||
track_location: true,
|
||||
track_ship: true
|
||||
track_ship: true,
|
||||
solar_system_id: character.solar_system_id
|
||||
})
|
||||
end
|
||||
|
||||
@@ -369,6 +393,33 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_update_tracking_status(map_id, character_id) do
|
||||
with {:ok, old_tracking_paused} <-
|
||||
WandererApp.Cache.lookup(
|
||||
"map:#{map_id}:character:#{character_id}:tracking_paused",
|
||||
false
|
||||
),
|
||||
{:ok, tracking_paused} <-
|
||||
WandererApp.Cache.lookup("character:#{character_id}:tracking_paused", false) do
|
||||
case old_tracking_paused != tracking_paused do
|
||||
true ->
|
||||
WandererApp.Cache.insert(
|
||||
"map:#{map_id}:character:#{character_id}:tracking_paused",
|
||||
tracking_paused
|
||||
)
|
||||
|
||||
[{:character_tracking, %{tracking_paused: tracking_paused}}]
|
||||
|
||||
_ ->
|
||||
[:skip]
|
||||
end
|
||||
else
|
||||
error ->
|
||||
Logger.error("Failed to update character_tracking: #{inspect(error, pretty: true)}")
|
||||
[:skip]
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_update_ship(map_id, character_id) do
|
||||
with {:ok, old_ship_type_id} <-
|
||||
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:ship_type_id"),
|
||||
|
||||
@@ -18,34 +18,38 @@ defmodule WandererApp.Maps do
|
||||
]
|
||||
|
||||
def find_routes(map_id, hubs, origin, routes_settings, false) do
|
||||
{:ok, routes} =
|
||||
WandererApp.Esi.find_routes(
|
||||
map_id,
|
||||
origin,
|
||||
hubs,
|
||||
routes_settings
|
||||
)
|
||||
WandererApp.Esi.find_routes(
|
||||
map_id,
|
||||
origin,
|
||||
hubs,
|
||||
routes_settings
|
||||
)
|
||||
|> case do
|
||||
{:ok, routes} ->
|
||||
systems_static_data =
|
||||
routes
|
||||
|> Enum.map(fn route_info -> route_info.systems end)
|
||||
|> List.flatten()
|
||||
|> Enum.uniq()
|
||||
|> Task.async_stream(
|
||||
fn system_id ->
|
||||
case WandererApp.CachedInfo.get_system_static_info(system_id) do
|
||||
{:ok, nil} ->
|
||||
nil
|
||||
|
||||
systems_static_data =
|
||||
routes
|
||||
|> Enum.map(fn route_info -> route_info.systems end)
|
||||
|> List.flatten()
|
||||
|> Enum.uniq()
|
||||
|> Task.async_stream(
|
||||
fn system_id ->
|
||||
case WandererApp.CachedInfo.get_system_static_info(system_id) do
|
||||
{:ok, nil} ->
|
||||
nil
|
||||
{:ok, system} ->
|
||||
system |> Map.take(@minimum_route_attrs)
|
||||
end
|
||||
end,
|
||||
max_concurrency: 10
|
||||
)
|
||||
|> Enum.map(fn {:ok, val} -> val end)
|
||||
|
||||
{:ok, system} ->
|
||||
system |> Map.take(@minimum_route_attrs)
|
||||
end
|
||||
end,
|
||||
max_concurrency: 10
|
||||
)
|
||||
|> Enum.map(fn {:ok, val} -> val end)
|
||||
{:ok, %{routes: routes, systems_static_data: systems_static_data}}
|
||||
|
||||
{:ok, %{routes: routes, systems_static_data: systems_static_data}}
|
||||
error ->
|
||||
{:ok, %{routes: [], systems_static_data: []}}
|
||||
end
|
||||
end
|
||||
|
||||
def find_routes(map_id, hubs, origin, routes_settings, true) do
|
||||
|
||||
@@ -28,8 +28,6 @@ defmodule WandererApp.Server.ServerStatusTracker do
|
||||
|
||||
@logger Application.compile_env(:wanderer_app, :logger)
|
||||
|
||||
def get_status(), do: GenServer.call(@name, :get_status)
|
||||
|
||||
def start_link(opts \\ []), do: GenServer.start(__MODULE__, opts, name: @name)
|
||||
|
||||
@impl true
|
||||
@@ -42,9 +40,6 @@ defmodule WandererApp.Server.ServerStatusTracker do
|
||||
@impl true
|
||||
def terminate(_reason, _state), do: :ok
|
||||
|
||||
@impl true
|
||||
def handle_call(:get_status, _from, state), do: {:reply, {:ok, state}, state}
|
||||
|
||||
@impl true
|
||||
def handle_call(:stop, _, state), do: {:stop, :normal, :ok, state}
|
||||
|
||||
@@ -66,7 +61,7 @@ defmodule WandererApp.Server.ServerStatusTracker do
|
||||
} = state
|
||||
) do
|
||||
Process.send_after(self(), :refresh_status, @refresh_interval)
|
||||
Task.async(fn -> _get_server_status(retries) end)
|
||||
Task.async(fn -> get_server_status(retries) end)
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
@@ -104,7 +99,7 @@ defmodule WandererApp.Server.ServerStatusTracker do
|
||||
def handle_info(_action, state),
|
||||
do: {:noreply, state}
|
||||
|
||||
defp _get_server_status(retries) do
|
||||
defp get_server_status(retries) do
|
||||
case WandererApp.Esi.get_server_status() do
|
||||
{:ok, result} ->
|
||||
{:status, _get_status(result)}
|
||||
@@ -113,7 +108,7 @@ defmodule WandererApp.Server.ServerStatusTracker do
|
||||
if retries > 0 do
|
||||
:retry
|
||||
else
|
||||
@logger.warning("#{__MODULE__} failed to refresh server status: :timeout")
|
||||
Logger.warning("#{__MODULE__} failed to refresh server status: :timeout")
|
||||
{:status, @initial_state}
|
||||
end
|
||||
|
||||
@@ -121,9 +116,12 @@ defmodule WandererApp.Server.ServerStatusTracker do
|
||||
if retries > 0 do
|
||||
:retry
|
||||
else
|
||||
@logger.warning("#{__MODULE__} failed to refresh server status: #{inspect(error)}")
|
||||
Logger.warning("#{__MODULE__} failed to refresh server status: #{inspect(error)}")
|
||||
{:status, @initial_state}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, :unknown}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -15,26 +15,39 @@ defmodule WandererApp.Zkb.KillsProvider.Fetcher do
|
||||
@doc """
|
||||
Fetch killmails for multiple systems, returning a map of system_id => kills.
|
||||
"""
|
||||
def fetch_kills_for_systems(system_ids, since_hours, state, _opts \\ []) when is_list(system_ids) do
|
||||
try do
|
||||
{final_map, final_state} =
|
||||
Enum.reduce(system_ids, {%{}, state}, fn sid, {acc_map, acc_st} ->
|
||||
case fetch_kills_for_system(sid, since_hours, acc_st) do
|
||||
{:ok, kills, new_st} ->
|
||||
{Map.put(acc_map, sid, kills), new_st}
|
||||
def fetch_kills_for_systems(system_ids, since_hours, state, _opts \\ [])
|
||||
when is_list(system_ids) do
|
||||
zkill_preload_disabled = WandererApp.Env.zkill_preload_disabled?()
|
||||
|
||||
{:error, reason, new_st} ->
|
||||
Logger.debug(fn -> "[Fetcher] system=#{sid} => error=#{inspect(reason)}" end)
|
||||
{Map.put(acc_map, sid, {:error, reason}), new_st}
|
||||
end
|
||||
if not zkill_preload_disabled do
|
||||
try do
|
||||
{final_map, final_state} =
|
||||
Enum.reduce(system_ids, {%{}, state}, fn sid, {acc_map, acc_st} ->
|
||||
case fetch_kills_for_system(sid, since_hours, acc_st) do
|
||||
{:ok, kills, new_st} ->
|
||||
{Map.put(acc_map, sid, kills), new_st}
|
||||
|
||||
{:error, reason, new_st} ->
|
||||
Logger.debug(fn -> "[Fetcher] system=#{sid} => error=#{inspect(reason)}" end)
|
||||
{Map.put(acc_map, sid, {:error, reason}), new_st}
|
||||
end
|
||||
end)
|
||||
|
||||
Logger.debug(fn ->
|
||||
"[Fetcher] fetch_kills_for_systems => done, final_map_size=#{map_size(final_map)} calls=#{final_state.calls_count}"
|
||||
end)
|
||||
|
||||
Logger.debug(fn -> "[Fetcher] fetch_kills_for_systems => done, final_map_size=#{map_size(final_map)} calls=#{final_state.calls_count}" end)
|
||||
{:ok, final_map}
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("[Fetcher] EXCEPTION in fetch_kills_for_systems => #{Exception.message(e)}")
|
||||
{:error, e}
|
||||
{:ok, final_map}
|
||||
rescue
|
||||
e ->
|
||||
Logger.error(
|
||||
"[Fetcher] EXCEPTION in fetch_kills_for_systems => #{Exception.message(e)}"
|
||||
)
|
||||
|
||||
{:error, e}
|
||||
end
|
||||
else
|
||||
{:error, :kills_disabled}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -58,18 +71,25 @@ defmodule WandererApp.Zkb.KillsProvider.Fetcher do
|
||||
if not force? and KillsCache.recently_fetched?(system_id) do
|
||||
cached_kills = KillsCache.fetch_cached_kills(system_id)
|
||||
final = maybe_take(cached_kills, limit)
|
||||
Logger.debug(fn -> "#{log_prefix}, recently_fetched?=true => returning #{length(final)} cached kills" end)
|
||||
|
||||
Logger.debug(fn ->
|
||||
"#{log_prefix}, recently_fetched?=true => returning #{length(final)} cached kills"
|
||||
end)
|
||||
|
||||
{:ok, final, state}
|
||||
else
|
||||
Logger.debug(fn -> "#{log_prefix}, hours=#{since_hours}, limit=#{inspect(limit)}, force=#{force?}" end)
|
||||
Logger.debug(fn ->
|
||||
"#{log_prefix}, hours=#{since_hours}, limit=#{inspect(limit)}, force=#{force?}"
|
||||
end)
|
||||
|
||||
cutoff_dt = hours_ago(since_hours)
|
||||
|
||||
result =
|
||||
retry with: exponential_backoff(300)
|
||||
|> randomize()
|
||||
|> cap(5_000)
|
||||
|> expiry(120_000) do
|
||||
retry with:
|
||||
exponential_backoff(300)
|
||||
|> randomize()
|
||||
|> cap(5_000)
|
||||
|> expiry(120_000) do
|
||||
case do_multi_page_fetch(system_id, cutoff_dt, 1, 0, limit, state) do
|
||||
{:ok, new_st, total_fetched} ->
|
||||
# Mark system as fully fetched (to prevent repeated calls).
|
||||
@@ -118,10 +138,13 @@ defmodule WandererApp.Zkb.KillsProvider.Fetcher do
|
||||
|
||||
with {:ok, st1} <- increment_calls_count(state),
|
||||
{:ok, st2, partials} <- ZkbApi.fetch_and_parse_page(system_id, page, st1) do
|
||||
Logger.debug(fn -> "[Fetcher] system=#{system_id}, page=#{page}, partials_count=#{length(partials)}" end)
|
||||
Logger.debug(fn ->
|
||||
"[Fetcher] system=#{system_id}, page=#{page}, partials_count=#{length(partials)}"
|
||||
end)
|
||||
|
||||
{_count_stored, older_found?, total_now} =
|
||||
Enum.reduce_while(partials, {0, false, total_so_far}, fn partial, {acc_count, had_older, acc_total} ->
|
||||
Enum.reduce_while(partials, {0, false, total_so_far}, fn partial,
|
||||
{acc_count, had_older, acc_total} ->
|
||||
# If we have a limit and reached it, stop immediately
|
||||
if reached_limit?(limit, acc_total) do
|
||||
{:halt, {acc_count, had_older, acc_total}}
|
||||
@@ -170,7 +193,10 @@ defmodule WandererApp.Zkb.KillsProvider.Fetcher do
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_partial(%{"killmail_id" => kill_id, "zkb" => %{"hash" => kill_hash}} = partial, cutoff_dt) do
|
||||
defp parse_partial(
|
||||
%{"killmail_id" => kill_id, "zkb" => %{"hash" => kill_hash}} = partial,
|
||||
cutoff_dt
|
||||
) do
|
||||
# If we've already cached this kill, skip
|
||||
if KillsCache.get_killmail(kill_id) do
|
||||
:skip
|
||||
@@ -191,7 +217,8 @@ defmodule WandererApp.Zkb.KillsProvider.Fetcher do
|
||||
defp parse_partial(_other, _cutoff_dt), do: :skip
|
||||
|
||||
defp fetch_full_killmail(k_id, k_hash) do
|
||||
retry with: exponential_backoff(300) |> randomize() |> cap(5_000) |> expiry(30_000), rescue_only: [RuntimeError] do
|
||||
retry with: exponential_backoff(300) |> randomize() |> cap(5_000) |> expiry(30_000),
|
||||
rescue_only: [RuntimeError] do
|
||||
case WandererApp.Esi.get_killmail(k_id, k_hash) do
|
||||
{:ok, full_km} ->
|
||||
{:ok, full_km}
|
||||
@@ -206,12 +233,25 @@ defmodule WandererApp.Zkb.KillsProvider.Fetcher do
|
||||
|
||||
{:error, reason} ->
|
||||
if HttpUtil.retriable_error?(reason) do
|
||||
Logger.warning("[Fetcher] ESI get_killmail retriable error => kill_id=#{k_id}, reason=#{inspect(reason)}")
|
||||
Logger.warning(
|
||||
"[Fetcher] ESI get_killmail retriable error => kill_id=#{k_id}, reason=#{inspect(reason)}"
|
||||
)
|
||||
|
||||
raise "ESI error: #{inspect(reason)}, will retry"
|
||||
else
|
||||
Logger.warning("[Fetcher] ESI get_killmail failed => kill_id=#{k_id}, reason=#{inspect(reason)}")
|
||||
Logger.warning(
|
||||
"[Fetcher] ESI get_killmail failed => kill_id=#{k_id}, reason=#{inspect(reason)}"
|
||||
)
|
||||
|
||||
{:error, reason}
|
||||
end
|
||||
|
||||
error ->
|
||||
Logger.warning(
|
||||
"[Fetcher] ESI get_killmail failed => kill_id=#{k_id}, reason=#{inspect(error)}"
|
||||
)
|
||||
|
||||
error
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -223,6 +263,7 @@ defmodule WandererApp.Zkb.KillsProvider.Fetcher do
|
||||
do: {:ok, %{st | calls_count: c + 1}}
|
||||
|
||||
defp reached_limit?(nil, _count_so_far), do: false
|
||||
|
||||
defp reached_limit?(limit, count_so_far) when is_integer(limit),
|
||||
do: count_so_far >= limit
|
||||
|
||||
|
||||
@@ -126,7 +126,9 @@ defmodule WandererApp.Zkb.KillsProvider.Parser do
|
||||
KillsCache.incr_system_kill_count(system_id)
|
||||
end
|
||||
else
|
||||
Logger.warning("[Parser] store_killmail => build_kill_data returned nil for kill_id=#{kill_id}")
|
||||
Logger.warning(
|
||||
"[Parser] store_killmail => build_kill_data returned nil for kill_id=#{kill_id}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -144,7 +146,6 @@ defmodule WandererApp.Zkb.KillsProvider.Parser do
|
||||
"total_value" => total_value,
|
||||
"npc" => npc
|
||||
}) do
|
||||
|
||||
victim_map = extract_victim_fields(victim)
|
||||
final_blow_map = extract_final_blow_fields(attackers)
|
||||
|
||||
@@ -153,17 +154,14 @@ defmodule WandererApp.Zkb.KillsProvider.Parser do
|
||||
"kill_time" => kill_time_dt,
|
||||
"solar_system_id" => sys_id,
|
||||
"zkb" => zkb,
|
||||
|
||||
"victim_char_id" => victim_map.char_id,
|
||||
"victim_corp_id" => victim_map.corp_id,
|
||||
"victim_alliance_id" => victim_map.alliance_id,
|
||||
"victim_ship_type_id" => victim_map.ship_type_id,
|
||||
|
||||
"final_blow_char_id" => final_blow_map.char_id,
|
||||
"final_blow_corp_id" => final_blow_map.corp_id,
|
||||
"final_blow_alliance_id" => final_blow_map.alliance_id,
|
||||
"final_blow_ship_type_id" => final_blow_map.ship_type_id,
|
||||
|
||||
"attacker_count" => attacker_count,
|
||||
"total_value" => total_value,
|
||||
"npc" => npc
|
||||
@@ -179,14 +177,14 @@ defmodule WandererApp.Zkb.KillsProvider.Parser do
|
||||
"alliance_id" => alli,
|
||||
"ship_type_id" => st_id
|
||||
}),
|
||||
do: %{char_id: cid, corp_id: corp, alliance_id: alli, ship_type_id: st_id}
|
||||
do: %{char_id: cid, corp_id: corp, alliance_id: alli, ship_type_id: st_id}
|
||||
|
||||
defp extract_victim_fields(%{
|
||||
"character_id" => cid,
|
||||
"corporation_id" => corp,
|
||||
"ship_type_id" => st_id
|
||||
}),
|
||||
do: %{char_id: cid, corp_id: corp, alliance_id: nil, ship_type_id: st_id}
|
||||
do: %{char_id: cid, corp_id: corp, alliance_id: nil, ship_type_id: st_id}
|
||||
|
||||
defp extract_victim_fields(_),
|
||||
do: %{char_id: nil, corp_id: nil, alliance_id: nil, ship_type_id: nil}
|
||||
@@ -208,14 +206,14 @@ defmodule WandererApp.Zkb.KillsProvider.Parser do
|
||||
"alliance_id" => alli,
|
||||
"ship_type_id" => st_id
|
||||
}),
|
||||
do: %{char_id: cid, corp_id: corp, alliance_id: alli, ship_type_id: st_id}
|
||||
do: %{char_id: cid, corp_id: corp, alliance_id: alli, ship_type_id: st_id}
|
||||
|
||||
defp extract_attacker_fields(%{
|
||||
"character_id" => cid,
|
||||
"corporation_id" => corp,
|
||||
"ship_type_id" => st_id
|
||||
}),
|
||||
do: %{char_id: cid, corp_id: corp, alliance_id: nil, ship_type_id: st_id}
|
||||
do: %{char_id: cid, corp_id: corp, alliance_id: nil, ship_type_id: st_id}
|
||||
|
||||
defp extract_attacker_fields(%{"ship_type_id" => st_id} = attacker) do
|
||||
%{
|
||||
@@ -235,52 +233,78 @@ defmodule WandererApp.Zkb.KillsProvider.Parser do
|
||||
|> enrich_final_blow()
|
||||
end
|
||||
|
||||
|
||||
defp enrich_victim(km) do
|
||||
km
|
||||
|> maybe_put_character_name("victim_char_id", "victim_char_name")
|
||||
|> maybe_put_corp_info("victim_corp_id", "victim_corp_ticker", "victim_corp_name")
|
||||
|> maybe_put_alliance_info("victim_alliance_id", "victim_alliance_ticker", "victim_alliance_name")
|
||||
|> maybe_put_alliance_info(
|
||||
"victim_alliance_id",
|
||||
"victim_alliance_ticker",
|
||||
"victim_alliance_name"
|
||||
)
|
||||
|> maybe_put_ship_name("victim_ship_type_id", "victim_ship_name")
|
||||
end
|
||||
|
||||
|
||||
defp enrich_final_blow(km) do
|
||||
km
|
||||
|> maybe_put_character_name("final_blow_char_id", "final_blow_char_name")
|
||||
|> maybe_put_corp_info("final_blow_corp_id", "final_blow_corp_ticker", "final_blow_corp_name")
|
||||
|> maybe_put_alliance_info("final_blow_alliance_id", "final_blow_alliance_ticker", "final_blow_alliance_name")
|
||||
|> maybe_put_alliance_info(
|
||||
"final_blow_alliance_id",
|
||||
"final_blow_alliance_ticker",
|
||||
"final_blow_alliance_name"
|
||||
)
|
||||
|> maybe_put_ship_name("final_blow_ship_type_id", "final_blow_ship_name")
|
||||
end
|
||||
|
||||
defp maybe_put_character_name(km, id_key, name_key) do
|
||||
case Map.get(km, id_key) do
|
||||
nil -> km
|
||||
0 -> km
|
||||
nil ->
|
||||
km
|
||||
|
||||
0 ->
|
||||
km
|
||||
|
||||
eve_id ->
|
||||
result = retry with: exponential_backoff(200) |> randomize() |> cap(2_000) |> expiry(10_000), rescue_only: [RuntimeError] do
|
||||
case WandererApp.Esi.get_character_info(eve_id) do
|
||||
{:ok, %{"name" => char_name}} ->
|
||||
{:ok, char_name}
|
||||
result =
|
||||
retry with: exponential_backoff(200) |> randomize() |> cap(2_000) |> expiry(10_000),
|
||||
rescue_only: [RuntimeError] do
|
||||
case WandererApp.Esi.get_character_info(eve_id) do
|
||||
{:ok, %{"name" => char_name}} ->
|
||||
{:ok, char_name}
|
||||
|
||||
{:error, :timeout} ->
|
||||
Logger.debug(fn -> "[Parser] Character info timeout, retrying => id=#{eve_id}" end)
|
||||
raise "Character info timeout, will retry"
|
||||
{:error, :timeout} ->
|
||||
Logger.debug(fn -> "[Parser] Character info timeout, retrying => id=#{eve_id}" end)
|
||||
|
||||
{:error, :not_found} ->
|
||||
Logger.debug(fn -> "[Parser] Character not found => id=#{eve_id}" end)
|
||||
:skip
|
||||
raise "Character info timeout, will retry"
|
||||
|
||||
{:error, reason} ->
|
||||
if HttpUtil.retriable_error?(reason) do
|
||||
Logger.debug(fn -> "[Parser] Character info retriable error => id=#{eve_id}, reason=#{inspect(reason)}" end)
|
||||
raise "Character info error: #{inspect(reason)}, will retry"
|
||||
else
|
||||
Logger.debug(fn -> "[Parser] Character info failed => id=#{eve_id}, reason=#{inspect(reason)}" end)
|
||||
{:error, :not_found} ->
|
||||
Logger.debug(fn -> "[Parser] Character not found => id=#{eve_id}" end)
|
||||
:skip
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
if HttpUtil.retriable_error?(reason) do
|
||||
Logger.debug(fn ->
|
||||
"[Parser] Character info retriable error => id=#{eve_id}, reason=#{inspect(reason)}"
|
||||
end)
|
||||
|
||||
raise "Character info error: #{inspect(reason)}, will retry"
|
||||
else
|
||||
Logger.debug(fn ->
|
||||
"[Parser] Character info failed => id=#{eve_id}, reason=#{inspect(reason)}"
|
||||
end)
|
||||
|
||||
:skip
|
||||
end
|
||||
|
||||
error ->
|
||||
Logger.debug(fn ->
|
||||
"[Parser] Character info failed => id=#{eve_id}, reason=#{inspect(error)}"
|
||||
end)
|
||||
|
||||
:skip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
case result do
|
||||
{:ok, char_name} -> Map.put(km, name_key, char_name)
|
||||
@@ -291,109 +315,180 @@ defmodule WandererApp.Zkb.KillsProvider.Parser do
|
||||
|
||||
defp maybe_put_corp_info(km, id_key, ticker_key, name_key) do
|
||||
case Map.get(km, id_key) do
|
||||
nil -> km
|
||||
0 -> km
|
||||
nil ->
|
||||
km
|
||||
|
||||
0 ->
|
||||
km
|
||||
|
||||
corp_id ->
|
||||
result = retry with: exponential_backoff(200) |> randomize() |> cap(2_000) |> expiry(10_000), rescue_only: [RuntimeError] do
|
||||
case WandererApp.Esi.get_corporation_info(corp_id) do
|
||||
{:ok, %{"ticker" => ticker, "name" => corp_name}} ->
|
||||
{:ok, {ticker, corp_name}}
|
||||
result =
|
||||
retry with: exponential_backoff(200) |> randomize() |> cap(2_000) |> expiry(10_000),
|
||||
rescue_only: [RuntimeError] do
|
||||
case WandererApp.Esi.get_corporation_info(corp_id) do
|
||||
{:ok, %{"ticker" => ticker, "name" => corp_name}} ->
|
||||
{:ok, {ticker, corp_name}}
|
||||
|
||||
{:error, :timeout} ->
|
||||
Logger.debug(fn -> "[Parser] Corporation info timeout, retrying => id=#{corp_id}" end)
|
||||
raise "Corporation info timeout, will retry"
|
||||
{:error, :timeout} ->
|
||||
Logger.debug(fn ->
|
||||
"[Parser] Corporation info timeout, retrying => id=#{corp_id}"
|
||||
end)
|
||||
|
||||
{:error, :not_found} ->
|
||||
Logger.debug(fn -> "[Parser] Corporation not found => id=#{corp_id}" end)
|
||||
:skip
|
||||
raise "Corporation info timeout, will retry"
|
||||
|
||||
{:error, reason} ->
|
||||
if HttpUtil.retriable_error?(reason) do
|
||||
Logger.debug(fn -> "[Parser] Corporation info retriable error => id=#{corp_id}, reason=#{inspect(reason)}" end)
|
||||
raise "Corporation info error: #{inspect(reason)}, will retry"
|
||||
else
|
||||
Logger.warning("[Parser] Failed to fetch corp info: ID=#{corp_id}, reason=#{inspect(reason)}")
|
||||
{:error, :not_found} ->
|
||||
Logger.debug(fn -> "[Parser] Corporation not found => id=#{corp_id}" end)
|
||||
:skip
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
if HttpUtil.retriable_error?(reason) do
|
||||
Logger.debug(fn ->
|
||||
"[Parser] Corporation info retriable error => id=#{corp_id}, reason=#{inspect(reason)}"
|
||||
end)
|
||||
|
||||
raise "Corporation info error: #{inspect(reason)}, will retry"
|
||||
else
|
||||
Logger.warning(
|
||||
"[Parser] Failed to fetch corp info: ID=#{corp_id}, reason=#{inspect(reason)}"
|
||||
)
|
||||
|
||||
:skip
|
||||
end
|
||||
|
||||
error ->
|
||||
Logger.warning(
|
||||
"[Parser] Failed to fetch corp info: ID=#{corp_id}, reason=#{inspect(error)}"
|
||||
)
|
||||
|
||||
:skip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
case result do
|
||||
{:ok, {ticker, corp_name}} ->
|
||||
km
|
||||
|> Map.put(ticker_key, ticker)
|
||||
|> Map.put(name_key, corp_name)
|
||||
_ -> km
|
||||
|
||||
_ ->
|
||||
km
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_put_alliance_info(km, id_key, ticker_key, name_key) do
|
||||
case Map.get(km, id_key) do
|
||||
nil -> km
|
||||
0 -> km
|
||||
nil ->
|
||||
km
|
||||
|
||||
0 ->
|
||||
km
|
||||
|
||||
alliance_id ->
|
||||
result = retry with: exponential_backoff(200) |> randomize() |> cap(2_000) |> expiry(10_000), rescue_only: [RuntimeError] do
|
||||
case WandererApp.Esi.get_alliance_info(alliance_id) do
|
||||
{:ok, %{"ticker" => alliance_ticker, "name" => alliance_name}} ->
|
||||
{:ok, {alliance_ticker, alliance_name}}
|
||||
result =
|
||||
retry with: exponential_backoff(200) |> randomize() |> cap(2_000) |> expiry(10_000),
|
||||
rescue_only: [RuntimeError] do
|
||||
case WandererApp.Esi.get_alliance_info(alliance_id) do
|
||||
{:ok, %{"ticker" => alliance_ticker, "name" => alliance_name}} ->
|
||||
{:ok, {alliance_ticker, alliance_name}}
|
||||
|
||||
{:error, :timeout} ->
|
||||
Logger.debug(fn -> "[Parser] Alliance info timeout, retrying => id=#{alliance_id}" end)
|
||||
raise "Alliance info timeout, will retry"
|
||||
{:error, :timeout} ->
|
||||
Logger.debug(fn ->
|
||||
"[Parser] Alliance info timeout, retrying => id=#{alliance_id}"
|
||||
end)
|
||||
|
||||
{:error, :not_found} ->
|
||||
Logger.debug(fn -> "[Parser] Alliance not found => id=#{alliance_id}" end)
|
||||
:skip
|
||||
raise "Alliance info timeout, will retry"
|
||||
|
||||
{:error, reason} ->
|
||||
if HttpUtil.retriable_error?(reason) do
|
||||
Logger.debug(fn -> "[Parser] Alliance info retriable error => id=#{alliance_id}, reason=#{inspect(reason)}" end)
|
||||
raise "Alliance info error: #{inspect(reason)}, will retry"
|
||||
else
|
||||
Logger.debug(fn -> "[Parser] Alliance info failed => id=#{alliance_id}, reason=#{inspect(reason)}" end)
|
||||
{:error, :not_found} ->
|
||||
Logger.debug(fn -> "[Parser] Alliance not found => id=#{alliance_id}" end)
|
||||
:skip
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
if HttpUtil.retriable_error?(reason) do
|
||||
Logger.debug(fn ->
|
||||
"[Parser] Alliance info retriable error => id=#{alliance_id}, reason=#{inspect(reason)}"
|
||||
end)
|
||||
|
||||
raise "Alliance info error: #{inspect(reason)}, will retry"
|
||||
else
|
||||
Logger.debug(fn ->
|
||||
"[Parser] Alliance info failed => id=#{alliance_id}, reason=#{inspect(reason)}"
|
||||
end)
|
||||
|
||||
:skip
|
||||
end
|
||||
|
||||
error ->
|
||||
Logger.debug(fn ->
|
||||
"[Parser] Alliance info failed => id=#{alliance_id}, reason=#{inspect(error)}"
|
||||
end)
|
||||
|
||||
:skip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
case result do
|
||||
{:ok, {alliance_ticker, alliance_name}} ->
|
||||
km
|
||||
|> Map.put(ticker_key, alliance_ticker)
|
||||
|> Map.put(name_key, alliance_name)
|
||||
_ -> km
|
||||
|
||||
_ ->
|
||||
km
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_put_ship_name(km, id_key, name_key) do
|
||||
case Map.get(km, id_key) do
|
||||
nil -> km
|
||||
0 -> km
|
||||
nil ->
|
||||
km
|
||||
|
||||
0 ->
|
||||
km
|
||||
|
||||
type_id ->
|
||||
result = retry with: exponential_backoff(200) |> randomize() |> cap(2_000) |> expiry(10_000), rescue_only: [RuntimeError] do
|
||||
case WandererApp.CachedInfo.get_ship_type(type_id) do
|
||||
{:ok, nil} -> :skip
|
||||
{:ok, %{name: ship_name}} -> {:ok, ship_name}
|
||||
{:error, :timeout} ->
|
||||
Logger.debug(fn -> "[Parser] Ship type timeout, retrying => id=#{type_id}" end)
|
||||
raise "Ship type timeout, will retry"
|
||||
|
||||
{:error, :not_found} ->
|
||||
Logger.debug(fn -> "[Parser] Ship type not found => id=#{type_id}" end)
|
||||
:skip
|
||||
|
||||
{:error, reason} ->
|
||||
if HttpUtil.retriable_error?(reason) do
|
||||
Logger.debug(fn -> "[Parser] Ship type retriable error => id=#{type_id}, reason=#{inspect(reason)}" end)
|
||||
raise "Ship type error: #{inspect(reason)}, will retry"
|
||||
else
|
||||
Logger.warning("[Parser] Failed to fetch ship type: ID=#{type_id}, reason=#{inspect(reason)}")
|
||||
result =
|
||||
retry with: exponential_backoff(200) |> randomize() |> cap(2_000) |> expiry(10_000),
|
||||
rescue_only: [RuntimeError] do
|
||||
case WandererApp.CachedInfo.get_ship_type(type_id) do
|
||||
{:ok, nil} ->
|
||||
:skip
|
||||
end
|
||||
|
||||
{:ok, %{name: ship_name}} ->
|
||||
{:ok, ship_name}
|
||||
|
||||
{:error, :timeout} ->
|
||||
Logger.debug(fn -> "[Parser] Ship type timeout, retrying => id=#{type_id}" end)
|
||||
raise "Ship type timeout, will retry"
|
||||
|
||||
{:error, :not_found} ->
|
||||
Logger.debug(fn -> "[Parser] Ship type not found => id=#{type_id}" end)
|
||||
:skip
|
||||
|
||||
{:error, reason} ->
|
||||
if HttpUtil.retriable_error?(reason) do
|
||||
Logger.debug(fn ->
|
||||
"[Parser] Ship type retriable error => id=#{type_id}, reason=#{inspect(reason)}"
|
||||
end)
|
||||
|
||||
raise "Ship type error: #{inspect(reason)}, will retry"
|
||||
else
|
||||
Logger.warning(
|
||||
"[Parser] Failed to fetch ship type: ID=#{type_id}, reason=#{inspect(reason)}"
|
||||
)
|
||||
|
||||
:skip
|
||||
end
|
||||
|
||||
error ->
|
||||
Logger.warning(
|
||||
"[Parser] Failed to fetch ship type: ID=#{type_id}, reason=#{inspect(error)}"
|
||||
)
|
||||
|
||||
:skip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
case result do
|
||||
{:ok, ship_name} -> Map.put(km, name_key, ship_name)
|
||||
|
||||
@@ -46,7 +46,10 @@ defmodule WandererApp.Zkb.KillsProvider.Websocket do
|
||||
|
||||
# Called on disconnect
|
||||
def handle_disconnect(code, reason, _old_state) do
|
||||
Logger.warning("[KillsProvider.Websocket] Disconnected => code=#{code}, reason=#{inspect(reason)} => reconnecting")
|
||||
Logger.warning(
|
||||
"[KillsProvider.Websocket] Disconnected => code=#{code}, reason=#{inspect(reason)} => reconnecting"
|
||||
)
|
||||
|
||||
:reconnect
|
||||
end
|
||||
|
||||
@@ -69,41 +72,73 @@ defmodule WandererApp.Zkb.KillsProvider.Websocket do
|
||||
end
|
||||
|
||||
# The partial from zKillboard has killmail_id + zkb.hash, but no time/victim/attackers
|
||||
defp parse_and_store_zkb_partial(%{"killmail_id" => kill_id, "zkb" => %{"hash" => kill_hash}} = partial) do
|
||||
Logger.debug(fn -> "[KillsProvider.Websocket] parse_and_store_zkb_partial => kill_id=#{kill_id}" end)
|
||||
defp parse_and_store_zkb_partial(
|
||||
%{"killmail_id" => kill_id, "zkb" => %{"hash" => kill_hash}} = partial
|
||||
) do
|
||||
Logger.debug(fn ->
|
||||
"[KillsProvider.Websocket] parse_and_store_zkb_partial => kill_id=#{kill_id}"
|
||||
end)
|
||||
|
||||
result = retry with: exponential_backoff(300) |> randomize() |> cap(5_000) |> expiry(30_000), rescue_only: [RuntimeError] do
|
||||
case Esi.get_killmail(kill_id, kill_hash) do
|
||||
{:ok, full_esi_data} ->
|
||||
# Merge partial zKB fields (like totalValue) onto ESI data
|
||||
enriched = Map.merge(full_esi_data, %{"zkb" => partial["zkb"]})
|
||||
Parser.parse_and_store_killmail(enriched)
|
||||
:ok
|
||||
result =
|
||||
retry with: exponential_backoff(300) |> randomize() |> cap(5_000) |> expiry(30_000),
|
||||
rescue_only: [RuntimeError] do
|
||||
case Esi.get_killmail(kill_id, kill_hash) do
|
||||
{:ok, full_esi_data} ->
|
||||
# Merge partial zKB fields (like totalValue) onto ESI data
|
||||
enriched = Map.merge(full_esi_data, %{"zkb" => partial["zkb"]})
|
||||
Parser.parse_and_store_killmail(enriched)
|
||||
:ok
|
||||
|
||||
{:error, :timeout} ->
|
||||
Logger.warning("[KillsProvider.Websocket] ESI get_killmail timeout => kill_id=#{kill_id}, retrying...")
|
||||
raise "ESI timeout, will retry"
|
||||
{:error, :timeout} ->
|
||||
Logger.warning(
|
||||
"[KillsProvider.Websocket] ESI get_killmail timeout => kill_id=#{kill_id}, retrying..."
|
||||
)
|
||||
|
||||
{:error, :not_found} ->
|
||||
Logger.warning("[KillsProvider.Websocket] ESI get_killmail not_found => kill_id=#{kill_id}")
|
||||
:skip
|
||||
raise "ESI timeout, will retry"
|
||||
|
||||
{:error, :not_found} ->
|
||||
Logger.warning(
|
||||
"[KillsProvider.Websocket] ESI get_killmail not_found => kill_id=#{kill_id}"
|
||||
)
|
||||
|
||||
{:error, reason} ->
|
||||
if HttpUtil.retriable_error?(reason) do
|
||||
Logger.warning("[KillsProvider.Websocket] ESI get_killmail retriable error => kill_id=#{kill_id}, reason=#{inspect(reason)}")
|
||||
raise "ESI error: #{inspect(reason)}, will retry"
|
||||
else
|
||||
Logger.warning("[KillsProvider.Websocket] ESI get_killmail failed => kill_id=#{kill_id}, reason=#{inspect(reason)}")
|
||||
:skip
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
if HttpUtil.retriable_error?(reason) do
|
||||
Logger.warning(
|
||||
"[KillsProvider.Websocket] ESI get_killmail retriable error => kill_id=#{kill_id}, reason=#{inspect(reason)}"
|
||||
)
|
||||
|
||||
raise "ESI error: #{inspect(reason)}, will retry"
|
||||
else
|
||||
Logger.warning(
|
||||
"[KillsProvider.Websocket] ESI get_killmail failed => kill_id=#{kill_id}, reason=#{inspect(reason)}"
|
||||
)
|
||||
|
||||
:skip
|
||||
end
|
||||
|
||||
error ->
|
||||
Logger.warning(
|
||||
"[KillsProvider.Websocket] ESI get_killmail failed => kill_id=#{kill_id}, reason=#{inspect(error)}"
|
||||
)
|
||||
|
||||
:skip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
case result do
|
||||
:ok -> :ok
|
||||
:skip -> :skip
|
||||
:ok ->
|
||||
:ok
|
||||
|
||||
:skip ->
|
||||
:skip
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("[KillsProvider.Websocket] ESI get_killmail exhausted retries => kill_id=#{kill_id}, reason=#{inspect(reason)}")
|
||||
Logger.error(
|
||||
"[KillsProvider.Websocket] ESI get_killmail exhausted retries => kill_id=#{kill_id}, reason=#{inspect(reason)}"
|
||||
)
|
||||
|
||||
:skip
|
||||
end
|
||||
end
|
||||
|
||||
@@ -104,7 +104,7 @@ defmodule WandererAppWeb.AccessListMemberAPIController do
|
||||
Creates a new ACL member.
|
||||
"""
|
||||
@spec create(Plug.Conn.t(), map()) :: Plug.Conn.t()
|
||||
operation :create,
|
||||
operation(:create,
|
||||
summary: "Create ACL Member",
|
||||
description: "Creates a new ACL member for a given ACL.",
|
||||
parameters: [
|
||||
@@ -127,6 +127,8 @@ defmodule WandererAppWeb.AccessListMemberAPIController do
|
||||
@acl_member_create_response_schema
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
def create(conn, %{"acl_id" => acl_id, "member" => member_params}) do
|
||||
chosen =
|
||||
cond do
|
||||
@@ -160,8 +162,7 @@ defmodule WandererAppWeb.AccessListMemberAPIController do
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{
|
||||
error:
|
||||
"#{String.capitalize(type)} members cannot have an admin or manager role"
|
||||
error: "#{String.capitalize(type)} members cannot have an admin or manager role"
|
||||
})
|
||||
else
|
||||
info_fetcher =
|
||||
@@ -191,7 +192,7 @@ defmodule WandererAppWeb.AccessListMemberAPIController do
|
||||
|> json(%{error: "Creation failed: #{inspect(error)}"})
|
||||
end
|
||||
else
|
||||
{:error, error} ->
|
||||
error ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "Entity lookup failed: #{inspect(error)}"})
|
||||
@@ -206,7 +207,7 @@ defmodule WandererAppWeb.AccessListMemberAPIController do
|
||||
Updates the role of an ACL member.
|
||||
"""
|
||||
@spec update_role(Plug.Conn.t(), map()) :: Plug.Conn.t()
|
||||
operation :update_role,
|
||||
operation(:update_role,
|
||||
summary: "Update ACL Member Role",
|
||||
description: "Updates the role of an ACL member identified by ACL ID and member external ID.",
|
||||
parameters: [
|
||||
@@ -235,6 +236,8 @@ defmodule WandererAppWeb.AccessListMemberAPIController do
|
||||
@acl_member_update_response_schema
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
def update_role(conn, %{
|
||||
"acl_id" => acl_id,
|
||||
"member_id" => external_id,
|
||||
@@ -301,7 +304,7 @@ defmodule WandererAppWeb.AccessListMemberAPIController do
|
||||
Deletes an ACL member.
|
||||
"""
|
||||
@spec delete(Plug.Conn.t(), map()) :: Plug.Conn.t()
|
||||
operation :delete,
|
||||
operation(:delete,
|
||||
summary: "Delete ACL Member",
|
||||
description: "Deletes an ACL member identified by ACL ID and member external ID.",
|
||||
parameters: [
|
||||
@@ -325,6 +328,8 @@ defmodule WandererAppWeb.AccessListMemberAPIController do
|
||||
@acl_member_delete_response_schema
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
def delete(conn, %{"acl_id" => acl_id, "member_id" => external_id}) do
|
||||
external_id_str = to_string(external_id)
|
||||
|
||||
|
||||
@@ -32,16 +32,18 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
||||
)
|
||||
end
|
||||
|
||||
def handle_server_event(%{event: :untrack_character, payload: character_id}, %{
|
||||
assigns: %{
|
||||
map_id: map_id
|
||||
}
|
||||
} = socket) do
|
||||
def handle_server_event(
|
||||
%{event: :untrack_character, payload: character_id},
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id
|
||||
}
|
||||
} = socket
|
||||
) do
|
||||
:ok = WandererApp.Character.TrackingUtils.untrack([%{id: character_id}], map_id, self())
|
||||
socket
|
||||
end
|
||||
|
||||
|
||||
def handle_server_event(
|
||||
%{event: :characters_updated},
|
||||
%{
|
||||
@@ -276,6 +278,24 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
||||
)}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"startTracking",
|
||||
%{"character_eve_id" => character_eve_id},
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
current_user: %{id: current_user_id}
|
||||
}
|
||||
} = socket
|
||||
)
|
||||
when not is_nil(character_eve_id) do
|
||||
{:ok, character} = WandererApp.Character.get_by_eve_id("#{character_eve_id}")
|
||||
|
||||
WandererApp.Cache.delete("character:#{character.id}:tracking_paused")
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(event, body, socket),
|
||||
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
|
||||
|
||||
@@ -295,6 +315,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
||||
|> Map.put(:alliance_ticker, Map.get(character, :alliance_ticker, ""))
|
||||
|> Map.put_new(:ship, WandererApp.Character.get_ship(character))
|
||||
|> Map.put_new(:location, get_location(character))
|
||||
|> Map.put_new(:tracking_paused, character |> Map.get(:tracking_paused, false))
|
||||
|
||||
defp get_location(character),
|
||||
do: %{
|
||||
|
||||
@@ -329,15 +329,20 @@ defmodule WandererAppWeb.MapRoutesEventHandler do
|
||||
%{assigns: %{current_user: current_user, has_tracked_characters?: true}} = socket
|
||||
) do
|
||||
character_eve_ids
|
||||
|> Task.async_stream(fn character_eve_id ->
|
||||
set_autopilot_waypoint(
|
||||
current_user,
|
||||
character_eve_id,
|
||||
add_to_beginning,
|
||||
clear_other_waypoints,
|
||||
destination_id
|
||||
)
|
||||
end)
|
||||
|> Task.async_stream(
|
||||
fn character_eve_id ->
|
||||
set_autopilot_waypoint(
|
||||
current_user,
|
||||
character_eve_id,
|
||||
add_to_beginning,
|
||||
clear_other_waypoints,
|
||||
destination_id
|
||||
)
|
||||
end,
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task,
|
||||
timeout: :timer.minutes(1)
|
||||
)
|
||||
|> Enum.map(fn _result -> :skip end)
|
||||
|
||||
{:noreply, socket}
|
||||
|
||||
@@ -33,7 +33,8 @@ defmodule WandererAppWeb.MapEventHandler do
|
||||
"getCharactersTrackingInfo",
|
||||
"updateCharacterTracking",
|
||||
"updateFollowingCharacter",
|
||||
"updateMainCharacter"
|
||||
"updateMainCharacter",
|
||||
"startTracking"
|
||||
]
|
||||
|
||||
@map_system_events [
|
||||
|
||||
@@ -11,9 +11,7 @@ defmodule WandererAppWeb.ServerStatusLive do
|
||||
"server_status"
|
||||
)
|
||||
|
||||
{:ok, status} = WandererApp.Server.ServerStatusTracker.get_status()
|
||||
|
||||
{:ok, socket |> assign(server_online: not status.vip)}
|
||||
{:ok, socket |> assign(server_online: false)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
||||
Reference in New Issue
Block a user