def _refresh_certs(): p = Popen("security find-identity -p codesigning -v", bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) expire = now() + 30000 while p.poll() is None: if now() < expire: gevent.sleep(1) expr = re.compile(r'\s*\d+\)\s*\S+\s+"([^"]+(\s+\(.+\)))"') for each in map(lambda x: x.decode("utf8").strip(), p.stdout.readlines()): result = expr.match(each) if not result: continue if "REVOKED" in each: Log("过期的证书[%s]" % each) continue # noinspection PyBroadException try: cert, _id = result.groups() _certs[cert] = _id.strip()[1:-2] _certs[cert[:-len(_id)]] = _id.strip()[1:-2] Log("有效的证书[%s]" % each) except: Log("跳过[%s]" % each)
def info(_req: HttpRequest, project: str, uuid: str = ""): _project = IosProjectInfo.objects.filter( project=project).first() # type: IosProjectInfo udid = "" if not _project: return {} ret = str_json(_project.comments) ready = False if uuid: _user = UserInfo.objects.filter(uuid=uuid).first() # type: UserInfo if _user: ready = True udid = _user.udid else: Log("上传的uuid无效[%s]" % uuid) else: uuid = _req.get_signed_cookie("uuid", "", salt="zhihu") _user = UserInfo.objects.filter(uuid=uuid).first() # type: UserInfo if _user: ready = True udid = _user.udid else: Log("cookie中的uuid无效[%s]" % uuid) if ready: ret.update({ "ready": True, }) _task = TaskInfo.objects.filter(uuid=uuid).first() # type:TaskInfo if _task: if _task.state == "fail" or _task.expire.timestamp() * 1000 < now( ): __add_task(_user) else: uuid = _newbee(_project) ret.update({ "ready": False, }) ret.update({ "uuid": uuid, }) rsp = JsonResponse({ "ret": 0, "result": ret, "download_id": random_str(32), }) rsp.set_signed_cookie("uuid", uuid, salt="zhihu", expires=3600 * 24) if udid: rsp.set_signed_cookie("udid", _user.udid, salt="zhihu", expires=300 * 3600 * 24) return rsp
def _wait_code(info: IosAccountInfo, session: requests.Session, ts, save_now=False): """ 等待二次提交的需要 """ Log("等待[%s]二次验证" % info.account) # last = info.security_code expire = now() + 1200 * 1000 while now() < expire: gevent.sleep(1) for data in message_from_topic(__pub, is_json=True, limit=1): if data.get("ts") > ts: if data.get("code") and data.get("account") in { info.account, "*" }: rsp = session.post( "https://idmsa.apple.com/appleauth/auth/verify/phone/securitycode", json={ "securityCode": { "code": str(data.get("code")), }, "phoneNumber": { "id": 1, }, "mode": "sms", }) # Log("[%s] %s" % (rsp.status_code, rsp.json())) # rsp = session.post("https://idmsa.apple.com/appleauth/auth/verify/trusteddevice/securitycode", json={ # "securityCode": {"code": data["code"]}, # }) if rsp.status_code != 200: Log("账号[%s]验证校验码[%s]失败[%s]" % (info.account, data.get("code"), rsp.status_code)) # if rsp.status_code != 204: # Log("账号[%s]验证校验码[%s]失败[%s]" % (info.account, data.get("code"), rsp.status_code)) # continue rsp = session.get( "https://idmsa.apple.com/appleauth/auth/2sv/trust") if rsp.status_code == 204: session.cookies.update( {"__expire": str(now() + 6 * 3600 * 1000)}) if save_now: info.cookie = json_str(session.cookies) info.save() Log("账号[%s]登录成功" % info.account) return session raise Fail("登录二次验证超时")
def __list_all_devices(_config: IosAccountHelper): ret = _config.post( "获取所有的列表", "https://developer.apple.com/services-account/QH65B2/account/ios/device/listDevices.action?teamId=", data={ "includeRemovedDevices": True, "includeAvailability": True, "pageNumber": 1, "pageSize": 100, "sort": "status%3dasc", "teamId": _config.team_id, }, log=False) for device in ret["devices"]: # type: Dict _reg_device( device["deviceId"], device["deviceNumber"], device.get("model", device.get("deviceClass", "#UNKNOWN#")), device.get("serialNumber", "#UNKNOWN#")) # 更新一下info devices = list(map(lambda x: x["deviceNumber"], ret["devices"])) if json_str(devices) != _config.info.devices: Log("更新设备列表[%s]数量[%s]=>[%s]" % (_config.account, _config.info.devices_num, len(devices))) _config.info.devices = json_str(devices) _config.info.devices_num = len(devices) _config.info.save()
def db_get_list(key_list: Sequence[str], allow_not_found=True, fail=True, model=None): if len(key_list) == 0: return [] start = time.time() orig_ret = db_model.mget(key_list) cost = time.time() - start if cost > 0.01: Log("耗时的db操作[%s][%s]" % (len(key_list), key_list[:10])) ret = list(filter(lambda x: x is not None, orig_ret)) if len(ret) != len(key_list): if model is not None: tmp = model + ":" assert len(list(filter(lambda x: not x.startswith(tmp), key_list))) == 0 for i, k_v in enumerate(zip(key_list, orig_ret)): if k_v[1] is not None: continue orig_ret[i] = mongo_get(k_v[0], model=model) ret = list(filter(lambda x: x is not None, orig_ret)) if len(ret) != len(key_list): if not allow_not_found: if fail: if isinstance(fail, bool): raise Fail("少了一部分的数据") else: raise Fail(fail) return ret
def __add_task(_user: UserInfo): _account = IosAccountInfo.objects.filter( account=_user.account).first() # type:IosAccountInfo _project = IosProjectInfo.objects.filter( project=_user.project).first() # type:IosProjectInfo _profile = IosProfileInfo.objects.filter( sid="%s:%s" % (_user.account, _user.project)).first() # type:IosProfileInfo Assert(_profile, "[%s][%s]证书无效" % (_project.project, _account.account)) Log("[%s]发布任务[%s]" % (_user.project, _user.account)) db_session.publish( "task:package", json_str({ "cert": "iPhone Developer: zhangming luo", "cert_p12": "", "mp_url": entry("/apple/download_mp?uuid=%s" % _user.uuid), "mp_md5": md5bytes(base64decode(_profile.profile)), "project": _project.project, "ipa_url": _asset_url("%s/orig.ipa" % _user.project), "ipa_md5": _project.md5sum, "ipa_new": "%s_%s.ipa" % (_account.team_id, _account.devices_num), "upload_url": entry("/apple/upload_ipa?project=%s&account=%s" % (_user.project, _user.account)), "ts": now(), }))
def _reg_cert(_config: IosAccountInfo, cert_req_id, name, cert_id, sn, type_str, expire): sid = "%s:%s" % (_config.account, name) orig = str_json( db_model.get("IosCertInfo:%s:%s" % (_config.account, name)) or '{}') obj = { "name": name, "app": _config.account, "cert_req_id": cert_req_id, "cert_id": cert_id, "sn": sn, "type_str": type_str, "expire": expire, "expire_str": date_time_str(expire), } if orig == obj: return cert_req_id db_model.set("IosCertInfo:%s:%s" % (_config.account, name), json_str(obj), ex=(expire - now()) // 1000) _info = IosCertInfo() _info.sid = sid _info.app = _config.account _info.cert_req_id = cert_req_id _info.cert_id = cert_id _info.sn = sn _info.type_str = type_str _info.name = name _info.create = now() _info.expire = datetime.datetime.utcfromtimestamp(expire // 1000) _info.save() Log("注册新的证书[%s][%s]" % (name, cert_req_id)) return cert_req_id
def mongo_get(key: str, model=None, active=True) -> Optional[Dict]: if model is not None: if not key.startswith(model + ":"): return None i = key.index(':') if i <= 0: return None model, _id = key[0:i], key[i + 1:] if active: if active is True: active = {"ts": now()} ret = mongo(model).find_one_and_update( {"_id": key}, {"$set": { "__active__": active }}) if ret is not None: ret = json.dumps(ret, separators=(',', ':'), sort_keys=True, ensure_ascii=False) Log("从mongodb[%s]激活[%s][%s]" % (model, key, ret)) db_model.set(key, ret) return ret else: return mongo(model).find_one({"_id": _id})
def __logout(self): Log("登出账号[%s]" % self.account) _key = "apple:developer:cookie" self.__expire = 0 self.cookie.clear() self.info.cookie = "{}" self.info.save()
def __add_task(_user: UserInfo): _account = IosAccountInfo.objects.get(account=_user.account) _project = IosProjectInfo.objects.get(project=_user.project) _profile = IosProfileInfo.objects.get(sid="%s:%s" % (_user.account, _user.project)) _cert = IosCertInfo.objects.get( sid="%s:%s" % (_user.account, str_json_a(_profile.certs)[0])) Assert(_profile, "[%s][%s]证书无效" % (_project.project, _account.account)) Assert(_project.md5sum, "项目[%s]原始ipa还没上传" % _project.project) Assert(_cert.cert_p12, "项目[%s]p12还没上传" % _project.project) Log("[%s]发布任务[%s]" % (_user.project, _user.account)) _task, _ = TaskInfo.objects.get_or_create(uuid=_user.uuid) _task.state = "none" _task.worker = "" _task.save() resign_ipa.delay( **{ "uuid": _user.uuid, "cert": "iPhone Developer: %s" % _cert.name, "cert_url": entry("/apple/download_cert?uuid=%s" % _user.uuid), "cert_md5": md5bytes(base64decode(_cert.cert_p12)), "mp_url": entry("/apple/download_mp?uuid=%s" % _user.uuid), "mp_md5": md5bytes(base64decode(_profile.profile)), "project": _project.project, "ipa_url": entry("/projects/%s/orig.ipa" % _user.project), "ipa_md5": _project.md5sum, "ipa_new": "%s_%s.ipa" % (_account.team_id, _account.devices_num), "upload_url": entry("/apple/upload_ipa?uuid=%s" % _user.uuid), "process_url": entry("/apple/task_state?uuid=%s" % _user.uuid), })
def __profile_detail(_config: IosAccountHelper, _profile: IosProfileInfo): ret = _config.post( "获取profile详情", "https://developer.apple.com/services-account/QH65B2/account/ios/profile/getProvisioningProfile.action?teamId=", data={ "includeInactiveProfiles": True, "provisioningProfileId": _profile.profile_id, "teamId": _config.team_id, }, ) profile = ret["provisioningProfile"] devices = list(map(lambda x: x["deviceNumber"], profile["devices"])) devices_str = json_str(devices) if _profile.devices != devices_str: _profile.devices = devices_str _profile.devices_num = len(devices) __download_profile(_config, _profile) Log("更新profile[%s]" % _profile.sid) _profile.save() certs = [] for each in profile["certificates"]: certs.append(each["certificateId"]) if _profile.certs != json_str(certs): _profile.certs = json_str(certs) _profile.save()
def task_state(uuid: str, worker: str = "", state: str = "", auto_start=True): _task, _ = TaskInfo.objects.get_or_create(uuid=uuid) if state: if _task.worker: Assert(_task.worker == worker, "越权更改任务[%s]状态[%s]=>[%s]" % (uuid, _task.worker, worker)) else: _task.worker = worker _task.save() if _task.state != state: Log("任务[%s]状态变更[%s]=>[%s]" % (uuid, _task.state, state)) _task.state = state _task.save() return { "succ": True, } else: if auto_start and _task.state in {"fail", "expire", "none", ""}: # noinspection PyTypeChecker rebuild({ "uuid": uuid, }) # 获取当前的状态 return { "code": 1 if _task.state in {"fail", "expire"} else 0, "finished": _task.state == "succ", "progress": "%d%%" % ((_states.index(_task.state) + 1) * 100 / len(_states)) if _task.state in _states else "0%", }
def _refresh_certs(): expr = re.compile(r'\s*\d+\)\s*\S+\s+"([^"]+(\s+\(.+\)))"') for each in _shell_run("security find-identity -p codesigning -v"): result = expr.match(each) if not result: continue if "REVOKED" in each: Log("过期的证书[%s]" % each) continue # noinspection PyBroadException try: cert, _id = result.groups() _certs[cert] = _id.strip()[1:-2] _certs[cert[:-len(_id)]] = _id.strip()[1:-2] Log("有效的证书[%s]" % each) except: Log("跳过[%s]" % each)
def mongo_index(cate, index, unique=False): collection = _mongo(cate) if ("%s_1" % index) not in collection.index_information(): Log("创建mongo索引[%s][%s][unique:%s]" % (cate, index, unique)) collection.create_index(index, unique=unique, sparse=True, background=True)
def add_device(_content: bytes, uuid: str, udid: str = ""): _key = "uuid:%s" % uuid _detail = str_json(db_session.get(_key) or "{}") _account = _detail.get("account") project = _detail["project"] if not _detail: raise Fail("无效的uuid[%s]" % uuid) if not udid: # todo: 验证来源 with Block("提取udid", fail=False): udid = re.compile(br"<key>UDID</key>\n\s+<string>(\w+)</string>" ).findall(_content)[0].decode("utf8") if not udid: # 提取udid失败后删除uuid db_session.delete(_key) return { "succ": False, } for _user in UserInfo.objects.filter(udid=udid): _account = _user.account if _user.project == _detail["project"]: if uuid != _user.uuid: Log("转移设备的[%s]的uuid[%s]=>[%s]" % (udid, uuid, _user.uuid)) uuid = _user.uuid break if not _account: Log("为设备[%s]分配账号" % udid) _account = __fetch_account(udid, project, __add_device) else: _account = IosAccountInfo.objects.filter(account=_account).first() _user = UserInfo(uuid=uuid) _user.udid = udid _user.project = _detail["project"] _user.account = _account.account _user.save() if test_env(): # 测试环境可能uuid要重复测 pass else: db_session.delete(_key) __add_task(_user) return HttpResponsePermanentRedirect( "https://iosstore.sklxsj.com/detail.php?project=%s" % project)
def _debug(): _info, created = IosProjectInfo.objects.get_or_create(sid="test", project="test") if created: Log("初始化一个测试项目") _info.bundle_prefix = "com.test" _info.save() upload_project_ipa._orig_func("test", read_binary_file("projects/test.ipa"))
def _reg_device(device_id: str, udid: str, model: str, sn: str) -> str: # 需要缓存 _info = IosDeviceInfo() _info.udid = udid _info.device_id = device_id _info.model = model _info.sn = sn _info.create = now() _info.save() Log("注册新的设备[%s][%s][%s]" % (udid, device_id, sn)) return udid
def mapping_get(cate, mapping, prop="_key") -> Optional[str]: ret = db_model_ex.get("%s:%s" % (cate, mapping)) if ret is None: if prop is not None and len(prop): tmp = mongo(cate).find_one({prop: mapping}) if tmp is not None: Log("激活索引[%s:%s]=>[%s]" % (cate, mapping, tmp["id"])) ret = "%s:%s" % (cate, tmp["id"]) db_model_ex.set("%s:%s" % (cate, mapping), ret, ex=3 * 24 * 3600) return ret
def _reg_app(_config: IosAccountInfo, project: str, app_id_id: str, name: str, prefix: str, identifier: str): sid = "%s:%s" % (_config.account, project) _info = IosAppInfo() _info.sid = sid _info.app = _config.account _info.app_id_id = app_id_id _info.identifier = identifier _info.name = name _info.prefix = prefix _info.create = now() _info.save() Log("注册新的app[%s][%s][%s]" % (_config.account, app_id_id, identifier))
def upload_ipa(project: str, account: str, file: bytes): base = os.path.join("static/income", project) os.makedirs(base, exist_ok=True) _info = IosAccountInfo.objects.filter( account=account).first() # type:IosAccountInfo Assert(_info, "账号[%s]不存在" % account) filename = "%s_%s.ipa" % (_info.team_id, _info.devices_num) with open(os.path.join(base, filename), mode="wb") as fout: fout.write(file) Log("[%s]收到新打包的ipa[%s]" % (account, filename)) return { "succ": True, }
def upload_project_ipa(project: str, file: bytes): _info = IosProjectInfo.objects.get(project=project) base = os.path.join("static/projects", project) os.makedirs(base, exist_ok=True) with open(os.path.join(base, "orig.ipa"), mode="wb") as fout: fout.write(file) if _info.md5sum != md5bytes(file): _info.md5sum = md5bytes(file) _info.save() Log("更新工程[%s]的ipa[%s]" % (project, _info.md5sum)) # todo: 激活更新一下 return { "succ": True, }
def _load_awwdrca(): """ 加载 Apple Worldwide Developer Relations Certification Authority """ for each in _shell_run("security find-certificate -a", succ_only=True): if "Apple Worldwide Developer Relations Certification Authority" in each: return True Log("加载AppleWWDRCA") base = tempfile.gettempdir() _shell_run( "curl -sSLk https://developer.apple.com/certificationauthority/AppleWWDRCA.cer -o %s/AppleWWDRCA.cer" % base) _shell_run("security add-certificates %s/AppleWWDRCA.cer" % base) return True
def _newbee(_project: IosProjectInfo): # 默认一天的时效 for _ in range(100): import uuid _uuid = uuid.uuid4() if db_session.set("uuid:%s" % _uuid, json_str({ "project": _project.project, }), ex=3600 * 24, nx=True): Log("生成[%s]一个新的uuid[%s]" % (_project.project, _uuid)) return str(_uuid) raise Fail("生成失败")
def _reg_cert(_config: IosAccountInfo, cert_req_id, name, cert_id, sn, type_str, expire): sid = "%s:%s" % (_config.account, cert_id) _info = IosCertInfo() _info.sid = sid _info.account = _config.account _info.cert_req_id = cert_req_id _info.cert_id = cert_id _info.sn = sn _info.type_str = type_str _info.name = name _info.create = now() _info.expire = datetime.datetime.utcfromtimestamp(expire // 1000) _info.save() Log("更新证书[%s][%s]" % (name, cert_req_id)) return cert_req_id
def __list_all_profile(_config: IosAccountHelper, target_project: str = ""): ret = _config.post( "更新列表", "https://developer.apple.com/services-account/QH65B2/account/ios/profile/listProvisioningProfiles.action?teamId=", data={ "includeInactiveProfiles": True, "onlyCountLists": True, "sidx": "name", "sort": "name%3dasc", "teamId": _config.team_id, "pageNumber": 1, "pageSize": 500, }) target = None for profile in ret["provisioningProfiles"]: if not profile["name"].startswith("专用 "): continue project = profile["name"].replace("专用 ", "") _info = IosProfileInfo.objects.filter( sid="%s:%s" % (_config.account, project)).first() # type: IosProfileInfo expire = _to_dt(profile["dateExpire"]) detail = False if not _info: _info = IosProfileInfo() _info.sid = "%s:%s" % (_config.account, project) _info.app = _config.account _info.profile_id = profile["provisioningProfileId"] _info.expire = expire _info.devices = "" _info.devices_num = 0 detail = True if _info.expire != expire: _info.profile_id = profile["provisioningProfileId"] _info.expire = expire detail = True if detail: # 获取细节 __profile_detail(_config, _info) Log("更新profile[%s]" % _info.sid) _info.save() if project == target_project: target = _info return ret, target
def login_by_fastlane(_req: HttpRequest, cmd: str = "", account: str = ""): """ 通过命令获取会话 fastlane spaceauth -u [email protected] ex. export FASTLANE_SESSION='---\n- !ruby/object:HTTP::Cookie\n name: myacinfo\n value: DAWTKNV2e32a0823580640561dc9dfd382265048c32c2aa5b04485930b2ada1d1c7eba28dee6c6065c749f708f2a429f8f9f2d0f2f7d2ad629652ca5bc3383c0772d51c6ca34a2f7b09141f7b19c358c2b25d0738079ab1e48a06610228a574342c84ef13349ede1a012c25155e265a17989a3b09631dd953954505153fb7ef71aecfe303530cb684c89e8402cb82d8d4d93c3fc3c1209e334f65f71c7ae0cfdf0349ec757abcb104a591f5bea25ac0f1207004c19645b80ed82fb5cd0d3a740224b2f3aef9e91b049bb63a94ae3c76d027411f077069865209d733617a7a84f54cf7e9488e9b4f0a918d29f184f5ec76d95b5f55def61682f70b7f10fc12dc43d6e380213dd1f702a4f3ccab3ad438cd0f6a87c295e028a12ec410aa3fa689210d040377995914d4d3718b90f85ad5452d5db47ef9ae11c6b3216cf8ab61025adc203b0bf072ce832240c384d83f0f4aaf477a3c7313de4c20c5e32c530ff1ad76aebcd8538ac485a9a46941dfa94ee2f3fb40e38666533326562333665333834343461323366383636666563303166613533303330656361323836MVRYV2\n domain: apple.com\n for_domain: true\n path: "/"\n secure: true\n httponly: true\n expires: \n max_age: \n created_at: 2019-03-15 11:55:51.031441000 +08:00\n accessed_at: 2019-03-15 11:55:51.041509000 +08:00\n- !ruby/object:HTTP::Cookie\n name: dqsid\n value: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJzZnp2ZGFldGJPeWpXaTc1LVpTRmNBIn0.IEYqXF-pxIYdIwP2rdNRNhoxdCzJgGxt4olTZa2fXo8\n domain: olympus.itunes.apple.com\n for_domain: false\n path: "/"\n secure: true\n httponly: true\n expires: \n max_age: 1800\n created_at: &1 2019-03-15 11:55:52.798977000 +08:00\n accessed_at: *1\n curl localhost:8000/apple/login_by_fastlane --data $FASTLANE_SESSION """ if not cmd: cmd = _req.body.decode("utf-8") Assert(len(cmd), "命令行不对") cmd = cmd.replace("\\n", "") cookie = dict(re.compile(r"name:\s+(\S+)\s+value:\s+(\S+)").findall(cmd)) if not account: rsp = requests.post( "https://developer.apple.com/services-account/QH65B2/account/getUserProfile", headers={"cookie": to_form_url(cookie, split=';')}) if rsp.status_code != 200: return { "succ": False, "reason": "无效的fastlane export", } data = rsp.json() account = data["userProfile"]["email"] if account: _info = IosAccountInfo.objects.filter( account=account).first() # type:IosAccountInfo if not _info: _info = IosAccountInfo() _info.account = account _info.save() _info.cookie = json_str(cookie) _info.headers = json_str({}) _info.save() Log("通过fastlane登录[%s]" % account) return { "succ": True, "msg": "登录[%s]成功" % account, } else: return { "succ": False, "msg": "请求不具备提取登录信息", }
def __download_profile(_config: IosAccountHelper, _profile: IosProfileInfo): ret = _config.post( "获取profile文件", "https://developer.apple.com/services-account/QH65B2/account/ios/profile/downloadProfileContent?teamId=", data={ "provisioningProfileId": _profile.profile_id, }, json_api=False, is_json=False, log=False, is_binary=True, method="GET", ) profile = base64(ret) if profile != _profile.profile: _profile.profile = base64(ret) _profile.save() Log("更新profile文件[%s]" % _profile.sid)
def _package(orig_file, provisioning_profile, certificate, output_path): base = tempfile.mkdtemp() ipa_file = os.path.join(base, "orig.ipa") mp_file = os.path.join(base, "mp.ipa") os.symlink(os.path.abspath(orig_file), ipa_file) os.symlink(os.path.abspath(provisioning_profile), mp_file) Log("准备ipa[%s]" % ipa_file) # with Block("打包", fail=False): # app = app_argument(ipa_file).unpack_to_dir(base) # Log("准备provision") # app.provision(mp_file) # Log("构造entitlements") # app.create_entitlements() # Log("签名证书") # app.sign(certificate) # Log("重新封包") # app.package(output_path) # Log("ipa[%s]" % output_path) try: if not os.path.isfile(output_path) or True: # 方案二打包 sh_file = os.path.join(base, "provisions.sh") if os.path.exists("provisions.sh"): os.symlink(os.path.abspath("provisions.sh"), sh_file) else: os.symlink(os.path.abspath("tools/provisions.sh"), sh_file) _shell_run("%s -p %s -c '%s' %s " % (sh_file, mp_file, certificate, ipa_file), succ_only=True, verbose=True, debug=True, pwd=base) Assert(os.path.isfile(os.path.join(base, "stage", "out.ipa")), "打包脚本失败了") os.rename(os.path.join(base, "stage", "out.ipa"), os.path.abspath(output_path)) if not os.path.isfile(output_path): raise Fail("打包失败") finally: shutil.rmtree(base, ignore_errors=True)
def _reg_device(account: str, device_id: str, udid: str, model: str, sn: str) -> str: # 需要缓存 _info = IosDeviceInfo() _info.sid = "%s:%s" % (account, udid) _info.account = account _info.udid = udid _info.device_id = device_id _info.model = model _info.sn = sn _info.create = now() _info.save() Log("注册新的设备[%s][%s][%s]" % (udid, device_id, sn)) _account = IosAccountInfo.objects.get(account=account) device_map = str_json(_account.devices) device_map[udid] = device_id if json_str(device_map) != _account.devices: _account.devices = json_str(device_map) _account.devices_num = len(device_map) _account.save() return udid
def upload_ipa(worker: str, uuid: str, file: bytes): _user = UserInfo.objects.get(uuid=uuid) project = _user.project account = _user.account base = os.path.join("static/income", project) os.makedirs(base, exist_ok=True) _info = IosAccountInfo.objects.filter( account=account).first() # type:IosAccountInfo Assert(_info, "账号[%s]不存在" % account) filename = "%s_%s.ipa" % (_info.team_id, _info.devices_num) with open(os.path.join(base, filename), mode="wb") as fout: fout.write(file) Log("[%s]收到新打包的ipa[%s]" % (account, filename)) # todo: 遍历所有的设备关联的包? _task, _ = TaskInfo.objects.get_or_create(uuid=uuid) if _task.worker in {worker, "none"}: _task.state = "succ" _task.worker = worker _task.size = len(file) _task.save() return { "succ": True, }