Merge branch 'main' of github.com:jennypaxian/c3nav

This commit is contained in:
Jenny Paxian 2024-12-29 17:41:52 +01:00
commit 9e49c54733
17 changed files with 254 additions and 65 deletions

View file

@ -309,13 +309,6 @@ class EditorFormBase(I18nModelFormMixin, ModelForm):
_('Can not add redirecting slug “%s”: it is already used elsewhere.') % slug _('Can not add redirecting slug “%s”: it is already used elsewhere.') % slug
) )
def clean_data(self):
data = self.cleaned_data['data']
if not data.wifi:
raise ValidationError(_('WiFi scan data is missing.'))
data.wifi = [[item for item in scan if item.ssid] for scan in data.wifi]
return data
def clean(self): def clean(self):
if self.is_json: if self.is_json:
for name, field in self.missing_fields: for name, field in self.missing_fields:
@ -325,6 +318,16 @@ class EditorFormBase(I18nModelFormMixin, ModelForm):
if not self.cleaned_data.get('geometry'): if not self.cleaned_data.get('geometry'):
raise ValidationError('Missing geometry.') raise ValidationError('Missing geometry.')
if 'data' in self.fields:
data = self.cleaned_data['data']
if self.cleaned_data['fill_quest']:
if self.cleaned_data['data'].wifi:
raise ValidationError(_('Why is there WiFi scan data if this is a fill quest?'))
else:
if not self.cleaned_data['data'].wifi:
raise ValidationError(_('WiFi scan data is missing.'))
self.cleaned_data['data'].wifi = [[item for item in scan if item.ssid] for scan in data.wifi]
super().clean() super().clean()
def _save_m2m(self): def _save_m2m(self):
@ -398,7 +401,7 @@ def create_editor_form(editor_model):
'stroke_opacity', 'fill_color', 'fill_opacity', 'interactive', 'point_icon', 'extra_data', 'show_label', 'stroke_opacity', 'fill_color', 'fill_opacity', 'interactive', 'point_icon', 'extra_data', 'show_label',
'show_geometry', 'show_label', 'show_geometry', 'default_geomtype', 'cluster_points', 'update_interval', 'show_geometry', 'show_label', 'show_geometry', 'default_geomtype', 'cluster_points', 'update_interval',
'load_group_display', 'load_group_contribute', 'load_group_display', 'load_group_contribute',
'altitude_quest', 'altitude_quest', 'fill_quest',
] ]
field_names = [field.name for field in editor_model._meta.get_fields() field_names = [field.name for field in editor_model._meta.get_fields()
if not field.one_to_many and not isinstance(field, ManyToManyRel)] if not field.one_to_many and not isinstance(field, ManyToManyRel)]

View file

@ -458,7 +458,10 @@ editor = {
collector.find('.wifi-count').text(existing_data?.wifi?.length); collector.find('.wifi-count').text(existing_data?.wifi?.length);
collector.find('.ibeacon-count').text(existing_data?.ibeacon?.length); collector.find('.ibeacon-count').text(existing_data?.ibeacon?.length);
} else { } else {
data_field.closest('form').addClass('scan-lock'); if (window.mobileclient) {
$('[for=id_fill_quest]').hide();
data_field.closest('form').addClass('scan-lock');
}
} }
data_field.after(collector); data_field.after(collector);
} }

View file

@ -354,15 +354,18 @@ class Command(BaseCommand):
", is now"+str([group.title for group in new_groups]), new_group_ids, old_group_ids) ", is now"+str([group.title for group in new_groups]), new_group_ids, old_group_ids)
for import_tag, location in locations_so_far.items(): for import_tag, location in locations_so_far.items():
self.do_report( if location.import_block_data:
prefix='hub:new_groups', self.do_report(
obj_id=import_tag, prefix='hub:new_groups',
obj=import_tag, obj_id=import_tag,
report=Report( obj=import_tag,
category="location-issue", report=Report(
title="importhub: delete this", category="location-issue",
description="hub wants to delete this", title="importhub: delete this",
location=location, description="hub wants to delete this but it's blocked",
location=location,
)
) )
) print(f"NOTE: {location.slug} / {import_tag} should be deleted")
print(f"NOTE: {location.slug} / {import_tag} should be deleted") else:
location.delete()

View file

@ -1,6 +1,7 @@
import re import re
from functools import wraps from functools import wraps
from c3nav.mapdata.utils.cache.local import per_request_cache
from c3nav.mapdata.utils.user import get_user_data_lazy from c3nav.mapdata.utils.user import get_user_data_lazy
@ -55,3 +56,15 @@ class UserDataMiddleware:
def __call__(self, request): def __call__(self, request):
request.user_data = get_user_data_lazy(request) request.user_data = get_user_data_lazy(request)
return self.get_response(request) return self.get_response(request)
class RequestCacheMiddleware:
"""
Resets the request_cache at the start of every request.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
per_request_cache.clear()
return self.get_response(request)

View file

@ -0,0 +1,18 @@
# Generated by Django 5.0.8 on 2024-12-27 15:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mapdata', '0132_dataoverlay_update_interval_and_more'),
]
operations = [
migrations.AddField(
model_name='beaconmeasurement',
name='fill_quest',
field=models.BooleanField(default=False, verbose_name='create a quest to fill this'),
),
]

View file

@ -14,6 +14,7 @@ from django.utils.translation import ngettext_lazy
from c3nav.mapdata.models import MapUpdate from c3nav.mapdata.models import MapUpdate
from c3nav.mapdata.models.base import SerializableMixin, TitledMixin from c3nav.mapdata.models.base import SerializableMixin, TitledMixin
from c3nav.mapdata.utils.cache.local import per_request_cache
class AccessRestriction(TitledMixin, models.Model): class AccessRestriction(TitledMixin, models.Model):
@ -38,20 +39,20 @@ class AccessRestriction(TitledMixin, models.Model):
@staticmethod @staticmethod
def get_all() -> set[int]: def get_all() -> set[int]:
cache_key = 'all_access_restrictions:%s' % MapUpdate.current_cache_key() cache_key = 'all_access_restrictions:%s' % MapUpdate.current_cache_key()
access_restriction_ids = cache.get(cache_key, None) access_restriction_ids = per_request_cache.get(cache_key, None)
if access_restriction_ids is None: if access_restriction_ids is None:
access_restriction_ids = set(AccessRestriction.objects.values_list('pk', flat=True)) access_restriction_ids = set(AccessRestriction.objects.values_list('pk', flat=True))
cache.set(cache_key, access_restriction_ids, 300) per_request_cache.set(cache_key, access_restriction_ids, 300)
return access_restriction_ids return access_restriction_ids
@staticmethod @staticmethod
def get_all_public() -> set[int]: def get_all_public() -> set[int]:
cache_key = 'public_access_restrictions:%s' % MapUpdate.current_cache_key() cache_key = 'public_access_restrictions:%s' % MapUpdate.current_cache_key()
access_restriction_ids = cache.get(cache_key, None) access_restriction_ids = per_request_cache.get(cache_key, None)
if access_restriction_ids is None: if access_restriction_ids is None:
access_restriction_ids = set(AccessRestriction.objects.filter(public=True) access_restriction_ids = set(AccessRestriction.objects.filter(public=True)
.values_list('pk', flat=True)) .values_list('pk', flat=True))
cache.set(cache_key, access_restriction_ids, 300) per_request_cache.set(cache_key, access_restriction_ids, 300)
return access_restriction_ids return access_restriction_ids
@ -321,14 +322,14 @@ class AccessPermission(models.Model):
return AccessRestriction.get_all() return AccessRestriction.get_all()
cache_key = cls.request_access_permission_key(request)+f':{can_grant}' cache_key = cls.request_access_permission_key(request)+f':{can_grant}'
access_restriction_ids = cache.get(cache_key, None) access_restriction_ids = per_request_cache.get(cache_key, None)
if access_restriction_ids is None: if access_restriction_ids is None:
permissions = cls.get_for_request_with_expire_date(request, can_grant=can_grant) permissions = cls.get_for_request_with_expire_date(request, can_grant=can_grant)
access_restriction_ids = set(permissions.keys()) access_restriction_ids = set(permissions.keys())
expire_date = min((e for e in permissions.values() if e), default=timezone.now() + timedelta(seconds=120)) expire_date = min((e for e in permissions.values() if e), default=timezone.now() + timedelta(seconds=120))
cache.set(cache_key, access_restriction_ids, min(300, (expire_date - timezone.now()).total_seconds())) per_request_cache.set(cache_key, access_restriction_ids, min(300, (expire_date - timezone.now()).total_seconds()))
return set(access_restriction_ids) | (set() if can_grant else AccessRestriction.get_all_public()) return set(access_restriction_ids) | (set() if can_grant else AccessRestriction.get_all_public())
@classmethod @classmethod

View file

@ -447,6 +447,8 @@ class BeaconMeasurement(SpaceGeometryMixin, models.Model):
verbose_name=_('Measurement list'), verbose_name=_('Measurement list'),
default=BeaconMeasurementDataSchema()) default=BeaconMeasurementDataSchema())
fill_quest = models.BooleanField(_('create a quest to fill this'), default=False)
class Meta: class Meta:
verbose_name = _('Beacon Measurement') verbose_name = _('Beacon Measurement')
verbose_name_plural = _('Beacon Measurements') verbose_name_plural = _('Beacon Measurements')

View file

@ -23,6 +23,7 @@ from c3nav.mapdata.fields import I18nField
from c3nav.mapdata.grid import grid from c3nav.mapdata.grid import grid
from c3nav.mapdata.models.access import AccessRestrictionMixin from c3nav.mapdata.models.access import AccessRestrictionMixin
from c3nav.mapdata.models.base import SerializableMixin, TitledMixin from c3nav.mapdata.models.base import SerializableMixin, TitledMixin
from c3nav.mapdata.utils.cache.local import per_request_cache
from c3nav.mapdata.utils.fields import LocationById from c3nav.mapdata.utils.fields import LocationById
from c3nav.mapdata.utils.models import get_submodels from c3nav.mapdata.utils.models import get_submodels
@ -620,10 +621,10 @@ class Position(CustomLocationProxyMixin, models.Model):
if not user.is_authenticated: if not user.is_authenticated:
return False return False
cache_key = 'user_has_positions:%d' % user.pk cache_key = 'user_has_positions:%d' % user.pk
result = cache.get(cache_key, None) result = per_request_cache.get(cache_key, None)
if result is None: if result is None:
result = cls.objects.filter(owner=user).exists() result = cls.objects.filter(owner=user).exists()
cache.set(cache_key, result, 600) per_request_cache.set(cache_key, result, 600)
return result return result
def serialize_position(self, request=None): def serialize_position(self, request=None):

View file

@ -16,6 +16,7 @@ from shapely.ops import unary_union
from c3nav.mapdata.tasks import process_map_updates from c3nav.mapdata.tasks import process_map_updates
from c3nav.mapdata.utils.cache.changes import GeometryChangeTracker from c3nav.mapdata.utils.cache.changes import GeometryChangeTracker
from c3nav.mapdata.utils.cache.local import per_request_cache
class MapUpdate(models.Model): class MapUpdate(models.Model):
@ -48,47 +49,47 @@ class MapUpdate(models.Model):
@classmethod @classmethod
def last_update(cls, force=False): def last_update(cls, force=False):
if not force: if not force:
last_update = cache.get('mapdata:last_update', None) last_update = per_request_cache.get('mapdata:last_update', None)
if last_update is not None: if last_update is not None:
return last_update return last_update
try: try:
with cls.lock(): with cls.lock():
last_update = cls.objects.latest().to_tuple last_update = cls.objects.latest().to_tuple
cache.set('mapdata:last_update', last_update, None) per_request_cache.set('mapdata:last_update', last_update, None)
except cls.DoesNotExist: except cls.DoesNotExist:
last_update = (0, 0) last_update = (0, 0)
cache.set('mapdata:last_update', last_update, None) per_request_cache.set('mapdata:last_update', last_update, None)
return last_update return last_update
@classmethod @classmethod
def last_processed_update(cls, force=False, lock=True): def last_processed_update(cls, force=False, lock=True):
if not force: if not force:
last_processed_update = cache.get('mapdata:last_processed_update', None) last_processed_update = per_request_cache.get('mapdata:last_processed_update', None)
if last_processed_update is not None: if last_processed_update is not None:
return last_processed_update return last_processed_update
try: try:
with (cls.lock() if lock else nullcontext()): with (cls.lock() if lock else nullcontext()):
last_processed_update = cls.objects.filter(processed=True).latest().to_tuple last_processed_update = cls.objects.filter(processed=True).latest().to_tuple
cache.set('mapdata:last_processed_update', last_processed_update, None) per_request_cache.set('mapdata:last_processed_update', last_processed_update, None)
except cls.DoesNotExist: except cls.DoesNotExist:
last_processed_update = (0, 0) last_processed_update = (0, 0)
cache.set('mapdata:last_processed_update', last_processed_update, None) per_request_cache.set('mapdata:last_processed_update', last_processed_update, None)
return last_processed_update return last_processed_update
@classmethod @classmethod
def last_processed_geometry_update(cls, force=False): def last_processed_geometry_update(cls, force=False):
if not force: if not force:
last_processed_geometry_update = cache.get('mapdata:last_processed_geometry_update', None) last_processed_geometry_update = per_request_cache.get('mapdata:last_processed_geometry_update', None)
if last_processed_geometry_update is not None: if last_processed_geometry_update is not None:
return last_processed_geometry_update return last_processed_geometry_update
try: try:
with cls.lock(): with cls.lock():
last_processed_geometry_update = cls.objects.filter(processed=True, last_processed_geometry_update = cls.objects.filter(processed=True,
geometries_changed=True).latest().to_tuple geometries_changed=True).latest().to_tuple
cache.set('mapdata:last_processed_geometry_update', last_processed_geometry_update, None) per_request_cache.set('mapdata:last_processed_geometry_update', last_processed_geometry_update, None)
except cls.DoesNotExist: except cls.DoesNotExist:
last_processed_geometry_update = (0, 0) last_processed_geometry_update = (0, 0)
cache.set('mapdata:last_processed_geometry_update', last_processed_geometry_update, None) per_request_cache.set('mapdata:last_processed_geometry_update', last_processed_geometry_update, None)
return last_processed_geometry_update return last_processed_geometry_update
@property @property
@ -239,7 +240,8 @@ class MapUpdate(models.Model):
LevelRenderData.rebuild(geometry_update_cache_key) LevelRenderData.rebuild(geometry_update_cache_key)
transaction.on_commit( transaction.on_commit(
lambda: cache.set('mapdata:last_processed_geometry_update', last_geometry_update.to_tuple, None) lambda: per_request_cache.set('mapdata:last_processed_geometry_update',
last_geometry_update.to_tuple, None)
) )
else: else:
logger.info('No geometries affected.') logger.info('No geometries affected.')
@ -282,7 +284,7 @@ class MapUpdate(models.Model):
if new: if new:
transaction.on_commit( transaction.on_commit(
lambda: cache.set('mapdata:last_update', self.to_tuple, None) lambda: per_request_cache.set('mapdata:last_update', self.to_tuple, None)
) )
if settings.HAS_CELERY and settings.AUTO_PROCESS_UPDATES: if settings.HAS_CELERY and settings.AUTO_PROCESS_UPDATES:
transaction.on_commit( transaction.on_commit(

View file

@ -6,8 +6,9 @@ from django.utils.translation import gettext_lazy as _
from shapely import Point from shapely import Point
from shapely.geometry import mapping from shapely.geometry import mapping
from c3nav.mapdata.models.geometry.space import RangingBeacon from c3nav.mapdata.models.geometry.space import RangingBeacon, BeaconMeasurement
from c3nav.mapdata.quests.base import ChangeSetModelForm, register_quest, Quest from c3nav.mapdata.quests.base import ChangeSetModelForm, register_quest, Quest
from c3nav.routing.schemas import BeaconMeasurementDataSchema
class RangingBeaconAltitudeQuestForm(ChangeSetModelForm): class RangingBeaconAltitudeQuestForm(ChangeSetModelForm):
@ -103,3 +104,49 @@ class RangingBeaconBSSIDsQuest(Quest):
@classmethod @classmethod
def _qs_for_request(cls, request): def _qs_for_request(cls, request):
return RangingBeacon.qs_for_request(request).filter(import_tag__startswith="noc:", wifi_bssids=[]) return RangingBeacon.qs_for_request(request).filter(import_tag__startswith="noc:", wifi_bssids=[])
class BeaconMeasurementQuestForm(ChangeSetModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["data"].widget = HiddenInput()
def clean_bssids(self):
data = self.cleaned_data["data"]
if not data:
raise ValidationError(_("Need at least one scan."))
return data
class Meta:
model = BeaconMeasurement
fields = ("data", )
@property
def changeset_title(self):
return f'Beacon Measurement Quest: {self.instance.title}'
@register_quest
@dataclass
class BeaconMeasurementQuest(Quest):
quest_type = "beacon_measurement"
quest_type_label = _('Wifi/BLE Positioning')
quest_type_icon = "wifi"
form_class = BeaconMeasurementQuestForm
obj: BeaconMeasurement
@property
def quest_description(self) -> list[str]:
return [
_("Please stand as close to the given location as possible. "
"Feel free to close this window again to double-check."),
_("When you're ready, please click the button below and wait for measurements to arrive."),
]
@property
def point(self) -> Point:
return mapping(self.obj.geometry)
@classmethod
def _qs_for_request(cls, request):
return BeaconMeasurement.qs_for_request(request).filter(data=BeaconMeasurementDataSchema())

View file

@ -350,7 +350,7 @@ CustomLocationID = Annotated[NonEmptyStr, APIField(
)] )]
PositionID = Annotated[NonEmptyStr, APIField( PositionID = Annotated[NonEmptyStr, APIField(
title="position ID", title="position ID",
pattern=r"p:[A-Za-z0-9]+$", pattern=r"m:[A-Za-z0-9]+$",
description="the ID of a user-defined tracked position is made up of its secret" description="the ID of a user-defined tracked position is made up of its secret"
)] )]
Coordinates3D = tuple[float, float, float] Coordinates3D = tuple[float, float, float]

View file

@ -1,8 +1,7 @@
from collections import OrderedDict from collections import OrderedDict
from django.core.cache import cache from django.core.cache import cache
from django.conf import settings
from c3nav.mapdata.models import MapUpdate
class NoneFromCache: class NoneFromCache:
@ -42,6 +41,9 @@ class LocalCacheProxy:
self._items.pop(next(iter(self._items.keys()))) self._items.pop(next(iter(self._items.keys())))
def _check_mapupdate(self): def _check_mapupdate(self):
# todo: would be nice to not need this… why do we need this?
from c3nav.mapdata.models import MapUpdate
mapupdate = MapUpdate.current_cache_key() mapupdate = MapUpdate.current_cache_key()
if self._mapupdate != mapupdate: if self._mapupdate != mapupdate:
self._items = OrderedDict() self._items = OrderedDict()
@ -52,3 +54,18 @@ class LocalCacheProxy:
cache.set(key, value, expire) cache.set(key, value, expire)
self._items[key] = value self._items[key] = value
self._prune() self._prune()
def clear(self):
self._items.clear()
class RequestLocalCacheProxy(LocalCacheProxy):
""" this is a subclass without prune, to be cleared after every request """
def _prune(self):
pass
def _check_mapupdate(self):
pass
per_request_cache = RequestLocalCacheProxy(maxsize=settings.CACHE_SIZE_LOCATIONS)

View file

@ -242,7 +242,7 @@ class Locator:
return norm return norm
def locate_range(self, scan_data: ScanData, permissions=None, orig_addr=None): def locate_range(self, scan_data: ScanData, permissions=None, orig_addr=None):
peer_ids = tuple(i for i in scan_data if i < len(self.xyz)) peer_ids = tuple(i for i, item in scan_data.items() if i < len(self.xyz) and item.distance)
if len(peer_ids) < 3: if len(peer_ids) < 3:
# can't get a good result from just two beacons # can't get a good result from just two beacons
@ -262,7 +262,7 @@ class Locator:
# create 2d array with x, y, z, distance as rows # create 2d array with x, y, z, distance as rows
np_ranges = np.hstack(( np_ranges = np.hstack((
relevant_xyz, relevant_xyz,
np.array(tuple(scan_data[i].distance for i in peer_ids)).reshape((-1, 1)), np.array(tuple(float(scan_data[i].distance) for i in peer_ids)).reshape((-1, 1)),
)) ))
#print(np_ranges) #print(np_ranges)

View file

@ -115,3 +115,6 @@ class LocateIBeaconPeerSchema(BaseSchema):
class BeaconMeasurementDataSchema(BaseSchema): class BeaconMeasurementDataSchema(BaseSchema):
wifi: list[list[LocateWifiPeerSchema]] = [] wifi: list[list[LocateWifiPeerSchema]] = []
ibeacon: list[list[LocateIBeaconPeerSchema]] = [] ibeacon: list[list[LocateIBeaconPeerSchema]] = []
def __bool__(self):
return bool(self.wifi or self.ibeacon)

View file

@ -390,6 +390,7 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'c3nav.mapdata.middleware.RequestCacheMiddleware',
'c3nav.mapdata.middleware.UserDataMiddleware', 'c3nav.mapdata.middleware.UserDataMiddleware',
'c3nav.site.middleware.MobileclientMiddleware', 'c3nav.site.middleware.MobileclientMiddleware',
'c3nav.control.middleware.UserPermissionsMiddleware', 'c3nav.control.middleware.UserPermissionsMiddleware',

View file

@ -1999,3 +1999,21 @@ blink {
} }
} }
} }
.ap-name-bssid-result {
border-radius: 4px;
border: 1px solid gray;
padding: 4px 0;
box-shadow: inset 0px 0px 1px gray;
thead {
border-bottom: 1px solid gray;
}
td, th {
padding: 0 8px;
border: none;
font-size: 0.9em;
}
}

View file

@ -140,6 +140,17 @@ c3nav = {
if (window.mobileclient) { if (window.mobileclient) {
$body.addClass('mobileclient'); $body.addClass('mobileclient');
c3nav._set_user_location(null); c3nav._set_user_location(null);
try {
c3nav._ap_name_mappings = JSON.parse(localStorageWrapper.getItem('c3nav.wifi-scanning.ap-names'));
} catch (e) {
// ignore
}
if (c3nav._ap_name_mappings === null) {
c3nav._ap_name_mappings = {};
}
} else { } else {
document.addEventListener('visibilitychange', c3nav.on_visibility_change, false); document.addEventListener('visibilitychange', c3nav.on_visibility_change, false);
} }
@ -1465,9 +1476,50 @@ c3nav = {
.html((!no_close) ? '<button class="button-clear material-symbols" id="close-modal">clear</button>' : '') .html((!no_close) ? '<button class="button-clear material-symbols" id="close-modal">clear</button>' : '')
.append(content || '<div class="loader"></div>'); .append(content || '<div class="loader"></div>');
if ($modal.find('[name=look_for_ap]').length) { if ($modal.find('[name=look_for_ap]').length) {
if (!window.mobileclient) {
alert('need app!')
}
$modal.find('button').hide(); $modal.find('button').hide();
} }
}, },
_ap_name_scan_result_update: function () {
const $modal = $('#modal');
const $match_ap = $modal.find('[name=look_for_ap]');
if ($match_ap.length) {
const $wifi_bssids = $('[name=wifi_bssids]');
const ap_name = $match_ap.val();
const found_bssids = {};
let scan_complete = false;
if (ap_name in c3nav._ap_name_mappings) {
const mappings = c3nav._ap_name_mappings[ap_name];
for (const mapping of mappings) {
scan_complete = true;
for (const bssid of mapping) {
found_bssids[bssid] = (found_bssids[bssid] ?? 0) + 1;
if (found_bssids[bssid] === 1) {
scan_complete = false;
}
}
}
}
const $table = $('<table class="ap-name-bssid-result"><thead><tr><th>BSSID</th><th>count</th></tr></thead></table>')
for (const [bssid, count] of Object.entries(found_bssids)) {
$table.append(`<tr><td>${bssid}</td><td>${count}</td></tr>`);
}
$modal.find('.ap-name-bssid-result').remove();
$modal.find('form').before($table);
if (scan_complete) {
// todo only bssids that have count > 1
$wifi_bssids.val(JSON.stringify(Object.keys(found_bssids)));
$('#modal button[type=submit]').show();
}
}
},
_modal_click: function (e) { _modal_click: function (e) {
if (!c3nav.modal_noclose && (e.target.id === 'modal' || e.target.id === 'close-modal')) { if (!c3nav.modal_noclose && (e.target.id === 'modal' || e.target.id === 'close-modal')) {
history.back(); history.back();
@ -2118,14 +2170,24 @@ c3nav = {
_last_wifi_peers: [], _last_wifi_peers: [],
_last_ibeacon_peers: [], _last_ibeacon_peers: [],
_no_scan_count: 0, _no_scan_count: 0,
_ap_name_mappings: {},
_enable_scan_debugging: false,
_scan_debugging_results: [],
_wifi_scan_results: function (peers) { _wifi_scan_results: function (peers) {
peers = JSON.parse(peers); peers = JSON.parse(peers);
if (c3nav._enable_scan_debugging) {
c3nav._scan_debugging_results.push({
timestamp: Date.now(),
peers: peers,
});
}
if (c3nav.ssids) { if (c3nav.ssids) {
peers = peers.filter(peer => c3nav.ssids.includes(peer.ssid)); peers = peers.filter(peer => c3nav.ssids.includes(peer.ssid));
} }
let match_ap = $('[name=look_for_ap]').val(),
found_bssids = []; const ap_name_mappings = {};
for (const peer of peers) { for (const peer of peers) {
if (peer.level !== undefined) { if (peer.level !== undefined) {
@ -2137,26 +2199,21 @@ c3nav = {
peer.distance_sd = peer.rtt.distance_std_dev_mm / 1000; peer.distance_sd = peer.rtt.distance_std_dev_mm / 1000;
delete peer.rtt; delete peer.rtt;
} }
if (match_ap && peer.ap_name === match_ap) { if (peer.ap_name) {
found_bssids.push(peer.bssid); let mapping = ap_name_mappings[peer.ap_name] =(ap_name_mappings[peer.ap_name] ?? new Set());
mapping.add(peer.bssid);
} }
} }
if (found_bssids.length) {
let $wifi_bssids = $('[name=wifi_bssids]'), for (const [name, mapping] of Object.entries(ap_name_mappings)) {
val = JSON.parse($wifi_bssids.val()), let mappings = c3nav._ap_name_mappings[name] = (c3nav._ap_name_mappings[name] ?? []);
added = 0; mappings.push([...mapping]);
for (let bssid of found_bssids) {
if (!val.includes(bssid)) {
val.push(bssid);
added++;
}
}
if (added) {
$wifi_bssids.val(JSON.stringify(val));
} else {
$('#modal button[type=submit]').show();
}
} }
localStorageWrapper.setItem('c3nav.wifi-scanning.ap-names', JSON.stringify(c3nav._ap_name_mappings));
c3nav._ap_name_scan_result_update();
c3nav._last_wifi_peers = peers; c3nav._last_wifi_peers = peers;
c3nav._after_scan_results(); c3nav._after_scan_results();
}, },