Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

context range: make a version that uses less memory #446

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 136 additions & 17 deletions wazo_confd/plugins/context_range/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,73 @@
from xivo_dao.resources.extension import dao as extension_dao


class Range:
def __init__(self, start, end):
self.start = start
self.end = end

def contains(self, exten):
return self.start <= exten <= self.end

def split_at(self, exten):
if exten < self.start:
return [self]
if exten > self.end:
return [self]
if exten == self.start:
return [Range(start=self.plus(range.start, 1), end=self.end)]
if exten == self.end:
return [Range(start=self.start, end=self.minus(range.end, 1))]
if self.start < exten < self.end:
return [
Range(start=self.start, end=self.minus(exten, 1)),
Range(start=self.plus(exten, 1), end=self.end),
]

def split_from_search(self, search):
matching = False
start = None
end = None

for exten in self._generate_extens():
if search in exten:
if matching is False:
start = exten
elif matching is True:
end = exten
matching = True
else:
if matching is True:
yield Range(start=start, end=end or start)
start = None
end = None
matching = False

def _generate_extens(self):
exten_len = len(self.start)
first = int(self.start)
last = int(self.end)

for n in range(first, last + 1):
yield str(n).rjust(exten_len, '0')

@staticmethod
def plus(exten, n):
length = len(exten)
return str(int(exten) + n).rjust(length, '0')

@staticmethod
def minus(exten, n):
length = len(exten)
return str(int(exten) - n).rjust(length, '0')

def __str__(self):
return f'Range({self.start}, {self.end})'

def __repr__(self):
return self.__str__()


class RangeFilter:
def __init__(
self, context, extension_dao, availability=None, search=None, **kwargs
Expand All @@ -23,14 +90,30 @@ def __init__(
self._used_extensions = set(e.exten for e in configured_extens)

def get_ranges(self, range_type):
ranges = self._extract_ranges(self._context, range_type)
unfiltered_extens = self._list_exten_from_ranges(ranges)
filtered_extens = (
exten for exten in unfiltered_extens if self._include_exten(exten)
)
filtered_ranges = list(self._ranges_from_extens(filtered_extens))
count = len(filtered_ranges)
return filtered_ranges, count
configured_ranges = self._extract_ranges(self._context, range_type)
i = 0
while i < len(configured_ranges):
range = configured_ranges[i]
for exten in self._used_extensions:
if range.contains(exten):
del configured_ranges[i] # Replaces i++
new_ranges = range.split_at(exten)
j = 0
for j, new in enumerate(new_ranges):
configured_ranges.insert(i + j, new)
if j > 0:
i += j - 1
i += 1

if self._search:
filtered_ranges = []
for range in configured_ranges:
for new_range in range.split_from_search(self._search):
filtered_ranges.append(new_range)
else:
filtered_ranges = configured_ranges

return [{'start': r.start, 'end': r.end} for r in filtered_ranges]

def _include_exten(self, exten):
if self._availability == 'available':
Expand All @@ -43,15 +126,15 @@ def _include_exten(self, exten):

def _extract_ranges(self, context, range_type):
if range_type == 'user':
return context.user_ranges
return [Range(r.start, r.end) for r in context.user_ranges]
elif range_type == 'group':
return context.group_ranges
return [Range(r.start, r.end) for r in context.group_ranges]
elif range_type == 'queue':
return context.queue_ranges
return [Range(r.start, r.end) for r in context.queue_ranges]
elif range_type == 'conference':
return context.conference_room_ranges
return [Range(r.start, r.end) for r in context.conference_room_ranges]
elif range_type == 'incall':
return context.incall_ranges
return [Range(r.start, r.end) for r in context.incall_ranges]
else:
assert False, f'{range_type} is not supported'

Expand All @@ -76,8 +159,18 @@ def _sort_ranges(ranges):
ranges_by_len[len(range.start)].append(range)

for length in sorted(ranges_by_len.keys()):
for range in sorted(ranges_by_len[length], key=attrgetter('start')):
yield range
previous = None
for current in sorted(ranges_by_len[length], key=attrgetter('start')):
if previous is None:
previous = current
else:
if previous.end >= current.start:
previous = Range(
start=previous.start, end=max(previous.end, current.end)
)
else:
yield previous
yield current

@staticmethod
def _ranges_from_extens(extensions):
Expand Down Expand Up @@ -121,6 +214,29 @@ def sort(self, ranges):
return sorted(ranges, key=self._key, reverse=self._reverse)


class RangeMerger:
def merge(self, ranges):
ranges_by_len = defaultdict(list)
for range in ranges:
ranges_by_len[len(range['start'])].append(range)

for length in sorted(ranges_by_len.keys()):
previous = None
for current in sorted(ranges_by_len[length], key=itemgetter('start')):
if previous is None:
previous = current
else:
if int(previous['end']) + 1 >= int(current['start']):
previous = {
'start': min(previous['start'], current['start']),
'end': max(previous['end'], current['end']),
}
else:
yield previous
previous = current
yield previous


class ContextRangeService:
def __init__(self, context_dao, extension_dao):
self._context_dao = context_dao
Expand All @@ -132,9 +248,12 @@ def search(self, context_id, range_type, tenant_uuids=None, **parameters):
filter = RangeFilter(context, self._extension_dao, **parameters)
paginator = RangePaginator(**parameters)
sorter = RangeSorter(**parameters)
merger = RangeMerger()

ranges, count = filter.get_ranges(range_type)
sorted_ranges = sorter.sort(ranges)
ranges = filter.get_ranges(range_type)
merged_ranges = list(merger.merge(ranges))
count = len(merged_ranges)
sorted_ranges = sorter.sort(merged_ranges)
paginated_ranges = paginator.paginate(sorted_ranges)

return count, paginated_ranges
Expand Down