Skip to content

Commit f9d05ff

Browse files
Glauber Varjãoclaude
andcommitted
fix: logging detalhado Fase 3/4 + fix indentação + mensagem clara por modelo
Testado em terminais reais: - DS-K1T671MF (V3.2.30): LOCALS/ funciona via HTTP (200 + JPEG) - DS-K1T672MX (V3.18.0): isSupportFDSearchDataPackage=false, sem download remoto Fase 3 agora loga Content-Type, tamanho e primeiros bytes de cada tentativa. Mensagem final indica limitação do firmware quando não suporta download. Bump v4.1.8 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c2b4298 commit f9d05ff

5 files changed

Lines changed: 107 additions & 86 deletions

File tree

build_info.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
BUILD_TIMESTAMP = "2026-04-01 19:08"
1+
BUILD_TIMESTAMP = "2026-04-01 19:28"

core/isapi_client.py

Lines changed: 100 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1899,11 +1899,39 @@ def _safe_get(endpoint):
18991899
break
19001900

19011901
# === FASE 3: FDSearch multipart FILTRADO por employeeNo ===
1902-
# Sem ?format=json, o terminal retorna o JPEG embutido no multipart
1902+
# Sem ?format=json, terminal retorna JPEG embutido no multipart boundary
19031903
_log("Tentativa 3: FDSearch multipart filtrado por employeeNo")
1904+
1905+
def _try_multipart_search(endpoint, payload_str, label):
1906+
"""Faz POST e tenta extrair JPEG do multipart. Retorna img ou None."""
1907+
try:
1908+
url = self._url(endpoint)
1909+
resp = self.session.post(url, data=payload_str.encode("utf-8"),
1910+
headers={"Content-Type": "application/json"},
1911+
timeout=face_timeout)
1912+
status = resp.status_code
1913+
data = resp.content
1914+
data_len = len(data) if data else 0
1915+
ct = resp.headers.get("Content-Type", "")
1916+
_log(f" → {label}: status={status} size={data_len}B ct={ct[:60]}")
1917+
if status == 200 and data and data_len > 500:
1918+
img = _extract_jpeg(data)
1919+
if img and len(img) > 1024:
1920+
_log(f" → JPEG encontrado! {len(img)//1024}KB")
1921+
return img
1922+
# Se não tem JPEG, mostrar amostra dos dados para debug
1923+
if data_len > 100:
1924+
sample = data[:80]
1925+
_log(f" → Sem JPEG. Primeiros bytes: {sample[:40]}")
1926+
return None
1927+
except Exception as e:
1928+
_log(f" → {label}: erro={e}")
1929+
return None
1930+
19041931
for fdid, fd_type in fd_configs:
1905-
# 3a: Busca com filtro faceCustomCondition (terminais mais novos)
1906-
for search_payload in [
1932+
# 3a: FDSearch multipart com filtro byEmployeeNo
1933+
img = _try_multipart_search(
1934+
"/ISAPI/Intelligent/FDLib/FDSearch",
19071935
json.dumps({
19081936
"faceLibType": fd_type, "FDID": fdid,
19091937
"searchResultPosition": 0, "maxResults": 1,
@@ -1912,92 +1940,85 @@ def _safe_get(endpoint):
19121940
"EmployeeNoList": [{"employeeNo": employee_no}]
19131941
}
19141942
}),
1915-
# 3b: Busca com FPID direto (terminais mais antigos)
1943+
f"3a byEmployeeNo ({fd_type})")
1944+
if img:
1945+
return img
1946+
1947+
# 3b: FDSearch multipart com FPID direto
1948+
img = _try_multipart_search(
1949+
"/ISAPI/Intelligent/FDLib/FDSearch",
19161950
json.dumps({
19171951
"faceLibType": fd_type, "FDID": fdid, "FPID": employee_no,
19181952
"searchResultPosition": 0, "maxResults": 1
19191953
}),
1920-
]:
1921-
resp = _safe_post("/ISAPI/Intelligent/FDLib/FDSearch", search_payload)
1922-
status = resp.get("status_code", 0)
1923-
data = resp.get("data", b"")
1924-
data_len = len(data) if isinstance(data, bytes) else 0
1925-
if status == 200 and data_len > 500:
1926-
_log(f" → Multipart data: {data_len} bytes")
1927-
img = _extract_jpeg(data)
1928-
if img and len(img) > 1024:
1929-
_log(f" → JPEG encontrado! {len(img)//1024}KB")
1930-
return img
1931-
_log(f" → Sem JPEG no multipart (data tem {data_len}B)")
1932-
elif status == 400:
1933-
continue # tipo errado, tentar próximo
1934-
elif status != 200:
1935-
_log(f" → status={status}")
1954+
f"3b FPID direto ({fd_type})")
1955+
if img:
1956+
return img
19361957

1937-
# 3c: Busca pela página que contém o registro (fallback amplo)
1958+
# 3c: FDSearch multipart SEM faceLibType (alguns terminais ignoram)
1959+
img = _try_multipart_search(
1960+
"/ISAPI/Intelligent/FDLib/FDSearch",
1961+
json.dumps({
1962+
"FDID": fdid,
1963+
"searchResultPosition": 0, "maxResults": 1,
1964+
"faceCustomCondition": {
1965+
"searchType": "byEmployeeNo",
1966+
"EmployeeNoList": [{"employeeNo": employee_no}]
1967+
}
1968+
}),
1969+
f"3c sem faceLibType")
1970+
if img:
1971+
return img
1972+
1973+
# 3d: Busca pela página completa (extrai todos JPEGs)
19381974
if found_match:
19391975
page_start = max(0, pos - 30)
1940-
payload = json.dumps({
1941-
"faceLibType": fd_type, "FDID": fdid,
1942-
"searchResultPosition": page_start, "maxResults": 30
1943-
})
1944-
resp = _safe_post("/ISAPI/Intelligent/FDLib/FDSearch", payload)
1945-
if resp.get("ok") and resp.get("data"):
1946-
data = resp["data"]
1947-
if len(data) > 2000:
1948-
_log(f" → Página multipart: {len(data)} bytes")
1949-
idx = 0
1950-
jpeg_list = []
1951-
while True:
1952-
start = data.find(b'\xff\xd8\xff', idx)
1953-
if start < 0:
1954-
break
1955-
end = data.find(b'\xff\xd9', start + 3)
1956-
if end < 0:
1957-
break
1958-
jpeg_list.append(data[start:end + 2])
1959-
idx = end + 2
1960-
if jpeg_list:
1961-
_log(f" → {len(jpeg_list)} JPEG(s) na página")
1962-
for jpg in jpeg_list:
1963-
if len(jpg) > 1024:
1964-
_log(f" → Retornando JPEG {len(jpg)//1024}KB")
1965-
return jpg
1966-
1967-
# === FASE 4: FaceDataRecord + endpoints diretos GET ===
1968-
_log("Tentativa 4: FaceDataRecord + GET endpoints diretos")
1969-
for fdid, fd_type in fd_configs:
1970-
# 4a: FaceDataRecord (alguns terminais suportam GET por FPID)
1971-
fdr_payload = json.dumps({
1972-
"faceLibType": fd_type, "FDID": fdid, "FPID": employee_no
1973-
})
1974-
resp = _safe_post("/ISAPI/Intelligent/FDLib/FaceDataRecord?format=json", fdr_payload)
1975-
if resp.get("ok") and resp.get("data"):
1976-
img = _extract_jpeg(resp["data"])
1977-
if img and len(img) > 100:
1978-
_log(f" → JPEG via FaceDataRecord! {len(img)//1024}KB")
1976+
img = _try_multipart_search(
1977+
"/ISAPI/Intelligent/FDLib/FDSearch",
1978+
json.dumps({
1979+
"faceLibType": fd_type, "FDID": fdid,
1980+
"searchResultPosition": page_start, "maxResults": 30
1981+
}),
1982+
f"3d página {page_start}-{page_start+30}")
1983+
if img:
19791984
return img
19801985

1981-
# 4b: Endpoints diretos GET
1982-
for ep in [
1983-
f"/ISAPI/Intelligent/FDLib/{fdid}/picture/{employee_no}?faceLibType={fd_type}",
1984-
f"/ISAPI/Intelligent/FDLib/{fdid}/picture/{employee_no}",
1985-
f"/ISAPI/AccessControl/UserInfo/UserPic/{employee_no}",
1986-
f"/ISAPI/Intelligent/FDLib/FDDownload?format=json&FDID={fdid}&FPID={employee_no}",
1987-
f"/ISAPI/AccessControl/CaptureFaceData",
1988-
]:
1989-
resp = _safe_get(ep)
1990-
status = resp.get("status_code", 0)
1991-
if resp.get("ok") and resp.get("data"):
1992-
img = _extract_jpeg(resp["data"])
1993-
if img and len(img) > 100:
1994-
_log(f" → JPEG via GET! {len(img)//1024}KB")
1995-
return img
1996-
if status != 404:
1997-
_log(f" → {ep.split('/')[-1][:40]}: status={status}")
1986+
# === FASE 4: Endpoints alternativos ===
1987+
_log("Tentativa 4: Endpoints alternativos")
1988+
for fdid, fd_type in fd_configs:
1989+
endpoints = [
1990+
("POST", f"/ISAPI/Intelligent/FDLib/FaceDataRecord?format=json",
1991+
json.dumps({"faceLibType": fd_type, "FDID": fdid, "FPID": employee_no})),
1992+
("GET", f"/ISAPI/Intelligent/FDLib/{fdid}/picture/{employee_no}?faceLibType={fd_type}", None),
1993+
("GET", f"/ISAPI/Intelligent/FDLib/{fdid}/picture/{employee_no}", None),
1994+
("GET", f"/ISAPI/AccessControl/UserInfo/UserPic/{employee_no}", None),
1995+
("GET", f"/ISAPI/Intelligent/FDLib/FDDownload?format=json&FDID={fdid}&FPID={employee_no}", None),
1996+
]
1997+
for method, ep, payload in endpoints:
1998+
try:
1999+
url = self._url(ep)
2000+
if method == "POST" and payload:
2001+
r = self.session.post(url, data=payload,
2002+
headers={"Content-Type": "application/json"},
2003+
timeout=face_timeout)
2004+
else:
2005+
r = self.session.get(url, timeout=face_timeout)
2006+
status = r.status_code
2007+
data_len = len(r.content) if r.content else 0
2008+
ep_short = ep.split("?")[0].split("/")[-1]
2009+
if status == 200 and r.content:
2010+
img = _extract_jpeg(r.content)
2011+
if img and len(img) > 100:
2012+
_log(f" → JPEG via {method} {ep_short}! {len(img)//1024}KB")
2013+
return img
2014+
if status not in (404, 403):
2015+
_log(f" → {method} {ep_short}: status={status} size={data_len}B")
2016+
except Exception as e:
2017+
_log(f" → {method} {ep.split('/')[-1][:30]}: erro={e}")
19982018

1999-
_log("Foto não acessível via ISAPI — este terminal não expõe fotos por HTTP. "
2000-
"A foto está no armazenamento interno (LOCALS/) mas não é baixável remotamente.")
2019+
_log("Todas as tentativas falharam. Este terminal (firmware) não suporta "
2020+
"download remoto de fotos faciais via ISAPI. A foto existe no armazenamento "
2021+
"interno (LOCALS/) mas isSupportFDSearchDataPackage=false.")
20012022
return None
20022023

20032024
def delete_face(self, employee_no):

file_version_info.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# UTF-8
22
VSVersionInfo(
33
ffi=FixedFileInfo(
4-
filevers=(4, 1, 7, 0),
5-
prodvers=(4, 1, 7, 0),
4+
filevers=(4, 1, 8, 0),
5+
prodvers=(4, 1, 8, 0),
66
mask=0x3f,
77
flags=0x0,
88
OS=0x40004,
@@ -18,12 +18,12 @@ VSVersionInfo(
1818
[
1919
StringStruct(u'CompanyName', u'Protector Sistemas'),
2020
StringStruct(u'FileDescription', u'Protector ISAPI Manager'),
21-
StringStruct(u'FileVersion', u'4.1.7'),
21+
StringStruct(u'FileVersion', u'4.1.8'),
2222
StringStruct(u'InternalName', u'Protector_ISAPI_Manager'),
2323
StringStruct(u'LegalCopyright', u'© 2026 Protector Sistemas'),
2424
StringStruct(u'OriginalFilename', u'Protector_ISAPI_Manager.exe'),
2525
StringStruct(u'ProductName', u'Protector ISAPI Manager'),
26-
StringStruct(u'ProductVersion', u'4.1.7'),
26+
StringStruct(u'ProductVersion', u'4.1.8'),
2727
]
2828
)
2929
]

installer.iss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
; ============================================================================
1515

1616
#define MyAppName "Protector ISAPI Manager"
17-
#define MyAppVersion "4.1.7"
17+
#define MyAppVersion "4.1.8"
1818
#define MyAppPublisher "Protector Sistemas"
1919
#define MyAppURL "https://github.com/ProtectorAnalytics/protector-isapi-manager"
2020
#define MyAppExeName "Protector_ISAPI_Manager.exe"

version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# MAJOR = mudança que quebra compatibilidade
1111
# MINOR = funcionalidades novas (retrocompatíveis)
1212
# PATCH = correções de bugs
13-
VERSION = "4.1.7"
13+
VERSION = "4.1.8"
1414

1515
# Status: "stable", "beta", "dev", "rc1", "rc2"...
1616
VERSION_STATUS = "stable"

0 commit comments

Comments
 (0)