forked from RocketMap/RocketMap
-
Notifications
You must be signed in to change notification settings - Fork 0
/
workers.py
218 lines (169 loc) 路 7.58 KB
/
workers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
import logging
import sys
import time
import unittest
from threading import Lock
from geopy.distance import vincenty
from queue import PriorityQueue
from management_errors import NoMoreWorkers
from pogoservice import BanChecker, NetworkIssueRetryer, \
WorkingTimeScheduler, AccountReplacer, BlindChecker, TravelTime, CaptchaChecker, ApiDelay, ApplicationBehaviour
log = logging.getLogger(__name__)
'''
A worker that delegates to an underlying account. The underlying account is
dynamic and may be replaced based on captchas, sleep intervals errors or
similar. Given enough underlying accounts, a worker will normally not fail.
Provide scan method that obey api and KPH speed restrictions, suspending thread
if needed. Client code does not need to know about KPH or 10 second sleep
limits.
Based on distance between previous location, new location and KPH, will
determine earliest legal time scan can be performed and use this.
Transparently handles captchas so clients dont have to see them
'''
class WorkerQueueManager(object):
def __init__(self, account_manager, fast_speed, slow_speed, num_queues):
self.account_manager = account_manager
self.worker_queues = []
for i in range(num_queues):
account = account_manager.get_account()
worker = wrap_account(account, account_manager)
self.worker_queues.append(WorkerQueue(worker, fast_speed, slow_speed))
self.discarded_worker_queues = []
self.lock = Lock()
def is_scanning_active(self):
return len(self.worker_queues) > 0
def get_worker_for_location(self, location, priority_encounter, optional_encounter):
self.lock.acquire()
try:
optimal_queue = sys.maxsize
idx_of_optimal = -1
for idx, worker in enumerate(self.worker_queues):
time_to_service = worker.time_to_service(location, optional_encounter, priority_encounter)
if optimal_queue > time_to_service:
optimal_queue = time_to_service
idx_of_optimal = idx
if idx_of_optimal < 0:
raise NoMoreWorkers
return self.worker_queues[idx_of_optimal]
finally:
self.lock.release()
def discard_worker(self, scanner):
log.warn("Discarding worker due to (assumed) permanent failures" + scanner)
self.discarded_worker_queues.append(scanner)
def free_worker(self, worker):
''' no-op'''
class QueueEntry:
def __init__(self, location, encounter_id):
self.location = location
self.encounter_id = encounter_id
class WorkerQueue:
queue = PriorityQueue()
''' Represents a queue of actions waiting for a given worker '''
def __init__(self, worker, fast_speed, slow_speed):
self.slow_speed = slow_speed
self.fast_speed = fast_speed
self.worker = worker
def __str__(self):
return "WorkerQueue with worker {}, queuelen={}".format(str(self.worker),
str(self.queue.qsize()))
def time_to_service(self, location):
if self.queue.qsize() > 5:
return sys.maxsize # dont even consider it, we're probably lagging all across the board
time_to_service = 0
currentpos = self.worker.position()
if not currentpos:
return 0
# only considers time as last element in list right now. Deal with priorities later.
# And consider high-pri items vs zero travel time
# also consider not moving when encountering close-up
for queueEntry in self.queue.queue:
time_to_service += self.travel_time(currentpos, queueEntry.location)
time_to_service += 3 # for the encounter.
currentpos = queueEntry.location
time_to_service += self.travel_time(currentpos, location)
return time_to_service
def travel_time(self, current_positon, next_position):
if not current_positon or current_positon[0] is None:
return
distance = vincenty(current_positon, next_position).m
return self.__sleep_seconds(distance)[1]
def __sleep_seconds(self, distance):
slow_seconds = distance / self.slow_speed
fast_seconds = distance / self.fast_speed
if slow_seconds < (fast_seconds + 30):
return "slow", slow_seconds
else:
return "fast", fast_seconds
def blocking_wait(self, encounter_id, location):
self.enqueue(encounter_id, location)
self.__wait_for(encounter_id)
def do_encounter_pokemon(self, encounter_id, spawn_point_id, location):
self.blocking_wait(encounter_id, location)
return self.worker.do_encounter_pokemon(encounter_id, spawn_point_id, location)
def enqueue(self, encounter_id, location):
if len(self.queue.queue) > 5:
raise NoMoreWorkers
self.queue.put(QueueEntry(location, encounter_id))
def __wait_for(self, encounter_id):
while self.queue.queue[0].encounter_id != encounter_id:
time.sleep(3)
self.queue.get()
return self.worker
class RocketPool(object):
def __init__(self, account_manager, fast_speed, slow_speed):
self.account_manager = account_manager
def get(self):
acct = self.account_manager.get_account()
return wrap_account(acct, self.account_manager)
def wrap_account(account, account_manager):
replacer = AccountReplacer(account, account_manager)
api_delayed = ApiDelay(replacer)
retryer = NetworkIssueRetryer(api_delayed)
ban_checker = BanChecker(retryer, account_manager, replacer)
captcha_checker = CaptchaChecker(ban_checker, account_manager)
blind_checker = BlindChecker(captcha_checker, account_manager, replacer)
scheduler = WorkingTimeScheduler(blind_checker, account_manager.args.account_search_interval, replacer)
travel_time = TravelTime(scheduler)
return travel_time
def wrap_account_no_replace(account, account_manager, fast_speed=25):
api_delayed = ApiDelay(account)
retryer = NetworkIssueRetryer(api_delayed)
ban_checker = BanChecker(retryer, account_manager, None)
captcha_checker = CaptchaChecker(ban_checker, account_manager)
travel_time = TravelTime(captcha_checker, fast_speed)
ab = ApplicationBehaviour(travel_time)
return ab
def wrap_accounts_minimal(account, account_manager):
api_delayed = ApiDelay(account)
retryer = NetworkIssueRetryer(api_delayed)
captcha_checker = CaptchaChecker(retryer, account_manager)
travel_time = TravelTime(captcha_checker)
ab = ApplicationBehaviour(travel_time)
return ab
class DummyAccount(object):
def most_recent_position(self):
return (2.0, 3.0, 4)
class DummyAccount2(object):
def most_recent_position(self):
return ()
class DummyArgs:
account_search_interval = 299
class DummyAccountManager:
def __init__(self, account):
self.account = account
self.args = DummyArgs()
def get_account(self):
return self.account
class WorkerQueue_ServiceTime(unittest.TestCase):
def test(self):
manager = DummyAccountManager(DummyAccount())
worker = wrap_account(DummyAccount(), manager)
queue = WorkerQueue(worker,15,9)
queue.enqueue("ABCD", (2.1, 3.1))
queue.enqueue("EFG-ENC", (2.2, 3.2))
service = queue.time_to_service((2.3, 3.3))
d1 = vincenty((2.0, 3.0, 4), (2.1, 3.1)).m
d2 = vincenty((2.1, 3.1), (2.2, 3.2)).m
d3 = vincenty((2.2, 3.2), (2.3, 3.3)).m
print("distance is " + str(d1 + d2 + d3))
self.assertEqual(4711.4780730903, service)