Compare commits

...

394 Commits

Author SHA1 Message Date
RyuJongJin 1b3160939c 버전 2026.10.20_001
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 23:22:46 +09:00
RyuJongJin 5366bb3a14 원격 시험 흔들림: "주소에 비밀번호 없음" 이 무작위 표에 걸리던 것 · FTP = rsync 비교 실패 때 자세히 (관리자 지시 10/20, 28 이 찾음)
- 원인 (28 의 hwp2 전체 시험 원문): 실패한 것은 "FTP 맞추기 결과 = 실제 rsync 결과" 가 아니라 다음 시험 "FTP 동영상 … 다리로 재생".
  시험 비밀번호가 두 글자 "pw" 인데 다리 주소의 무작위 표 (…-4pwvUR-…) 에 우연히 들어가 isNot(contains('pw')) 가 실패
  (대략 1% 안팎). 비밀번호가 샌 것이 아님 - 앱은 고치지 않음
- ftp · sftp_stream · smb 시험: 길고 드문 비밀번호로 보고, 주소의 사용자 정보 (userinfo) 가 비었는지도 따로
- ftp_test "FTP 맞추기 결과 = 실제 rsync 결과": 실패하면 옵션 · 걸린 시간 · 포트 · 예외 · 다른 점과 보내는 쪽 · 맞추기 전 앱 쪽 ·
  rsync 쪽 · 앱 쪽 목록 (크기 · 바뀐 시각 ms) 을 한 번에. 맞추기가 예외를 던져도 어느 옵션인지 남김
- 확인: 혼자 12번 · 전체 단위 시험 (1143 통과) · 원격 5개 파일 -j 8 로 4번 모두 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 23:21:07 +09:00
RyuJongJin ab4c82d0b0 WIP 판 번호 검사 (관리자 지시 10/20, 15 제안): check_apk - versionCode = 판 이름 날짜 (261018) × 1000 + 순번, 시험판은 순번 >= 900 (987 을 --build-number 261018987 로 빌드해 522036987 이 통과하던 것) · check_windows_build - 시험판 번호의 날짜는 판 이름 날짜 (빌드한 날 아님, d3 판단: APK 와 같은 번호 · compareVersions 가 + 뒤 번호 전체를 견줘 261020985 가 261018987 보다 새것으로 보임) · 확인: 987 통과, 985 · 988 Windows (261020xxx) 실패, 정식 판 001 -Release 통과 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 22:48:48 +09:00
RyuJongJin 3d3ab5666f 통합 회귀 고침: 시험이 옛 글 · 정해진 시간 · 빗나간 누르기로 실패하던 것과 남긴 임시 파일 (관리자 지시 10/20)
- rsync_page: rsync_narrow (dfed3fa) 부터 옵션 이름은 줄바꿈되지 않게 묶여 그려져
  '원본 파일 지우기 (--remove-source-files)' 를 글 그대로 찾지 못함 → keepOptionsTogether 로 화면 글 그대로 찾기
- windows_b6_input (82 끌어 놓기 → [되돌리기]): 스낵바가 아래에서 올라오는 동안 (글은 이미 있음) 눌러 빗나감
  (hit test 에 앱 위젯이 하나도 없음) → 되돌리기가 시작되지 않아 이동 확인 창이 안 나옴.
  누르기 전에 정말 눌리는 자리에 올 때까지 기다림 (tapReady) - 실패하면 캡처 (JJ_SHOT_DIR) · 창 · 단추 · 글 목록.
  측정: [되돌리기] 는 보인 뒤 1 ~ 97ms 지나야 눌림. 985 (f947935) 의 같은 시험도 3번 중 2번 똑같이 실패 → rsync_narrow 이전부터,
  앱 쪽 바뀜 없음 (스낵바 · 되돌리기 코드는 985 와 같음). 사용자는 보이는 단추를 누르므로 앱은 고치지 않음
- windows_xplore_shot: 목록을 정해진 1.2초만 기다려 en 412 가 한 번 실패 → 'Show S01' 줄이 올 때까지 (최대 약 10초)
- video_adjust_e2e · windows_xplore_shot: 앱 임시 폴더 jj_mkvmaker 를 시험 폴더로 (appTempInto)
- shell: 끝까지 돌면 단계 기록 jj_shell_steps.txt 를 지움 (앱이 도중에 꺼졌을 때만 남음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 22:08:44 +09:00
RyuJongJin 44c69af83e 통합 시험 종료 코드: 다 통과했는데 0 이 아니던 것 (ai_e2e · browser_keep · dns_block_real) 고침 (관리자 지시 10/20)
- ai_e2e: 앱 임시 폴더 (jj_mkvmaker) 가 실행 폴더에 남아 exit 2 → 새 도우미 appTempInto 로 시험 폴더 아래에 두고
  시험 끝에 기준을 되돌린 뒤 함께 지움 (ai_cancel · ai_long 도 같게). 사용자 %TEMP%\jj_mkvmaker 는 건드리지 않음
- dns_block_real (· cef_dns_real · tor_dns_real): Edge (WebView2) 사용자 데이터 폴더는 heldByApp (jj_held_)
- tool/test_temp_check.ps1: 실행 폴더 지우기를 Edge 가 놓을 때까지 2초 간격 최대 30번 다시 해 봄,
  rd 오류는 cmd 안에서 버림 (PowerShell 의 2>$null 이 ErrorActionPreference Stop 에서 오류가 되어 exit 1 이었음).
  지우는 것은 그 실행 폴더 안뿐
- 확인: 다섯 파일 모두 -d windows · test_temp_check 로 exit 0, 남은 것 0, 실행 폴더 지워짐. analyze 문제 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:46:21 +09:00
RyuJongJin 9e40b485a9 rc 회귀 고침: 네트워크 공유 (UNC) 목록 · 기록 파일은 \\ 그대로 · 받는 쪽 UNC 도 \\ · 막히면 모니터링 [▶ 실행] 도 끔 · 표준 입력 (-) 문구 (검토 대기)
관리자 지시 (10/20, 28 이 f43f067 에서 확인 - 지운 것은 없음):
1. 회귀: ed459a0 이 목록 · 기록 · 배치 파일의 \\localhost\C$\… 를 //localhost/C$/… 로 바꿔 넘겨 rsync 가 "failed to open exclude
   file" (exit 11) 로 멈췄다 (985 는 \\ 그대로라 됨). 15 가 앱이 실제로 띄우는 방식 (응답 파일 · MSYS=noglob, rsync 3.5.1) 으로 자리마다
   확인 - 28 의 측정과 같음: 원본 = // 만 됨 (\\ 는 exit 23), 받는 쪽 = \\ 만 됨 (// 는 change_dir 실패 exit 3), 목록 · 기록 파일 =
   \\ 만 됨 (// 는 exit 11 · 기록이 안 생김).
   - rsyncFileOptionArgs: UNC 는 \\ 로 (//서버 로 적은 것도 \\ 로 바꿈), 드라이브는 /cygdrive/… 그대로.
   - 받는 쪽: 새 rsyncDestPath - UNC 면 \\서버\… (rsyncArgs 의 대상 · 실시간 동기화 rsyncArgsFor 의 받는 쪽). 원본은 toCygwinPath (//) 그대로.
     ed459a0 전부터 Windows 네트워크 공유가 받는 쪽인 rsync 는 실패했다 (관리자 판단: 데이터 문제는 아니지만 사용자가 겪는 실패라 rc 에).
2. 모니터링 복사 · rsync 카드: 옵션이 막혀 빨간 안내가 보이면 [▶ 실행] 도 끈다 (확인 창과 같게 - 누르면 실패로 끝났다).
3. --files-from=- · --exclude-from=- (표준 입력) 은 따로 된 문구 "표준 입력 (-) 은 쓸 수 없습니다" · 끝 문구 "목록 파일의 전체 경로를
   적어 주세요" (예전에는 "적어 주세요: - - C:\ …" 로 하이픈이 겹침).
시험: rsync_msys_args_test (실제 rsync - 관리 공유 \\localhost\C$ 로 원본 · 받는 쪽 UNC 와 목록 파일 \\ · // 를 복사 작업으로, 받는 쪽 UNC
실시간 동기화 쌍 · 바꿔 넘기기 단위 · 표준 입력 문구) · monitor_test (막히면 [실행] 꺼짐 · 고치면 켜짐).
전체 시험 1142 통과 · 1 실패 (explorer_test "여러 개 표시 → 삭제 · 새 폴더 · 이름 변경" - 이번 고침과 상관없음, 혼자 두 번 통과 ·
ed459a0 의 전체 시험에서도 통과 - 부하 흔들림) · analyze 문제 없음 · l10n 빠진 번역 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:29:47 +09:00
RyuJongJin f43f067d4b 시험 흔들림: remote_idle_test - "조각이 계속 오면 실패하지 않음" 은 0.1초 간격 · 3초 제한, SFTP 의 준비 (연결 · 목록 · 열기) 는 10초 (검토 대기)
0b 지적 (10/20): next10 전체 시험 (10분 넘음) 의 부하에서 0.4초 간격 대 1초 제한이 한 번 넘어 "서버가 답하지 않습니다 (1초)" 로 실패 (혼자서는 통과) - 여유를 30배로. 15 검증 (wt_verify_d3): 시험 다섯 파일을 함께 돌릴 때 SFTP 멈춤 시험의 준비 단계 (아직 멈추지 않은 list) 가 paramiko 의 답이 늦어 1초에 걸림 - 준비는 10초로 두고 멈추게 하기 바로 전에 1초로. 끊기는 쪽 확인은 그대로 1초. 코드 변경 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 18:31:22 +09:00
RyuJongJin ed459a0229 rc: 목록 · 기록 파일의 C:/ · C:\ · UNC 를 rsync 가 여는 꼴로 넘김 · Windows 는 드라이브 경로만 · 막힐 때는 이유 한 줄만 · ---delete 문구 (검토 대기)
관리자 지시 (10/20, 28 이 985 에서 확인):
1. --exclude-from · --include-from · --files-from · --filter / -f 의 ". 파일" · merge · --log-file · --write-batch · --read-batch ·
   --only-write-batch 의 값이 C:/… 이면 앱은 통과시키는데 rsync (MSYS2) 가 "failed to open exclude file" (exit 11) 로 실패했다
   (지운 것은 없음). 관리자 판단: 막지 말고 바르게 넘긴다 - rsyncFileOptionArgs 가 그 값의 C:\ · C:/ · UNC 를 toCygwinPath 로
   (rsyncArgs · 실시간 동기화 rsyncArgsFor). 패턴 · ": 이름" · 받는 쪽 기준 옵션 (--backup-dir 등) · -e 의 값은 그대로.
2. Windows 는 드라이브 (C:\ · C:/) · UNC · /cygdrive/ 만 전체 경로 - 그 밖의 / 는 MSYS 가 자기 설치 폴더 기준으로 읽어 막는다.
   끝 문구: Windows "C:\ 처럼 드라이브로 시작하는 전체 경로로", Android "/ 로 시작하는 전체 경로로".
3. [실행] 이 막힐 때 (알아볼 수 없음 · 상대 경로 등) 는 "미리 볼 수 없습니다" 줄과 [알고 실행합니다] 체크를 숨기고 막히는
   이유 한 줄만 (Rsync 화면 확인 창 · 지우기 포함 실행 확인 창).
4. "원본 파일 지우기 (--remove-source-files)" 가 412 ko · ja 에서 옵션 중간에 갈리던 것: rc 에 든 dfed3fa (keepOptionsTogether)
   로 이미 고쳐짐 - 캡처 shots_rsync_narrow (ko · ja · en 412) 로 확인.
5. "---delete" 의 끝 문구는 "옵션은 - 나 -- 로 시작합니다".
시험: rsync_msys_args_test (실제 rsync - C:/ · C:\ · 다음 낱말 · merge · -f. 로 적은 목록의 keep.txt 를 지킴, --log-file=C:/… 에
기록이 생김 · 바꿔 넘기기 단위 · Windows 의 / 막기 · 끝 문구) · dismiss_test (막힐 때 미리 볼 수 없음 · 체크 숨김) ·
option_paste_test (---delete 문구). 전체 시험 1140개 통과 (17 건너뜀) · analyze 문제 없음 · l10n 빠진 번역 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 18:24:35 +09:00
RyuJongJin 86b8ddd603 rc_next: next9 f947935 (985) + rsync_narrow dfed3fa (좁은 Rsync 화면 · 옵션 이름 줄바꿈)
다음 정식 판 후보 (관리자 지시 10/20). 겹친 곳: copy_sync_settings.dart 의 sync_preview_view import (optionProblemText + keepOptionsTogether 둘 다).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 17:42:20 +09:00
RyuJongJin dfed3fa6bb 좁은 Rsync 화면: [오른쪽] 탭 · 넓게 바꾼 두 창도 그 창의 지금 폴더가 보임 · 옵션 이름이 든 제목 · 체크가 "(--" 에서 갈리지 않음 (검토 대기)
관리자 지시 (10/20, 28 이 983 에서 찾음):
1. 412 Rsync 화면의 [오른쪽] 탭: 탭 이름은 지금 폴더인데 목록은 저장 장치 맨 위에서 시작했다 (넓게 바꿔도 그대로).
   원인: 켤 때 _goTo 가 두 창 모두 지금 폴더까지 펼치고 스크롤하는데, 좁은 화면은 왼쪽 창만 그려 오른쪽 창의 스크롤은 아직
   화면에 붙지 않아 건너뛰었다. 고침: 창의 목록이 화면에 새로 붙을 때마다 (탭을 처음 볼 때 · 폭이 바뀌어 두 창이 될 때) 그 창의
   지금 폴더를 보이게 (_revealNewViews · _revealNow). 폭이 바뀌면 LayoutBuilder 만 다시 불리므로 그 안에서 부른다.
2. en 412 확인 창의 체크 제목 "Delete source files (--remove-source-files)" 가 "(--" 에서 갈렸다. keepOptionsTogether: 글 안의 옵션
   덩어리만 (괄호째 · 따로 쓴 --이름 · -u · /PURGE) unbreakableOption 으로 묶고 나머지는 그대로 줄바꿈. 옵션 이름이 든 다른 제목 ·
   체크 · 설명에도: 받는 쪽에서 n개가 지워집니다 (--delete …) · 거른 것 n개도 지워집니다 (--delete-excluded) 와 그 설명 ·
   ⇄ 의 (-u) · 받는 쪽이 더 새것이면 건너뛰기 (-u · /XO) · 원본에 없는 것을 대상에서 지우기 (--delete · /PURGE).
시험: explorer_parallel_test (28 장면: 맨 위에 폴더 80개 · 지금 폴더는 깊은 곳 - 왼쪽 · [오른쪽] 탭 · 넓게 · 다시 좁게 모두 지금 폴더가
보임, 고치기 전에는 [오른쪽] 탭에서 실패) · sync_preview_view_test (keepOptionsTogether). 이 글을 찾는 시험 5개는 같은 도우미를 거친
글로. 캡처 windows_rsync_narrow_shot_test ([오른쪽] 탭 · 확인 창, ko · en · ja × 412 · 960, 넘침 0) → release\test\shots_rsync_narrow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 17:39:07 +09:00
RyuJongJin f947935c19 next9: + msys_args 끝 (634fcbc · 849c830: 목록 · 기록 · 배치 파일의 상대 경로 막기) - 시험판 985
합침 (d3): 상대 경로 막기를 optionProblems 의 새 종류 relativePath 로 (안내 끝 문구 "드라이브 (C:) 나 / 로 시작하는 경로로", 내 판단).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 17:14:23 +09:00
RyuJongJin 849c83031d 기록 · 배치 파일 (--log-file · --write-batch · --read-batch · --only-write-batch) 의 상대 경로도 막음 (검토 대기)
관리자 지시 (10/20, 28 확인): 상대 경로면 응답 파일 임시 폴더에 생겼다가 함께 지워져 사용자가 기록을 찾지 못함 - "기록 · 배치 파일은 전체 경로로 적어 주세요". --backup-dir · --partial-dir · --temp-dir · --compare-dest 류는 받는 쪽 기준이라 그대로 (시험에 둠). 번역 en · ja · zh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 17:10:55 +09:00
RyuJongJin 634fcbc920 목록 파일 (--exclude-from · --include-from · --files-from · ". 파일" · merge) 의 상대 경로는 막음 - "목록 파일은 전체 경로로 적어 주세요" (검토 대기)
관리자 판단 (10/20, 7160577 에 이어): rsync 는 자기 작업 폴더 (Windows 는 응답 파일 폴더) 기준으로, 미리 보기는 앱의 작업 폴더
기준으로 상대 경로를 읽어 서로 다른 목록 파일을 볼 수 있다. unreadableOptions 가 막는다 (옵션 칸 · 확인 창 · 모니터링 빨갛게, 실행 ·
실시간 동기화 막음).
- 대상: --exclude-from · --include-from · --files-from (= 와 다음 낱말), --filter / -f (붙여 · 다음 낱말 · 짧은 묶음 -avf) 의
  ". 파일" · "merge[,고치개] 파일". 전체 경로 = C:\ · C:/ · \서버 · / (/cygdrive/… · Android).
- 빼는 것: ": 이름" · dir-merge · -F (폴더마다 읽는 규칙 파일 이름 - 원래 상대), --exclude 의 값 (패턴).
시험: rsync_msys_args_test (막는 것 10 · 막지 않는 것 11). dismiss_test · live_sync_scene_test 의 merge 규칙을 전체 경로로.
거르기 · 맞추기 · 실시간 · 복사 시험 25개 파일 382개 통과.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 17:03:29 +09:00
RyuJongJin bd0801ce66 next9: + msys_args (7160577: Windows rsync 인수를 응답 파일로 · MSYS=noglob - ' 가 인수를 합쳐 거르기가 사라지던 것) - 시험판 985
합침 (d3): unreadableOptions 는 optionProblems 를 거친다 (b1a43c3 의 종류별 안내). 15 의 windows 매개변수 · 큰따옴표 막기를
optionProblems 로 옮기고, 안내는 새 종류 doubleQuote "큰따옴표 (\") 를 빼 주세요" (내 판단).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 16:59:12 +09:00
RyuJongJin 716057734a P0 Windows rsync: 인수를 응답 파일 (@) 로 넘기고 MSYS=noglob - ' 짝짓기 · 글로브 펼치기로 거르기가 사라져 지킬 파일이 지워지던 것 (검토 대기)
관리자 지시 (10/20, 28 재현 - 데이터, 정식 판에도 있음): Windows 의 rsync (MSYS2 런타임, 3.5.1) 는 Windows 프로그램이 넘긴
명령 줄을 스스로 다시 읽는다. 앱의 인수는 맞는데:
- ' 를 따옴표로 짝지어 인수를 합침: -a --delete --exclude="it's.txt" --exclude="Bob's.txt" 가 한 인수가 되어 거르기가 사라지고
  it's.txt · Bob's.txt 가 지워짐 (미리 보기는 "남음"). ' 하나면 rsync 가 실패, ' 가 든 경로 (한쪽 · 양쪽) 는 합쳐져 실패.
- * ? [ ] { } 를 작업 폴더의 파일로 펼침: --exclude *.log 가 작업 폴더의 x.log y.log 로 바뀌어 거르기가 빗나감.
실험 (실제 rsync): MSYS=noglob 은 글로브만 막고 ' 짝짓기는 그대로 (CYGWIN=noglob 은 소용없음). Dart 는 빈칸 · 큰따옴표가 없는
인수를 따옴표로 감싸지 않고, 감쌀 때의 \" 를 MSYS 는 다르게 읽어 명령 줄로는 고칠 수 없음. 응답 파일에 인수마다 큰따옴표로 감싸
넣고 (안의 \ 는 그대로) "@args.txt" 로 넘기면 ' · * ? [ ] { } · 빈칸 · 한글 · \ · @ 가 모두 그대로 감. 응답 파일 경로에 빈칸 · ' 가
있으면 @ 가 먹히지 않아 그 폴더를 작업 폴더로 하고 상대 이름으로 넘김.
- lib/core/rsync_launch.dart: prepareRsyncLaunch (Windows 만 - Android 는 그대로) · 끝나면 지움. 기존 MSYS 값에 noglob 을 더함.
- 띄우는 곳 둘 다: TransferJob (복사 · Rsync 화면 · 모니터링 [실행] · 최종 정리 - robocopy 는 그대로) · LiveSync (실시간 동기화).
  미리 보기는 rsync 를 쓰지 않는다.
- 큰따옴표 (") 가 든 인수는 응답 파일로도 그대로 못 넘김 (Windows 파일 이름에는 없음): 옵션 칸 · 확인 창 · 모니터링에 빨갛게
  (unreadableOptions, Windows 만) · 띄울 때도 막고 이유를 보임.
- 알아 둘 것: rsync 의 작업 폴더가 응답 파일 폴더가 되어, 옵션의 상대 경로 (--exclude-from=목록.txt) 는 그 폴더 기준 (예전에도
  앱의 작업 폴더라 정해지지 않았음 - 절대 경로를 쓰는 것이 맞음).
시험 rsync_msys_args_test (실제 rsync): ' 두 개 · 하나의 --exclude, ' 가 든 원본 경로 · 원본 · 대상 경로 · 경로 + exclude, 따로 쓴 *
· ? · [ ] · { }, [ ] · { } · 빈칸 · 한글 경로, 실시간 동기화 쌍, 응답 파일 지우기, 도우미 · 큰따옴표 막기. 고치기 전에는 7개 실패.
거르기 · 맞추기 · 실시간 · 복사 시험 25개 파일 · rsync_page_test (Windows 통합) 통과.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 16:52:26 +09:00
RyuJongJin b1a43c36c5 WIP 옵션 칸 보충 (관리자 지시 10/20, 28 지적): --cc · --zc · --zl · --zt 등 값 받는 별칭 (오탐) · -–delete 처럼 하이픈과 대시가 섞인 것도 고침 · 안내 끝 문구를 문제 종류에 맞게 (따옴표 닫기 · - 로 시작 · 값 적기 · 곧은 글자로) · 옵션 낱말은 줄바꿈 없이 · 캡처 시험 windows_option_problem_shot_test (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 16:46:57 +09:00
RyuJongJin f45ad3b25f WIP 옵션 칸 틈 셋 (관리자 판단 10/20, 28 확인): 값 자리가 아닌데 - 로 시작하지 않는 낱말 ("경로로 읽힙니다") · 하이픈 셋 이상 · 끝에 값 없는 옵션 ("--exclude 의 값이 없습니다") 도 알아볼 수 없음으로 실행을 막음 - 실제 rsync 는 원본 경로로 먹어 --delete 가 빠지거나 아무것도 보내지 않음 (시험으로 확인) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 16:06:04 +09:00
RyuJongJin 55caef5c3f WIP 옵션 붙여 넣기 보충 (15 검토 F1~F5, 관리자 지시 10/20): 닫히지 않은 따옴표는 뒤 옵션을 삼키지 않고 "따옴표가 닫히지 않았습니다" 로 실행을 막음 (데이터 - 예전에는 뒤의 --exclude 가 사라져 지킬 파일이 지워짐, 곧은 따옴표도) · 대시 하나는 rsync 긴 옵션 이름일 때만 -- (–avz → -avz) · --filter · -f 값의 규칙 머리 대시를 - 로 · 값 자리 대시는 그대로 · “a b”.txt 처럼 닫는 따옴표 뒤에 글이 이어져도 닫음 · 시험 파일의 보이지 않는 글자를 \u 표기로 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:52:05 +09:00
RyuJongJin 0d86a9b163 next9: + gb_flaky (60b994a: settings_group_bar_test 흔들림, 시험만)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:42:19 +09:00
RyuJongJin 60b994a9e6 시험 흔들림: settings_group_bar_test (412 묶음 바로가기) - 화면이 멈출 때까지 기다리고 치운 뒤에도 남은 일을 끝냄 (검토 대기)
관리자 지시 (10/20): 67개 시험 파일을 한꺼번에 돌릴 때 한 번 실패 (혼자서 · 무거운 시험과 함께 · 67개 다시 663개 모두 재현되지 않음). 실패 줄에 Expected · Timeout 이 없어 값이 틀린 것이 아니라 시험 밖으로 넘어간 예외로 봄: 설정 화면의 칸들이 시작한 진짜 입출력 (레지스트리 조회 · 구성 요소 목록 HttpClient · 파일) 이 부하가 크면 한 번 그린 뒤 · 화면을 치운 뒤에야 끝남. 이제 재기 전과 치운 뒤에 화면이 멈출 때까지 (위젯 수가 세 번 같음) 진짜 시간을 흘리고, 그만큼 시험 시간을 2분으로. 코드 변경 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:39:20 +09:00
RyuJongJin 2bacd88925 WIP 시험 흔들림: 멈춘 마그넷 줄의 "n초째" 를 61~99초로 (전체 시험 부하에서 64초가 나와 실패) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:09:37 +09:00
RyuJongJin 484f11d9c2 next9: + remote_idle (7f2263b: 원격 서버가 답하지 않으면 정한 시간 뒤 실패 · File closed 가 이유를 덮던 것) - 시험판 983
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 14:57:25 +09:00
RyuJongJin d2dc267ae2 WIP 옵션 칸에 붙여 넣은 글을 곧은 글자로 쓴 것과 같게 읽음 (관리자 지시 10/20, 28 확인, 데이터): 굽은 따옴표는 낱말 앞 · = 뒤 · -f 뒤에서 따옴표로, 낱말 맨 앞 대시 (– — ‑ - 등) 는 - · -- 로, 소프트 하이픈 · 전각 빈칸 고침 - 예전에는 --exclude=‘*.log’ 가 거르지 못해 받는 쪽 keep.log 가 지워졌고 –delete 는 원본 경로로 넘어가 --delete 가 빠졌다 · 그래도 알아볼 수 없는 옵션이 남으면 확인 창 · 설정 칸 · 모니터링에 빨갛게, 실행 · 실시간 동기화는 막음 · 시험 28 의 13가지 (실제 rsync 결과 = 곧은 글자) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 14:57:25 +09:00
RyuJongJin 7f2263bbb9 원격 서버가 멈추면 정한 시간 (처음 60초) 뒤 실패 - FTP · SFTP · SMB · WebDAV 받기 · 올리기 · 명령 (검토 대기)
관리자 판단 (10/20): 서버가 중간에 멈추면 복사 · 동기화가 영원히 멈추던 것 (15 가 FTP 에서 찾음 - 명령의 답에만 시간 제한).
- lib/core/remote_idle.dart: idleRead (조각 사이) · idleWrite (보내는 쪽이 한 조각도 더 가져가지 않거나 다 보낸 뒤 답이 없음) ·
  idleCall (요청 하나). 넘으면 그 연결을 끊고 RemoteIdleTimeout "서버가 답하지 않습니다 (n초)".
- FTP: 자료 연결 받기 (await for) · 올리기 (addStream). SFTP: 파일 읽기 · 쓰기와 모든 명령 (목록 · 열기 · 닫기 · 지우기 등) -
  멈추면 그 연결을 닫고 다음에 다시 연다. SMB: 조각 읽기 · 쓰기 채널 호출. WebDAV: 받기 · 올리기와 모든 요청의 답 (머리 · 몸).
- 올리다 실패하면 서버의 받다 만 .jjpart 지우기도 정한 시간까지만 (못 지우면 다음 정리 71).
- 설정 "원격 서버 응답 기다리기" (환경 설정 > 파일 탐색기): 30 · 60 (처음) · 120 · 300초, 그 밖의 값은 60. 번역 en · ja · zh.
- 버그 수정 (멈추는 서버 시험으로 찾음): 원격에서 로컬로 받다 실패하면 임시 파일을 닫고 지운 뒤 finally 에서 한 번 더 닫다가
  난 "File closed" 가 진짜 이유를 덮어썼다 (vWrite) - 이제 원래 이유 그대로.
시험 remote_idle_test: 멈추는 시험 서버로 정한 시간 (시험은 1초) 뒤 실패 - FTP (Dart 로 만든 서버: 보내다 멈춤 · 받다 멈춤,
로컬에 받다 만 파일 없음), WebDAV (머리 없음 · 몸 멈춤 · 받기 멈춤 · .jjpart 지우기 부탁), SFTP (paramiko + 멈추는 중계: 받는 중 ·
열기 · 올리는 중), 도우미 · 설정. smb_test: 가짜 채널이 답하지 않음 (열린 것은 닫음).
캡처 windows_remote_idle_shot_test (ko · en · ja × 412 · 960, 넘침 0) → release\test\shots_remote_idle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 14:46:42 +09:00
RyuJongJin f5a01a1ecd next9: + filter_phsr 끝 (d7da812: P keep/*** 가 받는 쪽에만 있는 안쪽 폴더도 지킴) - 시험판 982
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 14:18:37 +09:00
RyuJongJin 0ef2c2c33b next9: + filter_phsr (c593440: H · S · P · R · ,s · ,r 을 rsync 와 같게 따름) - 시험판 982
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 14:16:35 +09:00
RyuJongJin d7da812da6 거르기: "이름/***" 을 경로로 맞춤 - "P keep/***" 가 받는 쪽에만 있는 안쪽 폴더까지 지킴 (검토 대기)
28 확인 (10/20, c593440 독립 확인 22개 중 1개 - 안전한 쪽으로 다름): -a --delete --filter="R keep/extra.txt" --filter="P keep/***",
받는 쪽에만 keep/extra.txt · keep/sub/deep.txt 가 있으면 실제 rsync 는 keep/extra.txt 만 지우는데, 미리 보기 · 실시간 동기화의
묻는 목록은 keep/sub/ 까지 지운다고 보였다 (맞춘 뒤에도 지울 것으로 남음).
원인: "이름/***" 의 끝을 떼고 나면 패턴에 / 가 없어 이름으로만 맞췄다 - keep/sub 는 이름이 sub 라 맞지 않음. 원래 패턴에 / 가
있으므로 rsync 처럼 경로 (끝부분) 로 맞춘다. 보내는 쪽에서는 거른 폴더 안으로 내려가지 않아 드러나지 않았다.
rsync_phsr_test 에 28 의 장면 (keep/k.txt · 받는 쪽 keep/extra.txt · keep/sub/deep.txt) 과 *** 장면 4개 - 고치기 전에는 32 · 33 번 실패.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 14:15:50 +09:00
RyuJongJin 57676371dc WIP 옵션 글의 줄바꿈 없는 빈칸 · 보이지 않는 글자 (관리자 지시 10/20): 옵션을 나눌 때 (splitOptions) 와 옵션 칸에 붙여 넣을 때 (OptionTextFormatter) 보통 빈칸으로 / 지움 - 묶은 글 · 다른 곳의 글을 붙여 넣어도 rsync 가 같은 인수로 읽음 · 소스의 보이지 않는 글자는 \u 표기로 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 14:05:26 +09:00
RyuJongJin c593440249 거르기: H · S · P · R 과 고치개 ,s · ,r 을 rsync 와 똑같이 따름 - 미리 보기 · 원격 맞추기 · 실시간 동기화 (검토 대기)
관리자 지시 (10/20, 데이터 쪽): 예전에는 이 규칙들을 "미리 볼 수 없음" 으로 두고 경고만 했다. 이제 rsync (man rsync FILTER RULES ·
exclude.c) 와 같게:
- 규칙마다 걸리는 쪽 (RuleSide): 그냥 + · - 는 양쪽, H (hide) = -,s · S (show) = +,s 는 보내는 쪽 (보낼 것 고르기) 에만,
  P (protect) = -,r · R (risk) = +,r 은 받는 쪽 (지울 것 고르기) 에만. ",sr" 은 양쪽. 긴 이름 (hide · show · protect · risk) 도.
- 쪽마다 그 쪽에 걸리는 규칙만 위에서부터 보고 처음 맞는 것이 이긴다 (RsyncFilter.excluded = 보내는 쪽, protects = 받는 쪽).
- --delete-excluded 면 쪽을 적지 않은 규칙은 보내는 쪽에만 (rsync 와 같음 - 예전 동작과도 같은 결과).
- 지울 때 (previewSync · _mirrorV · 실시간 동기화 diff/_gone) 는 "거른 것 · --delete-excluded 아님" 대신 protects 로 지킨다:
  보내는 쪽에서만 거른 것 (H · -,s) 은 보내지 않았으므로 --delete 면 지운다 (28 의 a.txt 와 같은 일을 이제 미리 보기가 보임).
- 그 밖의 고치개 (,! · ,p · ,x · ,e 등) · H/S/P/R 에 붙은 고치개 · merge · "!" · exclude-from 파일 안의 규칙 머리는 그대로 미리 볼 수 없음.
기준 시험 rsync_phsr_test: 규칙 장면 31개 (양쪽에 같은 이름이 많은 장면) 마다 실제 rsync 를 돌린 (-n 아님) 결과 트리와
① 미리 보기로 예측한 트리 ② 원격 맞추기 (WebDAV) ③ 실시간 동기화 로컬 쌍 (묻는 목록 = rsync 가 지운 것 · 맞춘 뒤 맞출 것 없음)
④ 실시간 동기화 WebDAV 쌍 - 모두 같음. protects 를 예전처럼 (쪽 무시) 바꾸면 25개 장면이 다르게 나오는 것도 확인 (시험이 잡음).
바꾼 시험: rsync_filter_test (이 규칙들은 미리 볼 수 있음 · 15 재현은 이제 미리 보기도 a.txt 지움), P0 시험 · 경고 창 시험 ·
P0 캡처는 아직 따르지 못하는 merge 규칙 (파일 안에 "H a.txt") 으로, P 는 이제 따르므로 "묻는 목록에 지킨 것 없음" 으로.
smb_test 의 28 장면 시험에 5분 (여러 시험을 함께 돌리면 기본 30초를 넘음).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 14:04:28 +09:00
RyuJongJin 0bd1466212 WIP 실시간 동기화 화면 (관리자 지시 10/20, 28 지적): 경고 글 안의 옵션 (--filter=H a.txt) 을 줄바꿈 없이 묶음 · 좁은 화면에서는 카드의 "rsync · 복사로 맞춤 · 지우기 포함" 칩을 경로 아래 한 줄로 (잘리던 것) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:50:49 +09:00
RyuJongJin b2c400a0fb WIP 시험: 켤 때 지우는 것은 앱이 업데이트 때 만든 이름만 - 다른 jj 이름 (앱 임시 폴더 · 도구 받기 · Chrome 엔진 · 비슷한 이름) 은 남김 (관리자 지시 10/20, 검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:35:16 +09:00
RyuJongJin f6de74010c next9: + lsync_p0 (aba095b: 미리 볼 수 없는 거르기 규칙의 지우기 포함 쌍은 저절로 지우지 않음) - 시험판 981
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:25:15 +09:00
RyuJongJin e228e84adb WIP 시험 · 앱이 %TEMP% 에 고정 이름을 남기지 않게 (관리자 지시 10/20): 시험의 앱 임시 폴더는 파일마다 jj_store_ (flutter_test_config) · 업데이트 시험은 시험 폴더 (GitHubUpdater.tempRoot, 기록은 스크립트 옆) · check.py 는 임시 폴더 · 앱이 켜질 때 지난 업데이트가 남긴 새 판 폴더 · .ps1 · .log 를 지움 (2분 지난 것) · 검사는 jj 로 시작하는 것 모두 실패 · 마그넷 멈춘 막대 "Stalled" 따로 열쇠 (28 지적) · explorer_test 82 부하 때 목록 기다리기 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:25:15 +09:00
RyuJongJin aba095b342 P0 실시간 동기화: 미리 볼 수 없는 거르기 규칙이 있는 지우기 쌍은 저절로 지우지 않고 보내기만 · 지우기는 확인 뒤 한 번 (검토 대기)
관리자 3a 지시 (10/20, 28 재현 - 데이터 버그 수정): 로컬 rsync 쌍 · 지우기 포함 · 옵션 --filter="-,s a.txt" (또는
"H a.txt" · -f"H a.txt") 에서 묻는 목록은 [extra.txt, old.log] 인데 [지우기] 를 누르면 a.txt 도 지워졌다 (실제 rsync 는
보내는 쪽만 빼기 · 숨기기를 받는 쪽에서 지울 것으로 봄). "P *.txt" 는 반대로 지킨 것을 끝없이 물었다.
- 지우기 포함 rsync 쌍의 옵션에 미리 볼 수 없는 규칙 (RsyncFilter.unsupported) 이 하나라도 있으면 자동 맞추기는 --delete
  계열 없이 (보내기만) 하고 묻지도 않는다 (목록이 실제와 달라서). 맞출 것 · 대상에만 있는 것도 지우기 없이 센다.
- 카드에 빨간 "이 옵션의 일부 거르기 규칙은 미리 볼 수 없어 자동으로 지우지 않습니다 (규칙)" · [지금 지우기 (확인)].
  그 창은 FilterLimitNote (빨간 "실제로 더 지워질 수 있습니다") 와 [알고 실행합니다] 체크가 있어야 [지우고 맞추기] -
  이번 한 번만 지우고 다음 자동 맞추기는 다시 보내기만.
- 묻는 창 (confirmLiveDeletes) 도 같은 창을 써서 규칙이 있으면 FilterLimitNote · 체크.
- P (지키기) 는 따로 따르지 않고 위와 같이 다룸 (경고 · 저절로 지우지 않음 · 끝없이 묻지 않음) - 지킨 것을 목록에서 빼려면
  P · R 과 다른 규칙의 순서 (받는 쪽 규칙) 를 맞춰야 해서 이번에는 하지 않음.
시험: live_sync_scene_test P0 - 세 가지 규칙 × 새 쌍 · 확인한 쌍을 실제 rsync 로 (먼저 실제 rsync 가 a.txt 를 지우는 것 확인
→ 자동 두 번은 아무것도 지우지 않음 · 묻지 않음 · − 줄 없음 → 지금 지우기 한 번 → 다음 자동은 다시 보내기만), P 규칙 세 번,
미리 볼 수 있는 규칙만이면 예전처럼, 창 (빨간 줄 · 체크 전 버튼 꺼짐 · [지우지 않기] 없음). LiveSync 시험 157개 통과.
캡처: windows_lsync_p0_shot_test (카드 · 지금 지우기 창, ko · en · ja × 412 · 960, 넘침 0) → release\test\shots_lsync_p0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:21:56 +09:00
RyuJongJin 2f4ad6c146 WIP 시험 임시 폴더 검사 보충: 실패는 시험 소스 이름의 폴더만 (앱 고정 이름 jj_mkvmaker · 파일은 참고로 보이고 함께 지움) · explorer_test 의 -u 찾기를 옵션 낱말로 (시험 폴더 위가 %TEMP% 라 vscode-stable-user-x64 에 걸림) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:57:50 +09:00
RyuJongJin f377a14184 next8: + tmptag (시험 임시 폴더 검사 표)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:39:47 +09:00
RyuJongJin 84863c1569 WIP 시험 임시 폴더 검사: 실행마다 %TEMP%\t_jjrun_<표> 로 TEMP · TMP 를 돌려 그 안만 셈 - 다른 세션 시험과 섞여 거짓 실패하던 것 · 앱 코드가 남긴 것도 잡음 · 도우미가 표 밖에 생기면 실패 · Edge 데이터 폴더 (앱이 끝날 때까지 잡힘) 는 jj_held_ 로 보이기만 하고 함께 지움 (관리자 지시 10/20, 검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:39:46 +09:00
RyuJongJin 6e646b60c1 next8: + a8bc585 (실시간 동기화: 옵션이 바뀌어 지울 것이 늘면 다시 묻기) - 시험판 980
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:30:18 +09:00
RyuJongJin a8bc585b1a 실시간 동기화: 확인한 쌍도 rsync 옵션이 바뀌어 지울 것이 늘면 다시 묻기 (검토 대기)
관리자 3a 판단 (10/20, 데이터 쪽이라 안전한 쪽으로): 로컬 · 원격 쌍 모두.
- 쌍에 확인할 때의 rsync 옵션을 기억 (LiveSyncPair.confirmedOptions, 설정 파일에 저장 - 틀린 값은 없는 것으로).
- 맞추기 전에 지금 옵션이 기억한 것과 다르면, 두 옵션으로 지울 것을 견주어 늘었으면 (--delete-excluded · --exclude ·
  --filter 를 바꿔 새로 지워질 것이 생김) 지우지 않고 맞춘 뒤 다시 묻는다 (42 와 같은 창, 주황 "옵션이 바뀌어 지울 것이
  늘었습니다" 줄). 늘지 않았으면 묻지 않고 지금 옵션을 기억.
- 이 값이 생기기 전에 확인한 쌍은 예전 동작 (--delete-excluded 를 따르지 않음) 과 견준다 - 업데이트 뒤 --delete-excluded 로
  새로 지워질 것이 있으면 묻고, 없으면 기억만 (처음 한 번 설정을 저장).
- 확인 창 높이를 화면 높이에 따라 (360 ~ 520) - 412 세로에서 거른 것 묶음이 잘려 보이던 것 (캡처로 찾음).
시험: live_sync_scene_test (로컬, 실제 rsync - 늘지 않는 바꿈 · --delete-excluded 더함 · 거르기 뺌 · 예전 쌍 · 설정 저장),
webdav_test (원격 쌍). LiveSync 를 쓰는 시험 14개 파일 148개 통과. 캡처 windows_live_excl_shot_test 를 다시 묻는 창으로
(ko · en · ja × 412 · 960, 넘침 0) → release\test\shots_live_excl.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:26:08 +09:00
RyuJongJin 2033c48e5e next8: + magnet_ui (197ba04: dn 이름 제목 · 멈춘 줄 막대) - 시험판 980
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:24:16 +09:00
RyuJongJin a19d68e167 next8: next7 + next6 (44a6c4e: 실시간 동기화 거르기 · --delete-excluded) - 시험판 980
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:24:14 +09:00
RyuJongJin 44a6c4e6b2 실시간 동기화 지울지 묻는 창: --delete-excluded 로 지워지는 거른 것은 따로 묶어 빨갛게 (검토 대기)
관리자 3a 조건 (10/20, 954703c 의 판단 ③ 을 받아들이며): 사용자가 알고 누르게.
- diff · _diffV · _gone 이 지울 것 가운데 --delete-excluded 때문에 지워지는 것 (거른 것 · 거른 것이 든 폴더) 을 따로 모음
  → LiveSync.toDeleteExcluded. 확인 창 (confirmLiveDeletes) 은 그냥 지울 것 다음에 "거른 것 n개도 지워집니다
  (--delete-excluded)" · 설명 (지키려면 옵션에서 빼기) · 그 목록을 빨갛게. 번역 en · ja · zh-Hans.
시험: live_sync_scene_test - 28 장면마다 따로 묶은 것 (없어야 할 때 없음 · 지울 목록 안 · *.bak / *.log 장면은 정확히),
창의 묶음 제목 · 순서. 캡처: windows_live_excl_shot_test (ko · en · ja × 412 · 960, 넘침 0) → release\test\shots_live_excl.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:10:30 +09:00
RyuJongJin 197ba047c4 마그넷 줄: 메타데이터 전에도 dn 이름 · 멈춘 줄은 막대가 움직이지 않고 주황 "멈춤" (검토 대기)
관리자 지시 (10/20, 28 이 next5 6b60f34 확인에서 찾음):
1. 마그넷에 이름 (dn=…) 이 있으면 메타데이터를 받기 전에도 줄 제목에 그 이름 (magnetName: 주소 글 풀기 · + 는 빈칸 ·
   줄바꿈 · 제어 문자는 빈칸, 없으면 예전처럼 주소). 메타데이터를 받으면 토렌트 이름으로 바뀌는 것은 그대로.
2. 멈춘 줄 (주황 "트래커 · 피어에 닿지 않습니다") 의 진행 막대: 움직이는 "준비 중…" 대신 멈춘 막대 · 주황 · "멈춤"
   (이미 있는 번역 "Stopped" · "停止" · "已停止"). 멈춤이 풀리면 원래대로.
시험: downloads_ui_test (magnetName · 줄 제목 · 멈춤 막대 value 0 · pumpAndSettle 이 끝남 = 움직이지 않음).
캡처: integration_test/windows_magnet_shot_test (ko · en · ja × 412 · 960, 넘침 0) → release\test\shots_magnet_ui.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:01:13 +09:00
RyuJongJin 954703c4f0 실시간 동기화 (rsync 쌍): 맞출 것 · 지울지 묻는 목록이 옵션의 거르기를 따름 · --delete-excluded 는 지우기 포함 쌍에서 따름 (검토 대기)
관리자 지시 (10/20) 2번 중 28 의 장면 (옵션 28개) 을 실시간 동기화로 돌려 찾음 (버그 수정):
- 로컬 쌍의 diff 는 거르기를 전혀 따르지 않아, rsync 가 보내지 않는 것을 "맞출 것" 으로 늘 세고, 받는 쪽의 거른 것
  (예: --exclude=*.log 의 keep.log) 까지 지울지 물었다 (rsync 는 지우지 않음).
- 원격 쌍 (_diffV) · 로컬 쌍 모두: 받는 쪽에만 있는 폴더에 거른 것이 들어 있으면 "− 폴더" 한 줄로 셌다 - 묻는 창에 폴더가
  통째로 지워지는 것처럼 보이고, 맞춘 뒤 (폴더는 남으므로) 맞출 것에 계속 남았다. 이제 rsync · _mirrorV 처럼 그 안의
  거르지 않은 것만 (_gone · _hasExcluded 를 둘이 같이 씀).
- 로컬 쌍의 rsync 인수가 --delete-excluded 를 늘 뺐다 (97 의 지우기 빼기에 걸림) - 묻는 창에는 지운다고 보이고 실제로는
  남았다. 내 판단: 쌍이 "지우기 포함" 일 때만 남김 (원격 쌍 · Rsync 화면과 같게, 지우기 전 확인 (42) 은 그대로). 지우기 없는 쌍은 그대로 뺌.
시험: live_sync_scene_test (로컬, 실제 rsync) · webdav_test (WebDAV 쌍) - 결과 트리 = 실제 rsync, 묻는 목록 = rsync 가 지우는 것,
맞춘 뒤 맞출 것 없음. 고치기 전 로컬 실패 · WebDAV 실패 (− old · − old2 · − onlydst) 확인. live_sync_args_test 에 --delete-excluded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:52:10 +09:00
RyuJongJin 8cce03d99c next7: next5 (6b60f34) + next6 (83e9fdd) 합침 - 시험판 979 2026-10-20 11:46:19 +09:00
RyuJongJin 6b60f348e2 WIP 마그넷: 인터넷 IPv6 가 없으면 (Tailscale ULA 뿐) aria2 의 IPv6 끔 - 시작되지 않던 것 (28 확인) · 정한 시간 (처음 60초) 동안 메타데이터 · 피어가 없으면 줄에 이유 후보 · [다시 시도] · 실제 마그넷 받기 통합 시험 (JJ_REAL_MAGNET=1) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:32:40 +09:00
RyuJongJin 10954a632b 시험: FTP · SMB 동영상도 받지 않고 재생 · 다음 화 · 그 자막 (검토 대기)
관리자 지시 (10/20) 2번 - 위키 974 목록 1 "받지 않고 재생" · "다음 화" 의 📱 ⏸ 가운데 기기 없이 할 수 있는 것:
SFTP · WebDAV 에만 있던 시험을 FTP (pyftpdlib 시험 서버) · SMB (가짜 채널) 에도. preparePlayback 이 같은 폴더의
동영상만 차례로 (1화 → 2화), 이 기기 안의 다리 (127.0.0.1) 주소에 비밀번호 없음, Range (건너뛰기) 206, 다음 화도 바로,
다음 화의 자막을 찾음. 둘 다 통과 - 코드 변경 없음.
SMB 시험은 위젯 시험 바인딩의 가짜 HttpClient 를 그 시험 동안만 끔 (다리에 실제로 묻기 위해).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:29:03 +09:00
RyuJongJin 39c6d9dce4 시험: WebDAV · SMB 맞추기도 실제 rsync 결과 트리와 같음 (28 의 장면 · 옵션 28개, 검토 대기)
관리자 지시 (10/20) 2번 - 위키 974 목록 8 "원격 동기화도 거르기를 따름" 의 📱 ⏸ 가운데 기기 없이 할 수 있는 것:
28 이 손으로 한 WebDAV 21 장면 비교를 시험으로 남기고 (시험 WebDAV 서버), SMB 는 가짜 채널로 같은 28개.
네 원격 (WebDAV · SFTP · FTP · SMB) 모두 결과 트리 (폴더 · 파일 · 글) 가 실제 rsync 와 같음 - 코드 변경 없음.
rsync_scenes 의 bundledRsync 를 sceneRsync 로 (live_sync.dart 의 같은 이름과 겹침).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:25:14 +09:00
RyuJongJin 83e9fdd676 시험: SFTP · FTP 맞추기 결과 트리 = 실제 rsync 결과 트리 (28 의 장면 · 옵션 28개, 검토 대기)
관리자 지시 (10/20): 28 이 WebDAV 로 비교한 장면 · 옵션 (rsync_filter_test 의 "28 의 장면" 27개 + 지우기 없는 거르기 1개) 을
SFTP (paramiko) · FTP (pyftpdlib) 시험 서버로. 같은 장면을 로컬에서 실제 rsync (앱에 든 rsync.exe) 로 맞춘 결과 트리와
원격 맞추기 (FileOps.mirror + 옵션의 지우기 · 거르기) 뒤 서버 폴더의 트리 (폴더 · 파일 · 글) 를 비교. 28개 모두 같음 - 코드 변경 없음.
거르기를 빼고 돌리면 다른 점 423줄로 실패하는 것도 확인 (시험이 실제로 잡는지).
장면 · 비교 도구는 test/support/rsync_scenes.dart 로 (다른 원격 시험도 쓰게).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:20:38 +09:00
RyuJongJin f13de6b1d8 WIP 177 보충 5 (28 확인): 짧은 옵션 묶음을 rsync (popt) 처럼 한 글자씩 풂 - -avf"- *.log" · -rf "- x" 의 f 규칙을 따르고, 패턴 안의 C · F 를 -C · -F 로 잘못 보지 않음 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:18:48 +09:00
RyuJongJin ca63bd1fb5 WIP 시험판 검사 도구: 순번 900 이상의 v 태그 (시험판 Pre-release) 는 정식 판으로 보지 않음 · 정식 판 (-Release) 인데 순번 900 이상이면 실패 (검토 대기)
관리자 지시 (10/19, 28 지적): 시험판에 태그 (예: v2026.10.18_977) 를 단 뒤 같은 APK 를 tool/check_apk.ps1 로 다시 검사하면
"시험판인데 정식 판 v2026.10.18_977 과 같음" 으로 거짓 실패했다 (그 태그를 정식 판으로 봄). 거꾸로 977 APK 에 -Release 를 주면 통과했다.
- check_apk.ps1: 정식 판 태그는 순번 900 미만만. -Release 인데 versionCode 의 순번이 900 이상이면 실패 ("시험판 번호입니다").
- check_windows_build.ps1: 원래 태그를 보지 않아 거짓 실패는 없었다. -Release 인데 순번이 900 이상이면 실패하도록 같은 규칙을 맞춤 (전에는 1000 이상만).

확인 (실제 파일):
- APK 977 (release\test\977): -Release 없이 통과, -Release 면 실패 / APK 001 (정식 판): -Release 통과, -Release 없이 실패 (정식 판 번호).
- Windows 977 (release\test\977\stage): -Release 없이 통과, -Release 면 실패 / Windows 001 (release\stage_v2026.10.18_001): -Release 통과, 없이 실패.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 23:17:25 +09:00
RyuJongJin 484991cdda WIP 시험: FTP (pyftpdlib) · SMB (가짜 채널) 맞추기도 거르기를 따르고 미리 보기 = 실제 (44407c3 보충) (검토 대기)
관리자 지시 (10/19): 원격 맞추기 (_mirrorV) 의 거르기를 WebDAV · SFTP 에 이어 FTP · SMB 로도 확인.
- ftp_test (새 1, pyftpdlib 평문 시험 서버): WebDAV 때와 같은 거르기 5가지 (--exclude 파일 · 폴더 / --delete-excluded / --include 와 --exclude 차례 /
  --filter "- …" "- /…" / 지우기 없음) 마다 미리 보기의 + · ~ 는 실제로 있고 − 는 실제로 없어지며, 실제로 지워진 것은 모두 미리 보기 지울 목록에.
  자세히: 거른 것은 보내지 않음 · 받는 쪽의 거른 것 지킴 · 거른 것이 든 폴더는 남기고 안의 것만 지움.
- smb_test (새 1, 가짜 SMB 채널): 같은 규칙 - 미리 보기 지울 목록 {old.txt, gone/g.txt} · 새로 {a.txt} 와 실제 결과가 같음.
코드는 바꾸지 않음 (시험만).

시험: ftp_test · smb_test 17 통과, 임시 폴더 흔적 0, analyze 문제 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 23:15:03 +09:00
RyuJongJin 44407c36de WIP 원격 맞추기 (WebDAV · SFTP · SMB · FTP) 도 rsync 옵션의 거르기를 미리 보기와 같게 따름 - M2 의 본 고침 (검토 대기)
관리자 지시 (10/19): 원격은 rsync 대신 앱이 맞추며 (file_ops._mirrorV · 실시간 동기화 원격 쌍) --exclude 등을 따르지 않아, 지키려고 뺀 것도 복사 · 지웠다
(22ae039 는 경고만). 바탕: remote_filter = next2 7cd8eb8 + next3 63ef362 (d3 의 RsyncFilter · F1~F4).
- FileOps.mirror / _mirrorV 에 filter (RsyncFilter): 거른 것은 보내지 않고 (거른 폴더 안으로 내려가지 않음), 받는 쪽의 거른 것은
  지키고 (--delete-excluded 면 지움), 받는 쪽에만 있는 폴더 안에 거른 것이 있으면 폴더는 남기고 거르지 않은 것만 지운다 (미리 보기 sync_preview 와 같은 규칙).
- 복사 · 이동 작업: rsync 를 고른 작업이 원격이라 앱이 맞출 때 그 옵션의 거르기를 넘긴다.
- 실시간 동기화: rsync 쌍이면 환경 설정 rsync 옵션의 거르기를 맞추기와 대기 목록 (diff · _diffV) 에 같이. _diffV 는 M1 규칙도 (지우기 없으면 같은 이름 폴더 줄은 기다림으로 세지 않음).
- 확인 창 (Rsync 화면 · 지우기 확인): 원격도 같은 거르기로 미리 보고, 미리 볼 수 없는 규칙 (unsupported) 의 빨간 경고 · [알고 실행] 체크 (63ef362) 를 원격에도.
  22ae039 의 주황 "원격은 거르기를 따르지 않음" 경고는 뺐다. 안내 글: "… 옵션 중 -u · --delete · --remove-source-files · 거르기 (--exclude · --include · --filter · --delete-excluded) 를 따름".

시험: webdav_test (새 1: 시험 WebDAV 서버에 거르기 5가지 - --exclude 파일 · 폴더 / --delete-excluded / --include 와 --exclude 차례 / --filter "- …" "- /…" / 지우기 없음 -
마다 미리 보기의 + · ~ 는 실제로 있고 − 는 실제로 없어지며, 실제로 지워진 것은 모두 미리 보기 지울 목록에. 한글 폴더 · 거른 것 지키기 · 거른 것이 든 폴더 자세히) ·
sftp_test (새 1: paramiko 서버, 보내지 않기 · 지키기 · 폴더 안만 지우기, 미리 보기 지울 목록 = {old.txt, gone/g.txt}) ·
sync_preview · sync_preview_view · explorer_webdav · rsync_filter · mirror_safety · sync_tools · live_sync · transfer 통과, 임시 폴더 흔적 0, analyze 문제 없음, l10n 빠짐 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:54:14 +09:00
RyuJongJin 65f5e49aad next4: next2 7cd8eb8 (M2 · 줄바꿈 되돌림 · 글 뷰어 이모지) 합침
# Conflicts:
#	assets/l10n/en.json
#	assets/l10n/ja.json
#	assets/l10n/zh-Hans.json
2026-10-19 22:35:14 +09:00
RyuJongJin 3704c7c2cc next4: next3 (177 보충 4) 합침 2026-10-19 22:34:21 +09:00
RyuJongJin 6d40eaf174 WIP 177 보충 4 (28 확인): 붙여 쓴 -f"+ *.txt" 도 규칙으로 읽음 (예전에는 말없이 버려 지울 목록이 적게 보였다) · --files-from 은 "미리 볼 수 없음" (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:34:20 +09:00
RyuJongJin 7cd8eb8b15 WIP 글 뷰어: 긴 줄을 재는 조각이 이모지 (두 단위 글자) 가운데서 끊겨 "not well-formed UTF-16" 이 나던 것 (검토 대기)
28 지적 (10/19, 4716537 의 회귀): 이모지가 든 긴 줄에서 cut 콜백의 재는 조각 끝 (from + size) 이 서로게이트 쌍 가운데 (0xD800 다음) 에 떨어지면
반쪽 글자로 TextSpan 을 만들어 painting 오류 "string is not well-formed UTF-16" 이 화면마다 30번 나고, 그 재기를 버리고 더 큰 조각을 다시 쟀다
(화면에 빠진 글은 없었음).
→ 조각 끝이 두 단위 글자의 뒷쪽 (0xDC00~0xDFFF) 이면 한 칸 앞에서 자른다 (viewerRows 뒤쪽과 같은 처리).
같이: 4716537 에서 l10n 검사에 걸린 폭 재기 글자 '가' 에 // l10n-skip (화면 글이 아님).

확인: integration_test/windows_textview_shot_test.dart 에 이모지 줄 ('abc😀' × 3000, 412 · 960) 과 "그리는 중 오류 없음" 을 더함 -
고치기 전 코드로는 Windows 에서 UTF-16 오류 10건으로 실패, 고친 뒤 8 통과 · 오류 0 (release\test\shots_textview_emoji).
xplore_tools_test (새 1: 이모지 긴 줄 · 조각이 반쪽 이모지로 끝나지 않음 - flutter_tester 엔진은 반쪽 글자를 받아들여 회귀는 Windows 캡처로 잡음) 8 통과,
analyze 문제 없음, l10n 빠짐 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:30:46 +09:00
RyuJongJin 22ae03953d WIP 원격 확인 창: 옵션에 거르기 (--exclude 등) 가 있으면 "원격은 거르기를 따르지 않음" 주황 경고 · 안내 글에 SMB · FTP (검토 대기)
관리자 지시 (10/19, 15 의 원격 동기화 검토 M2): 원격 (WebDAV · SFTP · SMB · FTP) 은 rsync 대신 앱이 직접 맞추며 옵션의 거르기
(--exclude · --include · --filter · -f · --exclude-from · --include-from · -C · -F) 를 따르지 않는다 - 지키려고 뺀 것도 복사하고, 지우기면 지운다.
미리 보기도 거르기 없이 계산해 실제와 같지만, 확인 창은 이것을 알리지 않았다.
- 새 optionsFilter (sync_preview.dart): 옵션에 거르기가 있는지.
- Rsync 화면 확인 창 · 지우기 포함 작업의 확인 창 (모니터링 [실행] 등, confirmDeletingRun): 원격이고 거르기가 있으면 주황 경고
  "원격 (WebDAV · SFTP · SMB · FTP) 은 옵션의 거르기 … 를 따르지 않습니다 - 거른 것도 복사하고, 지우기가 들어 있으면 지웁니다. 위 비교 목록이 실제로 할 일입니다."
- 원격 안내 글 "WebDAV · SFTP: rsync 대신 앱이 직접 맞춥니다" → "WebDAV · SFTP · SMB · FTP: …" (SMB · FTP 도 같은 길).
- 관리자 지적: 6278403 에서 test/webdav_test.dart · test/sftp_test.dart 의 줄바꿈이 파일 전체에서 CRLF 로 바뀐 것을 원래 (LF) 로 되돌림
  (6278403~1 과 비교하면 +27 · +23 줄만 남음).
_mirrorV 가 RsyncFilter 를 따르게 하는 것은 next3 와 합친 뒤의 다음 일 (관리자 결정).

시험: sync_preview_test (새 1: optionsFilter 있음 10 · 없음 6, -e "ssh -C" 는 아님) · explorer_webdav_test (안내 글 · 거르기 없으면 경고 없음) ·
sync_preview_view · webdav · sftp 31 통과, 임시 폴더 흔적 0, analyze 문제 없음, l10n 빠짐 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:23:05 +09:00
RyuJongJin 092b68a28f next4: next2 (6278403) + next3 (63ef362) 합침 - 시험판 977 2026-10-19 22:15:47 +09:00
RyuJongJin 62784039e4 WIP P0 원격 맞추기: --delete 가 없으면 받는 쪽의 같은 이름 폴더를 지우지 않음 (예전: 통째로 지우고 파일로) · 미리 보기에 보임 (검토 대기)
관리자 지시 (10/19, 15 의 원격 동기화 검토 M1 - P0): WebDAV · SFTP · SMB · FTP 가 끼면 앱이 직접 맞추는데 (file_ops._mirrorV),
원본은 파일 X, 받는 쪽에는 같은 이름의 **폴더** X 가 있으면 --delete 가 없어도 그 폴더를 통째로 지우고 파일로 바꿨다.
미리 보기에는 "바뀜" 한 줄로만 보여 사용자가 모르게 지워졌다 (10/8 WebDAV 부터 정식 판에 있던 것으로 봄).
실제 rsync 는 --delete (또는 --force) 없이는 비어 있지 않은 폴더를 지우지 않는다.
- _mirrorV: --delete 가 없으면 그 이름은 건너뛰고 FileOps.dirConflicts 에 남긴다. --delete 면 지금처럼 지우고 파일로.
- 미리 보기 (sync_preview): 지우기 없음 → "받는 쪽에 같은 이름의 폴더가 있어 건너뜀 - 폴더는 그대로" (주황, 새 PreviewAction.skipDir),
  지우기 → 지울 목록에 "폴더 · 안의 n개를 지우고 같은 이름의 파일로" (빨강, 지울 수에 들어가 72 의 보호를 받음).
- 알림: 복사 · 이동 작업 끝 "같은 이름의 폴더가 있어 건너뜀 n개: …", 실시간 동기화 (lsync, 원격 쌍) 카드 "n개 맞춤 · 같은 이름의 폴더가 있어 건너뜀 n개" (같은 규칙).
- 로컬끼리 맞추기는 원래 폴더를 지우지 않았다 (파일 이름 바꾸기가 실패) - 그대로.

시험: webdav_test · sftp_test (새 각 1, 시험 WebDAV 서버 · paramiko SFTP 서버): 지우기 없음 → 미리 보기 skipDir · 지울 것 없음 · 실제로 폴더와 안의 파일 그대로 ·
다른 파일은 맞춤 · dirConflicts 1 / 지우기 → 미리 보기 지우기 줄 (안의 n개) · 실제로 파일로. analyze 문제 없음, l10n 빠짐 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:15:22 +09:00
RyuJongJin 63ef362f6d WIP 177 보충 3 (15 검토 F1~F4): 고치개 · H · P · merge · exclude-from 의 ! 등은 적용하지 않고 "미리 볼 수 없음" · 그런 규칙이 있으면 빨간 경고, --delete 와 함께면 체크해야 [실행] · 받는 쪽 받다 만 파일도 지울 목록에 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:14:38 +09:00
RyuJongJin 47165371eb WIP 글 뷰어: 긴 줄을 실제 글꼴로 줄을 바꿔 본 자리 (네 줄 끝) 에서 나눔 - 넓은 글자 · 고정폭이 아닌 글꼴에서도 줄 끝이 비지 않게 (검토 대기)
관리자 지시 (10/19, 27baf91 의 ③ 남은 것): 한글 · 한자처럼 넓은 글자가 섞인 줄도 줄 끝이 비지 않게.
Windows 캡처로 보니 'monospace' 가 실제로 고정폭이 아니라, 글자 수로 세면 (가장 넓은 'M' 기준) 영문 줄도 넷째 줄 끝이 비었다.
- 화면에서는 TextPainter 에 Text 와 같은 글꼴 · 글자 크기 · 기기 배율 · 폭으로 조각을 그려 보고, 다섯째 줄이 시작하는 자리 (= 네 줄의 끝) 에서 자른다.
  (조각 창은 화면 한 줄 글자 수 × 8 부터, 다섯 줄이 안 되면 두 배로). 한 번 나눈 결과는 글 · 폭 · 글자 크기가 같으면 다시 쓰지 않는다.
- viewerRows 는 cut (줄 끝 자리) 이 없으면 글자 폭 단위로: 넓은 글자 (isWideChar - 한글 자모 · 음절, 한자 · 가나 · 한중일 기호, 전각) 와 이모지는
  잰 비율 (기본 2) 칸으로 센다. 두 단위 글자는 반으로 자르지 않는다.
- 새 캡처 시험 integration_test/windows_textview_shot_test.dart (영문 · "한x" 섞임 · 한글만, 412 / 960).

확인: Windows 캡처 (release\test\shots_textview2 - 고치기 전 shots_textview 에서는 영문 · 섞인 줄의 넷째 줄 끝이 비었음) 6 통과,
xplore_tools_test (새 2: 넓은 글자 칸 세기 · 한글 섞인 긴 줄의 조각이 화면 네 줄) 7 통과, explorer X-plore g · h 통과, 임시 폴더 흔적 0, analyze 문제 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:05:43 +09:00
RyuJongJin 27baf91876 WIP 글 뷰어 (6366a2f 의 회귀 고침): 긴 줄을 나눠 그려도 Ctrl+A → Ctrl+C 는 줄 전체 · 나누는 폭을 실제 글자 폭에 · 막대에 컨트롤러 (검토 대기)
28 지적 · 관리자 지시 (10/19):
① 회귀: 줄바꿈 없는 긴 줄을 Ctrl+A → Ctrl+C 하면 화면에 그려진 조각만 복사됐다 (a74e7be 11,091자 → 6366a2f 1,513자).
   나눈 조각이 ListView.builder 의 줄이라 화면 밖 조각은 고르기에 들지 않았다.
   → Ctrl+A 를 누른 뒤 (다른 키 · 누르기 전까지) 의 Ctrl+C 는 읽은 글 전체를 복사한다 (화면의 강조는 앱 단축키 그대로). 나누는 것은 화면만.
   위 막대 [전체 복사] 는 원래부터 글 전체.
② (예전부터) Windows 에서 Scrollbar 에 컨트롤러가 없어 "ScrollPosition 없음" assert (데스크톱 ListView 는 PrimaryScrollController 를 쓰지 않음) → 같은 ScrollController 를 Scrollbar · ListView 에.
③ 보기: 4줄마다 줄 끝이 비어 보이던 것 - 글자 폭을 어림 (글자 크기 × 0.62) 대신 TextPainter 로 고정폭 글자 폭을 재서 (글자 크기 · 기기 글자 배율) 나눈다.
   (한글 · 한자처럼 두 칸 글자가 섞인 줄은 여전히 조금 빌 수 있음)

시험: xplore_tools_test (새 1: 11,090자 한 줄 + "\nend" 를 412 에서 열고 Ctrl+A · Ctrl+C → 11,094자 그대로 · 막대 컨트롤러 붙음 - 고치기 전 코드에서는 1,513자로 실패) 5 통과,
explorer_test X-plore g · h 통과, 임시 폴더 흔적 0, analyze 문제 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:54:05 +09:00
RyuJongJin b10158b46b WIP Android: 작업 알림 서비스 specialUse 설명을 실제 하는 일대로 · 망 종류를 ConnectivityManager 로 판단 - 권한 1개 추가 (관리자 판단) (검토 대기)
28 의 APK 점검 · 관리자 지시 (10/19):
1. KeepAliveService 의 PROPERTY_SPECIAL_USE_FGS_SUBTYPE 설명: "동기화 · 변환 · 다운로드" 만 적혀 있던 것을 실제로 하는 일 모두로
   (실시간 폴더 동기화 · MKV 변환 · AI 자막 · 다운로드 · 복사/이동 · 사용자의 SFTP/SMB/FTP/WebDAV 서버 전송 · 읽기 전용 Wi-Fi 공유, 알림에 무엇이 도는지 보임).
2. 망 종류 판단 (관리자 판단): 인터페이스 이름 (rmnet · ccmni …) 대신 Android 의 ConnectivityManager · NetworkCapabilities 로
   주소마다 wifi · ethernet · cellular · vpn 을 알아 (MainActivity "netKinds"), Wi-Fi 공유 주소 · SMB 찾기는 Wi-Fi · 유선만 쓴다.
   모바일 데이터 · VPN 은 뺀다. Tailscale (100.64/10) 은 VPN 이어도 설정 "Tailscale 에서도 열기" 대로 (통신사 CGNAT = cellular 는 뺀다).
   판단하지 못하면 (Android 가 아님 · 오류 · 종류를 모르는 주소 - 예: 휴대폰 핫스팟) 예전의 이름 방식.
   **권한 1개 추가 (관리자 판단): ACCESS_NETWORK_STATE** - 일반 권한 (사용자에게 묻지 않음), 망의 종류만 본다.
   릴리스 노트 초안 (위키 릴리스\다음판_노트_초안 · release\RELEASE_NOTES_draft_next.md) 에도 적음.
usesCleartextTraffic 은 그대로 (관리자: Android 는 IP 범위로 줄일 수 없고 http NAS 사용자가 있음).

시험: net_security_test (새 1: 종류가 이름보다 먼저 · 모르는 주소는 이름 · Tailscale 설정 · CGNAT) · lan_scan · smb_find · mdns_smb 13 통과, analyze 문제 없음,
APK 빌드 (Kotlin 컴파일) 성공. 실제 기기 (Wi-Fi + 모바일 데이터 동시 · VPN · Tailscale) 확인은 테스터 몫.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:51:53 +09:00
RyuJongJin 25a0cd3c96 WIP 통합 시험 · 캡처의 임시 폴더: 시험 끝에 (실패해도) 지우기 · test_temp_check 가 통합 시험에도 (검토 대기)
관리자 지시 (10/19, 28 이 찾음): Windows 캡처 · 통합 시험 (integration_test, -d windows) 이 %TEMP%\jj_* 시험 폴더를 남겼다
(시험 본문 끝의 deleteSync 는 실패하면 건너뛰고, 앱 · WebView 가 파일을 잡고 있으면 지우기가 실패했다).
- 새 integration_test/support/temp_dir.dart: testTempDir(prefix, {base}) - 만들면서 addTearDown 으로 지우기를 걸어 실패해도 지운다.
  잠겨 있으면 0.25초씩 20번 다시 (실제 시간 - 가짜 시계 밖), 긴 경로는 \?\, 끝내 못 지우면 알리기만.
  통합 시험 26개 파일의 Directory.systemTemp.createTempSync('jj…') 를 모두 이것으로 (JJ_SHOT_TMP 를 쓰는 캡처 도우미 포함).
- tool/test_temp_check.ps1: integration_test 의 이름 (testTempDir 포함) 도 모은다. 넘겨받은 .dart 파일이 있으면 그 파일과 그것이 import 하는
  support 파일의 이름만 센다 - 같은 때 다른 세션 · worktree 가 돌린 시험의 폴더 (jj_explorer_ · jj_sync_ · jj_fake_recycle_) 를 세던 거짓 실패를 없앰.
  시험 소스에 없는 이름의 새 jj 폴더는 참고로만 보인다. 사용: test_temp_check.ps1 integration_test\windows_b6_shot_test.dart -d windows
- com_guard_test 의 쓰지 않는 dart:io import 정리 (analyze).

시험: windows_b6 · windows_xplore · windows_ftp · windows_wifi 캡처 통과 · 새로 남은 것 0 (test_temp_check), analyze 문제 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:51:39 +09:00
RyuJongJin 86a994690b WIP 시험: 177 비교에 28 이 실제 실행으로 더 확인한 6개 (--del · --delete-after · **/x/ 등, 대상에만 있는 old2/ 안의 거른 것) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:50:02 +09:00
RyuJongJin 09fbe0337a WIP 177 보충 2: 28 의 장면 · 옵션 19개를 실제 rsync 와 비교하는 시험 · 가운데 "/**/" 는 폴더 하나 이상 (맨 앞 "**/" 만 0개) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:45:33 +09:00
RyuJongJin cda2a30664 WIP 177 보충 (28 확인): 대상에만 있는 폴더 안에 거른 것이 있으면 폴더는 남기고 안의 것만 지울 목록에 · ? · [..] 는 rsync 처럼 UTF-8 바이트 단위 · **/ 는 폴더 0개에도 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:40:42 +09:00
RyuJongJin 45e1a95df0 WIP 204 Android 브라우저: 아래 시스템 막대 (제스처 · 작업 표시줄) 가 잠깐 커질 때 웹 뷰 크기를 바꾸지 않고 그 띠 위로 그림 - 아래에서 검은 띠가 올라오던 것 (기기 확인 전, 원인 추정) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:35:47 +09:00
RyuJongJin 1396ba41c8 WIP 시험: Total Commander 모양 F8 = 지우기 - 확인 창 [취소] 면 그대로, [휴지통으로] 면 (가짜) 휴지통으로 (28 지적) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:20:36 +09:00
RyuJongJin cfed8fa355 WIP 145 초기화 창 캡처 (ko · en · ja × 412 · 960, 시험 windows_reset_shot): 잘림 없음 · 문구를 사실대로 - SFTP · SMB · FTP 비밀번호 · 키도 지움, "앱 설정 전체" 는 파일 탐색기 즐겨찾기도 지움 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:16:49 +09:00
RyuJongJin 722edc8ed1 WIP 177 Rsync 실행 전 비교 (미리 보기 · 지울 목록) 가 옵션의 거르기 (--exclude · --include · --exclude-from · --filter · --delete-excluded) 를 실제 rsync 와 같게 따름 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:09:13 +09:00
RyuJongJin 58254e42b2 WIP Wi-Fi 공유 (Windows): 켜기 전 방화벽 안내 · 막혔을 때 "개인 네트워크 허용" 하는 법과 설정을 여는 버튼 (검토 대기)
관리자 지시 (10/19, 사용자 "오늘 못했던 것 진행" 3번):
- 켜기 전 화면: "켜면 Windows 방화벽 창이 뜰 수 있습니다: '개인 네트워크' 에 체크하고 [허용]. '공용 네트워크' 는 체크하지 않는 것이 안전" (전에는 한 줄 안내만).
- 켜기 전 · 공유 중 화면 모두에 펼침 도움말 [다른 기기에서 열리지 않으면 (Windows 방화벽)]:
  ① 이 PC 의 망이 "개인 네트워크" 인지 (설정 > 네트워크 및 인터넷 > … > 네트워크 프로필 유형) + [네트워크 설정 열기] (ms-settings:network-status)
  ② 방화벽에서 이 앱 "개인" 허용 (제어판 > Windows Defender 방화벽 > 앱 허용 > [설정 변경]) + [방화벽 허용 앱 열기] (control /name Microsoft.WindowsFirewall /page pageConfigureApps)
  ③ 목록에 없으면 [다른 앱 허용] 으로 JJ_MKVMaker.exe · 바꾼 뒤 공유를 끄고 다시 켜기 · 회사 · 학교 PC 는 바꿀 수 없을 수 있음.
  내 판단: 방화벽을 앱이 직접 바꾸지 않는다 (관리자 권한 · 사용자 몰래 바꾸지 않게) - 안내와 설정 화면 열기만.

시험: wifi_share_page_test (새 1: 켜기 전 안내 · 도움말 펼치기 · 두 버튼이 여는 것) · wifi_share_test 10 통과, 임시 폴더 흔적 0, analyze 문제 없음, l10n 빠짐 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:55:38 +09:00
RyuJongJin 6366a2f880 WIP 글 뷰어: 줄바꿈 없는 큰 파일은 화면 폭에 맞춰 나눠 그려 멈추지 않게 (검토 대기)
관리자 지시 (10/19, 사용자 "오늘 못했던 것 진행" 2번 - 데이터 검토에서 남긴 작은 점):
줄바꿈 없는 큰 파일 (한 줄 수십만 자) 을 한 Text 로 그려 잠깐 멈췄다.
- 화면 한 줄에 드는 글자 수 (고정폭 글꼴 · 글자 크기 · 줄 번호 칸) 를 보고, 그 8배보다 긴 줄만 4배 (화면 네 줄쯤) 씩 나눠 그린다.
  보통 줄은 그대로 (화면에서 저절로 줄바꿈). 줄 번호는 첫 조각에만. 이모지 같은 두 단위 글자는 반으로 자르지 않는다.
- 나눈 줄은 글 · 한 줄 글자 수가 같으면 다시 만들지 않는다 (viewerRows).

시험: xplore_tools_test (새 2: viewerRows - 글 그대로 · 줄 번호 · 8배까지 그대로 · 이모지 / 200KB 한 줄 파일을 412 폭에서 여러 짧은 조각으로) 4 통과,
임시 폴더 흔적 0 (tool/test_temp_check.ps1), analyze 문제 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:50:59 +09:00
RyuJongJin 53f5c432bb WIP 압축 펼치기: 펼친 뒤 압축 파일이 바뀌면 (크기 · 바뀐 때) 펼치거나 새로 고칠 때 목록을 다시 읽음 (검토 대기)
관리자 지시 (10/19, 사용자 "오늘 못했던 것 진행" 1번 - 압축 펼치기의 알려진 한계):
읽어 둔 압축 목록을 계속 써서, 다른 프로그램이 압축을 바꿔도 접었다 펼치거나 새로 고쳐도 예전 목록이 보였다.
- 압축을 읽을 때의 (크기, 바뀐 때) 를 기억하고, 다시 펼칠 때 · 새로 고침 (F5 · [새로 고침] · 앱으로 돌아옴) 때 비교해 다르면
  그 압축의 목록과 모든 창의 그 안 폴더 목록을 잊고 다시 읽는다.
- 압축이 없어졌으면 목록을 잊고 그 안은 빈 목록으로 (예전 것을 보이지 않게).

시험: explorer_test (새 1: 펼친 뒤 압축을 바꾸고 F5 → 새 항목 · 예전 항목 없음) 62 통과, 임시 폴더 흔적 0 (tool/test_temp_check.ps1), analyze 문제 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:47:58 +09:00
RyuJongJin a74e7be59b 버전 2026.10.18_001
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 12:03:22 +09:00
RyuJongJin 6567d8a5b4 WIP 시험 흔들림: batch_test "186 · 187" - 실제 시간으로 기다리기 · 한도 4분 · 임시 폴더 지우기 다시 시도 (검토 대기)
976 직전 전체 시험 (50bd2bf) 에서 PC 가 바쁠 때 정해진 횟수 (400 × 25ms) 안에 v1 이 시작하지 않아 30초 한도에 걸렸고,
그때 ffmpeg 가 파일을 쥐고 있어 jj_batch186_ 임시 폴더를 지우지 못하고 남겼다. 240 (e7cd30e) 과 같은 방식으로 고침. 기대값은 그대로.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 04:52:24 +09:00
RyuJongJin 29b4365b74 WIP 압축 안 메뉴 정리: 펼친 압축 안의 것에는 [ZIP 으로 묶기] · [Wi-Fi 로 공유] · [PDF 로 만들기] 를 숨김 (검토 대기)
관리자 지시 (10/18, 데이터 검토에서 찾은 것): 펼친 압축 안의 항목은 실제 파일이 아니라 위 셋을 눌러도 없는 경로라 오류만 났다 (데이터는 다치지 않음).
→ 누른 것이나 표시한 것 중 하나라도 압축 안이면 그 셋을 메뉴에서 뺀다. 압축 밖은 그대로.

시험: explorer_test X-plore 16 통과 (f 에 덧붙임: 압축 안 폴더 메뉴에 ZIP · Wi-Fi 없음 · 밖의 폴더에는 ZIP 있음), analyze 문제 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 04:47:12 +09:00
RyuJongJin 50bd2bf259 WIP 데이터 검토: 대소문자만 다른 이름으로 바꿀 때 대소문자를 가리는 폴더의 다른 파일을 덮던 것 (검토 대기)
관리자 지시 (10/18, cd 의 X-plore e~i · 칸 그림 데이터 안전 검토) 중 찾음:
FileOps.rename 은 a.txt → A.txt 처럼 대소문자만 바뀌면 "같은 파일의 대소문자 바꾸기" 로 보고 같은 이름 확인을 건너뛰었다.
대소문자를 가리는 곳 (Android 앱 폴더 · ext4 · 일부 NAS 를 로컬로 붙인 것 · Windows 의 대소문자 구분 폴더) 에 A.txt 가 따로 있으면
File.rename 이 그 파일을 말없이 덮었다 (이름 변경 · 일괄 이름 바꾸기 · [D][T] 모두 이 길).
→ 대소문자만 다를 때는 폴더 목록에 정확히 그 이름이 따로 있는지 보고, 있으면 "같은 이름이 이미 있습니다" (목록을 못 읽으면 막음).
대소문자를 가리지 않는 보통 Windows 폴더에서 a.txt → A.txt 는 그대로 된다.

시험: file_ops_test (새 1: 보통 폴더는 바뀜 · fsutil 로 만든 대소문자 구분 폴더에서는 막고 두 파일 그대로 - 고치기 전에는 A.txt 를 덮어 실패) ·
batch_rename · batch_rename_date · app_trash · xplore_tools 34 통과, analyze 문제 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 04:39:24 +09:00
RyuJongJin 25e3123aac Merge commit 'e4de7ed' into next 2026-10-18 04:32:12 +09:00
RyuJongJin e4de7ed28e WIP 960 캡처: 가운데 버튼 [좌우 ⇆] 의 en 이름이 두 줄로 꺾이던 것 - "Columns ⇆" · "Rows ⇅" (검토 대기)
관리자 지시 (10/18, 960 폭 사진 점검): en "Side by side ⇆" 가 버튼 줄에서 "Side by side / ⇆" 두 줄로 꺾였다.
→ en 만 짧은 짝으로: 좌우 ⇆ = "Columns ⇆", 위아래 ⇅ = "Rows ⇅", 묶음 = "Columns ⇆ / Rows ⇅".
업데이트 안내 글 (migration_notice) 의 버튼 이름도 같은 것으로 (전에는 "Top and bottom" 으로 달랐음). ko · ja · zh 는 그대로.

확인: windows_xplore 캡처 (release\test\shots_trunc\windows_xplore_orient, 960 에서 한 줄) 4 통과, i18n_test 통과, l10n 빠짐 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 04:26:42 +09:00
RyuJongJin 160bfe85e0 Merge commit '565ad3b' into next 2026-10-18 04:25:04 +09:00
RyuJongJin 565ad3bd17 WIP 960 캡처: 탐색기 동영상 줄의 "크기 · 날짜" 가 넓은 창에서도 잘리던 것 - 동영상 정보와 한 줄 글로 (검토 대기)
관리자 지시 (10/18, next e8baa84 의 960 폭 캡처에서 찾음): X-plore 모양 트리의 동영상 줄이 "5KB · 2026-10-18 03:…" 로 잘렸다.
크기 · 날짜 글과 동영상 정보 (_MetaText) 가 Flexible 둘이라 폭을 반씩 나눴고, 정보가 비어 있어도 반을 차지했다.
→ _MetaText 가 앞글 (크기 · 날짜) 과 정보를 한 Text.rich 로 그린다 (넘치면 끝을 줄임). 동영상이 아닌 줄은 그대로.

확인: windows_xplore 캡처 (release\test\shots_trunc\windows_xplore_meta - 960 에서 "5KB · 2026-10-18 04:13" 다 보임) 4 통과, explorer_test 56 통과, analyze 문제 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 04:22:08 +09:00
RyuJongJin 6b6c23dc49 Merge commit '53a7418' into next 2026-10-18 04:11:01 +09:00
RyuJongJin 02a79839da WIP 시험 흔들림: folder_size_test "화면에서 사라진 폴더" - 조건이 될 때까지 실제 시간으로 기다리기 · 한도 2분 (관리자 지시) (검토 대기)
전체 시험 중 PC 가 바쁘면 정해진 횟수 (300 × 10ms) · 고정 시간 (20 · 100ms) 안에 계산이 끝나지 않아 흔들렸다 (11d709a 관리자 확인).
idle 을 실제 시간 (최대 30초) 으로, 고정 기다림은 조건 (읽기 시작 · busy 가 풀림) 으로. 기대값은 그대로.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 04:11:01 +09:00
RyuJongJin 53a74183a7 WIP 412 캡처의 잘림: 서버 창 칸 이름은 짧게 · 설명은 helper, 해시 비교 칸, 날짜 고르기, 갈 곳 막대는 가운데 줄임 (검토 대기)
관리자 지시 (10/18, next e8baa84 캡처에서 찾은 ①~④, 회귀 아님):
① FTP · SFTP · SMB · WebDAV 서버 창: "이름 (탭에 보임, 비우면 주소)" · "시작 폴더 (비우면 …)" · "도메인 · 작업 그룹 (보통 비움)" 이 en 412 에서 잘렸다
  → 칸 이름은 "이름" · "시작 폴더" · "도메인 · 작업 그룹", 설명은 helper (3줄까지).
② 해시 창 비교 칸 → "비교할 해시" + helper "받은 곳에서 알려 준 해시를 붙여넣으세요".
③ 일괄 이름 바꾸기 날짜 고르기: 고른 뒤 상자에는 짧게 ("날짜: 찍은 때" · "날짜: 바뀐 때"), 펼친 목록은 긴 설명 그대로.
④ 한 창 갈 곳 막대: "갈 곳:" 은 늘 보이고 폴더 이름이 길면 가운데를 줄인다 (middleEllipsis · textWidth - path_label.dart). 전체 경로는 길게 눌러 · 마우스를 올려.

확인: integration_test/windows_trunc_shot_test.dart (새, 서버 창 4개 위 · 아래, 412/960 x ko/en/ja) · windows_ftp · windows_p3 · windows_xplore_tools · windows_xplore 캡처 통과
(release\test\shots_trunc), explorer · explorer_webdav · batch_rename · file_tools · ftp · smb · smb_find 시험 통과, analyze 문제 없음, l10n 빠짐 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 04:09:02 +09:00
RyuJongJin 11d709a97b Merge commit '63b92b6' into next974 2026-10-18 03:30:27 +09:00
RyuJongJin 63b92b6956 WIP 보안 검토 6: 설정 "SMB 서명 · 암호화 필수" (처음 끔) · 서명도 암호화도 안 되는 연결은 탐색기 탭에 ⚠ (검토 대기)
관리자 결정 (10/18):
- 설정 > SMB [SMB 서명 · 암호화 필수] (smbRequireSecure, 처음 끔 - 오래된 NAS 가 붙지 않을 수 있음, 틀린 값은 끔).
  켜면 Android 다리 (smbj) 가 서명을 요구하고 (withSigningRequired), 로그인 뒤 세션이 서명도 암호화도 안 되면 닫고 막는다
  → "서명 · 암호화가 되지 않는 연결이라 열지 않았습니다 (설정 …)". 설정을 바꾸면 열려 있던 연결을 닫는다 (다음 연결부터).
- 서버마다 처음 연결이 되면 info 를 한 번 물어 (세션의 shouldEncryptData · isSigningRequired) 서명도 암호화도 안 되면 탐색기 · Rsync 탭에 ⚠ (DavPath.mark, 꺼 두어도).
  info 의 "encrypted" 는 협상 가능 여부 (supportsEncryption) 가 아니라 이 세션이 실제로 암호화되는지로 바꿈. [연결 확인] 경고도 서명 여부를 따라 두 가지.
- 서버 목록에서 빠지거나 설정이 바뀐 서버는 ⚠ 기억도 지운다.

시험: smb_test (새 1: 처음 끔 · 틀린 값 · 연결 전 🗄 · 처음 한 번만 info · ⚠ · 서명만 되면 ⚠ 없음 · 필수를 다리에 넘김 · 막힌 이유) · smb_find · explorer_webdav 11 통과,
analyze 문제 없음, l10n 빠짐 0, APK 빌드 (Kotlin 컴파일) 성공. 실제 NAS (서명 · 암호화 켬 / 끔) 확인은 기기에서.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 03:16:57 +09:00
RyuJongJin 38f53e70cc WIP 시험 흔들림: sftp_explorer_test 는 실제 시간으로 기다리고 시험 서버는 포트가 받는 것을 본 뒤 시작 (검토 대기)
관리자 지적 (10/18): "처음 연결: 지문 안내 → … → 지문은 설정에 저장" 이 전체 시험 (wt_mgr 00e9685) 에서만 실패.
시험 서버 (paramiko) 는 PORT 를 찍기 전에 listen 하므로, 무거울 때 걸리는 것은 settle 의 횟수 (300번 ≈ 10~15초) 였다 - 키 교환이 그보다 오래 걸림.
- settle: 횟수 대신 실제 시간 (처음 90초) 까지 조건을 기다린다.
- SftpTestServer.start: PORT · FINGERPRINT 줄을 기다리는 시간을 정하고 (60 · 30초, 서버가 죽으면 끝없이 기다리지 않게), 포트가 연결을 받는 것을 본 뒤 돌려준다.

시험: sftp_explorer · sftp_stream · sftp_test 10 통과.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 03:00:02 +09:00
RyuJongJin f6bdc35920 Merge commit '19d4f7b' into next974 2026-10-18 02:59:15 +09:00
RyuJongJin f10858c68c Merge commit '8ca9184' into next974 2026-10-18 02:59:13 +09:00
RyuJongJin 5038a60b87 Merge commit '546b915' into next974 2026-10-18 02:59:08 +09:00
RyuJongJin e7cd30ef41 WIP 시험 흔들림: batch_test P0 (931) 묶음을 239 처럼 조건이 될 때까지 기다리기 · 한도 4분 (관리자 지시) (검토 대기)
전체 시험 중 PC 가 바쁘면 (Windows 통합 시험과 겹침) "240 집계" 가 정해진 횟수 (800 × 25ms) 안에 v2 가 시작하지 않아
기본 30초 한도에 걸렸다. 시간으로 기다리고 (최대 90초), 이 묶음 세 시험의 한도를 4분으로. 기대값은 그대로.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 02:58:16 +09:00
RyuJongJin 546b915b55 WIP X-plore 숨기기 후속: 지우기 · 옮기기 · 이름 바꾸기를 따라감 · 설정의 [목록에서 숨긴 항목] · 위 막대 ⋮ 에도 보이기 (검토 대기)
관리자 결정 (10/18, 9f758ac 의 판단에 대해):
- 이름 바꾸기 (하나 · 일괄) · 옮기기 (다른 창으로 · 붙여넣기 · 끌어 놓기 · 되돌리기 - 작업이 끝나면) 를 하면 숨긴 목록의 경로도 새 경로로 (그 안의 것도).
  옮기기는 원래 자리에 없고 새 자리에 있는 것만 (같은 이름이라 바꾼 것 · 실패는 그대로).
- 영구로 지운 것 (그 안 포함, 실제로 없어진 것만) 은 목록에서 뺀다. 내 판단: 휴지통 (Windows · 앱 휴지통) 으로 보낸 것은 남긴다 - [되돌리기] 로 되살리면 그대로 숨김이 되게.
- 환경 설정 > 파일 탐색기 [목록에서 숨긴 항목 (n개)] → 목록 (이름 · 폴더) 과 하나씩 [숨기기 풀기] · [모두 풀기] (lib/ui/hidden_items_dialog.dart). 풀면 열려 있는 탐색기에 바로 보인다 (설정 바뀜을 듣는다).
- 위 막대 ⋮ 메뉴에도 [목록에서 숨긴 항목 보이기 (n개)] · [다시 숨기기] (큰 폴더를 펼쳐 목록 맨 아래가 멀 때).

시험: explorer_test (새 3: 이름 바꾸기 → 새 경로 · 영구 삭제 → 뺌 / 다른 창으로 옮기기 → 새 자리 / ⋮ 보이기 · 다시 숨기기 · 설정 창에서 풀기 → 바로 보임) 포함 68 통과, analyze 문제 없음, l10n 빠짐 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 02:57:16 +09:00
RyuJongJin 19d4f7bf3d WIP 보안 검토 10 · 3 보충: Wi-Fi 공유는 디스크를 비동기로 · 설정 "Tailscale 에서도 열기" (검토 대기)
관리자 결정 (10/18):
- 10: 요청마다 하던 경로 확인 (링크 · 폴더 밖) · 폴더 목록 · 파일 크기를 비동기 IO 로 (같은 망의 기기가 큰 폴더를 요청해도 화면 스레드가 멈칫하지 않게). 켤 때의 폴더 확인도.
- 3 보충: Tailscale (100.64/10) 주소는 계속 열되, 공유 화면에 [Tailscale 에서도 열기] (처음 켬, wifiShareTailscale). 끄면 같은 Wi-Fi · 유선 주소에만 연다. 틀린 값은 처음 값 (켬).

시험: net_security_test (Tailscale 끔 · 처음 값 · 틀린 값) · wifi_share · wifi_share_page · settings_store 22 통과, analyze 문제 없음, l10n 빠짐 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 02:48:38 +09:00
RyuJongJin 8ca9184fdc WIP 보안 검토 7: FTPS 사설 CA 체인 시험 - 지문은 서버 인증서 (잎) 것 (검토 대기)
관리자 지시 (10/18): 사설 CA → 서버 인증서 체인을 시험 서버에 만들어 (tool/ftp_test_server.py --chain, cryptography 로 시험 전용 CA · 잎, 서버는 use_certificate_chain_file 로 체인 전체를 보냄)
onBadCertificate 가 넘기는 인증서가 잎인지 확인했다 → 잎이다 (고칠 것 없음). 이것을 시험으로 남긴다:
- 처음 연결: FtpCertException 의 지문 = 잎 지문 (CA 지문 아님), subject 에 127.0.0.1
- CA 지문을 믿고 있으면 "바뀜" 으로 막는다 (같은 CA 가 서명한 다른 인증서를 받지 않게)
- 잎 지문을 믿으면 명령 · 자료 연결 (PROT P) 모두 됨
시험 서버는 잎 지문 (FINGERPRINT) 과 함께 CA_FINGERPRINT 를 알린다.

시험: ftp_test (새 1) 통과.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 02:40:50 +09:00
RyuJongJin 00e9685a33 Merge commit '4ae6845' into next974 2026-10-18 02:32:33 +09:00
RyuJongJin 7c068ede3e Merge commit '9f758ac' into next974 2026-10-18 02:32:23 +09:00
RyuJongJin 6434eb85d4 Merge commit '71c995e' into next974 2026-10-18 02:32:15 +09:00
RyuJongJin 4ae6845513 WIP 새 네트워크 기능 보안 고침: 원격 이름 경로 벗어나기 · FTPS 주입 · Wi-Fi 공유는 Wi-Fi · 유선 주소에만 · 찾기 범위 · SMB 강제 DNS (검토 대기)
관리자 지시 2 (10/18, SFTP · SMB · FTP · Wi-Fi 공유 · 스트리밍 다리 · mDNS/훑기 검토) 의 고침:
1 (P0, 로컬 데이터) 악성 · 탈취된 서버가 목록 이름을 "..\..\x" 로 보내면 임시 사본이 앱 임시 폴더 밖에 쓰이고 맞추기 (--delete) 가 위 폴더를 지울 수 있었다
  → vList: 그 폴더 바로 아래의 평범한 이름만 (".." · "." · "\" · NUL · 다른 폴더 버림, 모든 원격 목록이 거침) · DavPath.parse 는 ".." 마디 거부 · vLocalCopy 는 임시 폴더 안인지 확인 (서버에 묻기 전에)
2 FTPS 명시: 234 뒤에 평문으로 끼워 넣은 줄이 있으면 끊는다 (STARTTLS 주입)
3 Wi-Fi 공유: 0.0.0.0 이 아니라 화면에 보이는 Wi-Fi · 유선 · Tailscale 주소에만 따로 연다. 모바일 데이터 (rmnet · ccmni 등 - 통신사 10.x · 100.x) · 가상 어댑터 (WSL · Hyper-V · VirtualBox · VMware · Docker) · 다른 VPN 은 뺀다.
  주소가 없으면 켜지 않고 알림. 캡처에서도 172.24.240.1 (WSL) · 192.168.56.1 (VirtualBox) 이 주소 목록에 보였음.
4 SMB 찾기: Wi-Fi · 유선 연결만 훑는다 (isLanInterfaceName - 3 과 같은 기준)
5 SMB: 서버 이름을 FTP · SFTP 와 같은 DNS 설정 (강제 DNS) 으로 풀어 주소로 넘긴다 (Android 다리의 시스템 DNS 로 새지 않게)
8 FTP: \r · \n · NUL 이 든 명령은 보내지 않는다 (이름으로 명령 끼워 넣기)
11 mDNS: 사설망 주소만 받는다
15 [마스터 초기화] 확인 글에 SFTP · SMB · FTP · SFTP 키도 (실제로 지우는 것과 같게)
같이: Wi-Fi 공유 캡처 시험 (integration_test/windows_wifi_shot_test.dart) - 시험용 주소는 WifiShare.debugAddresses 로 (1차 캡처에서 화면이 다시 그려지지 않아 이 PC 의 실제 망 주소가 찍혔음)

남김 (보고서에): 6 SMB 서명 필수 · 암호화 안 될 때 표시 (Kotlin · 실기기), 7 FTPS 사설 CA 체인의 지문 고정 (사설 CA 서버 시험 필요), 10 Wi-Fi 공유 동기 IO.
괜찮음: 9 원격 임시 사본은 정리 창 "작업 임시 파일" 이 지움, 비밀 값 기록 · 설정 · 백업 · 오류 · 알림 · 경로 복사 없음, 인증서 · 호스트 키 건너뛰기 없음, 다리는 127.0.0.1 · 비밀 주소.

시험: test/net_security_test.dart (새 7) + 전체 902 통과 (17 건너뜀), analyze 문제 없음, l10n 빠짐 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 02:29:58 +09:00
RyuJongJin 9f758ac386 WIP X-plore 숨기기: 고른 파일 · 폴더를 목록에서만 숨기기 · 맨 아래 [숨긴 항목 보이기] (검토 대기)
관리자 지시 (10/18, X-plore 비교표 P3):
- 길게 누르기 메뉴 [목록에서 숨기기] (표시한 것이 여럿이면 "n개 목록에서 숨기기"). 앱 설정 explorerHiddenItems 에 경로 목록으로만 둔다 - 파일 · 폴더 자체 (속성 · 이름) 는 바꾸지 않는다.
- 펼친 폴더 안에 숨긴 것이 있으면 목록 맨 아래 (X-plore 자리) 에 [숨긴 항목 n개 보이기] → 흐리게 (불투명도 0.5) 보이고 [숨긴 항목 n개 다시 숨기기]. 보이기는 이번 실행 동안 · 모든 창 (내 판단: 다시 켜면 숨김).
  트리 (두 창 · 한 창) · 폴더 + 파일 목록 배치의 오른쪽 목록 모두.
- 보이는 숨긴 것의 메뉴는 [숨기기 풀기].
- 숨긴 것은 지우기 · 이동 대상에서 빠지지 않는다 (폴더를 지우거나 옮기면 안의 숨긴 것도 함께). 지우는 폴더 안에 숨긴 것이 있으면 확인 창에
  "지우는 폴더 안에 목록에서 숨긴 항목 n개가 있습니다 - 함께 지워집니다".
- 숨긴 것은 모두 고르기 · 빠른 필터 결과에서 빠진다 (보이는 것만 - 76 과 같게).
- 내 판단: 지우거나 이름이 바뀐 항목의 경로는 목록에 남는다 (같은 이름이 다시 생기면 숨김). 설정 화면에서 목록 보기 · 지우기는 아직 없음.
  트리는 Temp 처럼 큰 폴더를 펼쳐 두면 맨 아래가 멀다 (X-plore 와 같은 자리) - 필요하면 가운데 버튼 줄에도 둘 수 있음.

시험: explorer_test (새 2: 숨기기 → 목록에서 빠짐 · 파일 그대로 · 설정에 경로 · 보이기 → 흐리게 · 풀기 / 숨긴 것이 든 폴더 지우기 확인 창 알림 · 다른 파일은 알리지 않음) 57 통과,
settings_store_test 통과, analyze 문제 없음, l10n 빠짐 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 02:21:26 +09:00
RyuJongJin 71c995ee13 WIP SMB 찾기 화면 캡처 시험 (검토 대기)
integration_test/windows_smbfind_shot_test.dart - [같은 망에서 찾기] · 찾는 중 · 찾은 목록 · 고른 뒤 · 못 찾음, 412/960 x ko/en/ja (가짜 찾기, 실제 망은 훑지 않음).
결과 release	est\shots_smbfind_88379f1 (30장, 6 통과). 412 폭의 주소 안내 잘림은 next 의 17c1ea7 (helperMaxLines 3) 로 고쳐짐.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 02:06:52 +09:00
RyuJongJin e8baa846bb Merge commit 'd29593b' into next
# Conflicts:
#	assets/l10n/en.json
#	assets/l10n/ja.json
#	assets/l10n/zh-Hans.json
2026-10-18 02:02:22 +09:00
RyuJongJin a854299dda Merge commit '752707b' into next
# Conflicts:
#	lib/ui/ftp_settings.dart
2026-10-18 02:02:06 +09:00
RyuJongJin 7e2dc34b8d Merge commit '17c1ea7' into next 2026-10-18 02:01:50 +09:00
RyuJongJin 17c1ea7a04 WIP 서버 창 예시 글: 폰 세로 (412) 에서 잘리던 것 - FTP · SFTP · SMB · WebDAV 모두 세 줄까지 (검토 대기)
15 FTP 캡처에서 찾음 (관리자 지시 10/18, hint_fix = next 위). 서버 주소 · 시작 폴더 · 공유 이름 · WebDAV 주소 칸의 helperText 에 helperMaxLines: 3. 글은 그대로.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 01:50:07 +09:00
RyuJongJin d29593b2e6 WIP P3 보충: 폴더 속성 · 파일 [정보] 에 만든 때 · 숨김 · 읽기 전용 · 시스템 (보기만) (검토 대기)
관리자 지시 (10/18). lib/core/file_attributes.dart: Windows 는 FFI GetFileAttributesExW (kernel32, file_ops 의 GetDriveTypeW 와 같은 꼴), 그 밖 (Android) 은 이름이 점으로 시작하면 숨김 · 만든 때는 모름. 바꾸기는 넣지 않음.
폴더 속성 창: 만든 때 · 속성 (없으면 없음). 파일 [정보] 창: 만든 날 · 속성 (원격은 뺌).
시험: file_attributes_test 2 (시험 파일에만 attrib +h +r 을 켜고 끝에 끔 - 숨김 · 읽기 전용 · 폴더 · 없는 경로 null · 만든 때) · file_tools · explorer P3 통과, analyze 문제 없음, l10n 빠짐 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 01:47:45 +09:00
RyuJongJin 752707b842 WIP l FTP 캡처 (15): 412 에서 서버 주소 · 시작 폴더 칸의 예시 글이 잘리던 것 - 세 줄까지 (검토 대기)
캡처 시험 integration_test/windows_ftp_shot_test.dart (목록 · 추가 창 · 평문 경고 · 인증서 지문 창, 412/960 × ko/en/ja, release\test\shots_ftp_dd9e93b, 6 통과).
목록의 평문 ⚠ · 처음 값 FTPS · 평문 빨간 경고 · 지문 창은 세 언어 모두 문제 없음. 같은 꼴의 SFTP · SMB 창 예시 글도 잘리지만 이 브랜치에서는 FTP 만 고침.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 01:44:56 +09:00
RyuJongJin 8eb1674382 WIP X-plore P3 보충 (캡처에서 찾음) · 경로 막대 누르기로 위 폴더 고르기 (검토 대기)
캡처 (integration_test/windows_p3_shot_test.dart - 해시 · 비교 · 폴더 속성 · 16진, 412/960 × ko/en/ja, release\test\shots_p3_fix) 에서 찾아 고침:
- 16진 보기: 폭 412 에서 한 줄 16 바이트면 끝의 글자 칸이 화면 밖으로 잘렸다 → 폭 560 미만은 한 줄 8 바이트 (line(per:), 처음 값 16 그대로)
- 해시 창: 닫자마자 글 칸 컨트롤러를 dispose 해 닫히는 동안 "A TextEditingController was used after being disposed" (닫을 때마다) → 닫힌 뒤 dispose
관리자 지시 3 (X-plore 비교표의 작은 것): 위 막대의 경로를 누르면 지금 폴더 · 위 폴더들 (창의 맨 위까지, 가까운 것부터) 이 나와 고르면 그리로 (X-plore 4.4x 경로 막대).
지금 폴더는 고를 수 없게 흐리게.

시험: explorer_test (새: 경로 막대 - 지금 폴더 못 고름 · 바로 위 = left · 고르면 하나 적어짐) · file_tools_test 통과, analyze 문제 없음, l10n 빠짐 0, 캡처 시험 6 통과.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 01:43:17 +09:00
RyuJongJin 4070f8d2ee Merge commit '88379f1' into next 2026-10-18 01:18:36 +09:00
RyuJongJin 88379f16f4 WIP X-plore j 보충: SMB 찾기에 mDNS (_smb._tcp.local) 를 먼저 (검토 대기)
관리자 지시 (10/18): 훑기보다 먼저 mDNS 로 빠르게 찾기.
- lib/core/mdns_smb.dart (새, 패키지 없음): "레거시 유니캐스트" 질의 (RFC 6762 6.7) - 빈 포트에서 224.0.0.251:5353 으로 PTR (QU) 을 묻고
  응답자가 그 포트로 바로 답한다. Android 의 멀티캐스트 받기 (MulticastLock, Kotlin) 없이도 받을 수 있게 고른 방식 (내 판단).
  PTR → 인스턴스, SRV → 호스트 이름, A → IPv4 (이름 압축 읽기). 1.5초 모으고, 못 찾으면 빈 목록 (오류 없음). 묻기만 하고 알리지 않음
- 찾기 창: mDNS 먼저 → 이어서 포트 훑기 (mDNS 를 안 하는 NAS 도). 같은 주소는 하나로 (이름은 mDNS 것), 주소 순서. 안내 글도 고침

시험: mdns_smb_test 3 (질의 꼴 · 손으로 만든 응답 읽기 - 압축 이름 · SRV · A · 합치기) · smb_find_test - 4 통과, analyze 문제 없음, l10n 빠짐 0.
실제 NAS 의 mDNS 응답은 기기 · 사무실 망에서 확인 필요 (Synology 는 기본으로 _smb._tcp 를 알림).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 01:13:11 +09:00
RyuJongJin 9f94d8ea2c WIP next: FTP 동영상 스트리밍 다리 · 경로 복사 ftp:// (합친 뒤 고침, 관리자 지시) (검토 대기)
- 스트리밍 다리 (remote_stream): ftp:// 의 부분 읽기를 FtpRemote.openReadFrom (REST) 로 - 예전에는 처음부터 읽으며 건너뛰어
  동영상 건너뛰기가 느렸다 (15 가 알린 것). 시험: ftp_test "동영상 스트리밍 다리".
- 경로 복사: FTP 는 ftp://아이디@서버:포트/시작 폴더/경로 (암시 FTPS 990 은 ftps://, 처음 값 포트는 빼고, 비밀번호 없음).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 01:06:38 +09:00
RyuJongJin 8d6f288fab Merge branch 'smb_find' into next
# Conflicts:
#	assets/l10n/en.json
#	assets/l10n/ja.json
#	assets/l10n/zh-Hans.json
2026-10-18 01:03:14 +09:00
RyuJongJin fefe972937 Merge branch 'xplore_p3' into next 2026-10-18 01:02:55 +09:00
RyuJongJin f28af04f77 Merge branch 'ftp' into next
# Conflicts:
#	assets/l10n/en.json
#	assets/l10n/ja.json
#	assets/l10n/zh-Hans.json
2026-10-18 01:02:49 +09:00
RyuJongJin 7e38ad8aec Merge branch 'zip_aes' into next
# Conflicts:
#	assets/l10n/en.json
#	assets/l10n/ja.json
#	assets/l10n/zh-Hans.json
2026-10-18 01:02:39 +09:00
RyuJongJin 49990c4120 Merge branch 'secret_test' into next 2026-10-18 01:02:29 +09:00
RyuJongJin 4bd0226efb Merge branch 'sftp_next' into next 2026-10-18 01:02:23 +09:00
RyuJongJin 12c041eed0 Merge branch 'wifi_89' into next
# Conflicts:
#	assets/l10n/en.json
#	assets/l10n/ja.json
#	assets/l10n/zh-Hans.json
#	assets/licenses/THIRD_PARTY_NOTICES.txt
#	assets/licenses/THIRD_PARTY_NOTICES_ANDROID.txt
#	lib/ui/explorer_page.dart
2026-10-18 01:01:31 +09:00
RyuJongJin 3a1a6391ca WIP X-plore j: [SMB 서버 추가] 창에 [같은 망에서 찾기] (검토 대기)
관리자 지시 (10/18): cd 의 lan_scan.dart (xplore b6e4717) 를 묶음7_88 (88 SMB) 위로 가져와 SMB 추가 창의 [찾기] 에 붙인다.
- lib/core/lan_scan.dart · test/lan_scan_test.dart: b6e4717 의 것 그대로 (cd 작성 - 사설 /24 의 445 포트에 연결만 해 보고 닫음, 64개 동시, Tailscale 은 훑지 않음)
- SMB 추가 · 고치기 창: 서버 주소 아래 [같은 망에서 찾기] → 찾는 창 (무엇을 하는지 안내 · 진행 "n / 254 확인" · [취소]).
  찾은 서버를 누르면 서버 주소 칸에 이름 (역방향 조회, 모르면 주소), 이름 칸이 비었으면 이름 앞부분을 채움. 못 찾으면 직접 넣으라는 안내
- 누를 때만 훑는다 (저절로 훑지 않음 - 내 판단: 망 전체에 연결을 시도하므로 사용자가 고를 때만)
- mDNS (_smb._tcp) 는 아직 (관리자 "더하면 좋음") - 포트 훑기로 찾은 것의 이름은 역방향 DNS

시험: smb_find_test (가짜 찾기 - 실제 망을 훑지 않음: 고르면 주소 · 이름 채움, 이름 모르면 주소, 못 찾음 안내, 추가 창 유지),
lan_scan_test (127.0.0.1 만), smb_test - 7 통과, analyze 문제 없음, l10n 빠짐 0. Android 실제 망에서는 기기 확인 필요.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:59:10 +09:00
RyuJongJin d5e705152a Merge branch 'xplore' into next
# Conflicts:
#	assets/l10n/en.json
#	assets/l10n/ja.json
#	assets/l10n/zh-Hans.json
2026-10-18 00:59:10 +09:00
RyuJongJin fba28cb027 WIP X-plore P3 연결: 길게 누르기 [해시] · [폴더 속성], 빠른 보기 [16진] (검토 대기)
관리자 지시 (10/18): cd 의 도우미 (b6e4717, lib/ui/file_tools.dart) 를 탐색기에 연결.
- 길게 누르기 메뉴 (폴더 크기 계산 옆 - cd 가 넣을 격자 · 글 뷰어 항목과 떨어진 곳):
  · 파일: [해시 (MD5 · SHA-256)] → showFileHash (진행 · 취소, 복사, 붙여 넣은 값과 비교)
  · 폴더: [폴더 속성] → showFolderProperties (파일 · 폴더 수 · 크기 · 링크 · 만든 · 바뀐 때)
- 빠른 보기 (81) 창: [16진] → HexViewerPage (앞부분부터 나눠 읽기)
- 내 판단: 셋 다 이 기기의 파일만 (원격 WebDAV 는 해시 · 16진 · 속성이 파일을 다 받아야 해서 메뉴에서 뺌)

시험: explorer_test 에 연결 시험 (해시 "hello" 의 MD5 · SHA-256 값, sub 의 "파일 1개 · 폴더 0개", 빠른 보기 → 16진 화면) -
explorer · file_tools · quick_look 57 통과, analyze 문제 없음, l10n 빠짐 0. 캡처는 아직 (Windows 캡처 시험은 다음).
j (SMB 찾기) 는 이 브랜치에 88 (SMB) 이 없어 smb_find (묶음7_88 위) 에서 (관리자 지시).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:55:49 +09:00
RyuJongJin 60398d22e8 WIP X-plore P3: 저장 공간 분석에 [칸 그림] (treemap, 87 위) (검토 대기)
- 저장 공간 분석 화면에 세 번째 보기 [칸 그림]: 지금 폴더 안을 크기만큼의 칸으로 (squarified treemap).
  큰 폴더 30개는 따로, 나머지 폴더는 한 칸, 이 폴더 바로 안의 파일도 한 칸 (회색, 누를 수 없음). 칸 = 이름 · 크기 · %.
  폴더 칸을 누르면 그 안으로 (← 로 위로, [그 자리로] 는 예전 그대로). 지우기는 여기서 하지 않음 (87 결정 그대로).
- 시험: storage_chart_test (넓이 비례 · 겹치지 않음 · 누르기).
- 캡처: integration_test/windows_xplore_tools_shot_test.dart → release\test\shots_xplore_tools
  (격자 · 글 뷰어 · 일괄 이름 날짜 · 칸 그림, ko/en × 412/960).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:55:29 +09:00
RyuJongJin e512d75d57 WIP X-plore i: 일괄 이름 바꾸기에 날짜 · 시각 ([D] · [T], 사진은 찍은 때) · 마지막 규칙 기억 (검토 대기)
- [D] = 2026-10-18, [T] = 13-05-09 (Windows 는 ':' 를 못 써서 '-'). 날짜는 고름: 찍은 때 (JPEG Exif DateTimeOriginal →
  DateTimeDigitized → DateTime, 없으면 바뀐 때, 처음 값) / 바뀐 때. Exif 는 파일 앞 256KB 만 읽음 (image 패키지, 새 의존성 없음).
- 찍은 때를 다 읽기 전에는 [바꾸기] 를 누를 수 없음 (미리 보기와 실제가 다르지 않게).
- 마지막에 [바꾸기] 한 규칙을 설정 batchRenameLast 에 기억해 다음에 그 값으로 창이 열림. 틀리거나 없는 값은 처음 값.
- 내 판단 (캡처에서 발견): 412 폭에서 날짜 고르기가 창을 넘어 [D] [T] 옆 남은 폭에 맞춤, 이름 틀 설명은 4줄까지.
- 시험: batch_rename_date_test (토큰 · 기억 · Exif · 창), batch_rename_test 그대로 통과.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:55:28 +09:00
RyuJongJin 78486be27a WIP X-plore g · h: 격자 보기 · 글 뷰어 (검토 대기)
- g: 폴더 길게 누르기 메뉴 [격자로 보기 (그림 · 동영상)] - 썸네일 격자 (크게 · 작게), 누르면 열기 (보기 · 재생),
  칸을 길게 누르면 탐색기에서 그 자리로. WebDAV · SFTP 폴더는 메뉴에 없음 (썸네일을 받아야 해서).
- h: 파일 메뉴 [글 뷰어로 보기] (동영상 · 소리 빼고 어떤 파일이든, 읽기 전용) - 글자 코드 스스로 판별
  (UTF-8 · UTF-16 · CP949 · Shift-JIS · GBK, 위에서 바꿈), 큰 파일은 256KB 씩 [더 읽기], 줄 번호 · 글자 크기 · 전체 복사 · 고르기.
  나눠 읽은 끝에서 두 바이트 글자가 잘려 CP949 풀이가 실패하던 것은 끝 바이트를 다음 조각까지 미뤄 고침.
- 내 판단: 글 파일 (txt · srt · log 등) 을 열 앱이 없을 때만 글 뷰어로 연다 (예전에는 "열 앱이 없습니다").
  기본 앱이 있으면 예전처럼 그 앱으로 (사용자가 보는 동작은 바꾸지 않음).
- 내 판단 (캡처에서 발견): 좁은 화면 (폭 600 미만) 의 글 뷰어는 줄 번호 · 글자 크기를 ⋮ 메뉴로 (412 에서 파일 이름이 다 가려졌다).
- 번역: 이 커밋 뒤의 i · 저장 공간 그림 글도 같이 넣음 (en · ja · zh-Hans).
- 시험: xplore_tools_test (글 뷰어 CP949 나눠 읽기 · 격자), explorer_test "X-plore g · h".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:55:27 +09:00
RyuJongJin dd9e93b4fe WIP l FTP · FTPS: 탐색기 원격 탭 (WebDAV · SFTP 와 같은 길) - dart:io 로 직접, 처음 값 FTPS (검토 대기)
관리자 지시 (10/17, l): FTP · FTPS 를 탐색기 원격 탭으로. 평문 FTP 는 "암호화 안 됨" 경고, 처음 값은 FTPS, 비밀번호는 안전 저장소.
- lib/core/ftp.dart (새, 패키지 없음 - ftpconnect 3.0.0 은 dartssh2 ^3 에 묶여 우리 dartssh2 4.1.0 과 함께 못 씀):
  · 명령 채널 RawSocket → 명시 FTPS 는 AUTH TLS 뒤 RawSecureSocket.secure (같은 연결을 TLS 로), 암시 FTPS (990) 는 연결하자마자 TLS. PBSZ 0 · PROT P
  · 목록 MLSD (FEAT 에 MLST 가 있으면) 아니면 LIST (유닉스 꼴), 정보 · 만들기 · 지우기 (안쪽까지) · 옮기기 (RNFR/RNTO)
  · 받기 · 올리기는 따로 연결 (REST 로 부분 읽기 - 동영상 건너뛰기 · 이어 받기). 자료 연결의 TLS 는 명령에 150/125 를 받은 뒤
  · 자료 연결 주소는 명령 채널의 서버 주소로만 (PASV 가 알려 준 다른 주소로 가지 않음 - FTP 바운스 막기). EPSV 먼저
  · FTPS 로 고른 서버가 평문만 되면 몰래 평문으로 내려가지 않고 이유를 알림
  · 자체 서명 인증서: SFTP 호스트 키처럼 처음 한 번 지문 (SHA-256) 을 보이고 믿을지 묻고 (TOFU), 바뀌면 막음. 공인 인증서는 그대로
  · DNS 강제를 따름 (FtpRegistry.resolveWithDns - SFTP 와 같게)
- vfs: ftp://<서버 id>/<경로> (isFtp · noStreamUrl → 받아서 열기, 표시 📂 · 평문은 ⚠, 임시 폴더 jj_ftp)
- 설정: ftpServers (비밀번호는 ftp.pw.<id> 안전 저장소, 설정 파일 · .bak · 보관본에서 뺌), 마스터 초기화에서 지움
- 화면: 환경 설정 > 파일 탐색기 > FTP · FTPS (추가 · 고치기 · 지우기 · 연결 확인, 암호화 고르기 - 평문은 빨간 경고),
  탐색기 위 줄 탭 (평문은 ⚠ 이름) · [FTP 서버 추가] · 좁은 창 메뉴 · 길게 누르기 고치기 · 인증서 지문 안내 [지문 확인하고 연결]

시험: test/ftp_test.dart 7 - 설정의 비밀번호 (안전 저장소 · 보관본), 목록 줄 읽기, 실제 서버 (tool/ftp_test_server.py = pyftpdlib 2.2.0 ·
pyOpenSSL 26.4.0, 시험용 파이썬에만 - 앱에 안 들어감): 명시 FTPS (인증서 TOFU · 바뀐 인증서 막기 · 폴더 · 쓰기 · 목록 · 부분 읽기 · 옮기기 · 지우기),
암시 FTPS, 평문 FTP (+ FTPS 서버가 평문만이면 이유), 틀린 비밀번호 · 비밀번호 없음 · 닿지 않음.
sftp · master_lock · settings_store · webdav · explorer_webdav · explorer · sftp_explorer · licenses 와 함께 98 통과, analyze 문제 없음, l10n 빠짐 0.
시험 서버 메모: pyftpdlib 2.2.0 + pyOpenSSL 26 은 자료 연결 TLS 닫기에서 끝없이 되풀이해 STOR 가 끝나지 않는다 - 시험 서버에서만 덮어씀.
못 한 것: 실제 NAS (Synology 등) FTPS · TLS 세션 재사용을 요구하는 서버 (vsftpd require_ssl_reuse) 는 확인 못 함 (Dart 는 자료 연결의 TLS 세션 재사용을 못 함),
FTP 스트리밍 재생 (sftp_next 의 다리와 합칠 때 FtpRemote.openReadFrom 을 넣으면 됨), 경로 복사의 ftp:// 꼴.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:45:33 +09:00
RyuJongJin a80d8bcafe WIP X-plore f: 압축 (ZIP · 7z · RAR) 을 트리 안에서 폴더처럼 펼치기 · 읽기 전용 (검토 대기)
- 압축 파일의 › 로 펼치면 안의 폴더 · 파일이 트리에 (목록에 폴더 이름이 없어도 경로에서 폴더를 만든다).
  안의 파일을 열면 그 파일만 임시 폴더 (jj_arcview) 로 꺼내 연다 (보기 · 재생).
- 읽기 전용 (관리자 지시): 압축 안의 이름 바꾸기 · 일괄 이름 · 지우기 · 이동은 막고 알림. 압축 안으로 붙여넣기 · 놓기 · 새 폴더도 막음.
  압축 안의 것을 복사 · 붙여넣기 · 끌어 놓으면 86 의 풀기 창 (위험한 경로 · 링크 검사 · 같은 이름 · 갈 곳) 으로 꺼낸다.
- 비밀번호가 걸린 압축 · 파일은 미리 보지 않고 [압축 풀기] 로 안내 (비밀번호를 명령 줄 · 기록에 남기지 않는 86 흐름 그대로).
- 7z · RAR 은 동봉 7-Zip 이 있을 때만 (없으면 알림). WebDAV · SFTP 위의 압축과 Rsync 화면은 펼치지 않음.
- 내 판단: 압축 안 폴더를 펼쳐 지금 폴더가 압축 안이 되면 [새 폴더] · 붙여넣기를 막는다 (캡처에서 발견).
- 알려진 한계: 펼친 뒤 압축 파일이 바뀌면 다시 펼칠 때까지 예전 목록.
- 시험: explorer_test "X-plore f", 캡처 release\test\shots_xplore_f (archive_ko/en_412/960).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:42:08 +09:00
RyuJongJin b6e47178e3 WIP X-plore j · P3 도우미 (미연결, 15 에게 넘김) (검토 대기)
관리자 지시 (10/18): j (SMB 자동 찾기) 와 P3 의 해시 · 16진 보기 · 폴더 속성은 15 가 탐색기 · SMB 추가 창에 연결한다.
cd 가 만든 도우미와 시험만 넣는다 (탐색기 · 설정에는 아직 연결 안 함).
- lib/core/lan_scan.dart: 사설 IPv4 /24 를 445 포트 연결 시도로 훑기 (보내는 것 없음, 64개 동시, 취소 · 진행), 역방향 이름.
  Tailscale 100.64/10 은 넓어 훑지 않음. test/lan_scan_test.dart (이 기기의 열린 · 닫힌 포트).
- lib/ui/file_tools.dart: MD5 · SHA-256 (조각 읽기 · 취소 · 붙여 넣은 값과 비교), 16진 보기 ([더 읽기]), 폴더 속성 (링크 · junction 은 따라가지 않음).
  test/file_tools_test.dart. 번역 en · ja · zh-Hans.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:28:55 +09:00
RyuJongJin ba34014a1f WIP 회귀 고침: 탐색기 밀기 = 창 바꾸기는 설정으로만, 처음 값 끔 (검토 대기)
c2c4382 에서 X-plore 스타일의 밀기 = 창 바꾸기를 처음 값으로 켜서, 사용자가 10/8 에 정한
"화면 가운데를 밀면 화면 이동" 이 깨졌다 (components_test 실패). 관리자 판단 (10/18) 을 따라 모든 스타일에서 처음 값은 끔.
예전 설정 파일에 값이 없으면 끔. 밀기 시험은 설정을 켠 뒤 돌리고, 처음 값이 끔인지 확인을 더함.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:28:06 +09:00
RyuJongJin f4760cb4d7 WIP X-plore e: Android 앱 휴지통 · 85 진행 패널 시험 흔들림 · X-plore 캡처 시험 (검토 대기)
- e (관리자 지시): Android 는 저장 장치마다 .jj_trash 에 보관 (처음 14일, 설정 0 = 끔 - 예전처럼 바로 영구 삭제).
  지우기 = 휴지통으로 · [되돌리기] 알림 · 휴지통 화면 (되살리기 · 영구 삭제 · 비우기), 켤 때 기한 지난 것 정리.
  휴지통으로 못 옮긴 것은 영구 삭제할지 따로 묻는다 (몰래 영구 삭제하지 않음). Windows 의 [휴지통] 버튼은 시스템 휴지통을 연다.
- 내 판단: 한 창 "갈 곳" 줄은 전체 경로 대신 폴더 이름 (좁은 화면에서 잘림).
- 85 시험 흔들림 (관리자 지시): pumpAndSettle 대신 조건 기다리기 · 칩 onTap 직접 (3번 연속 통과).
- integration_test/windows_xplore_shot_test.dart: X-plore P1 화면 캡처 (ko/en × 412/960).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:28:06 +09:00
RyuJongJin b0ac3c4581 WIP 86 보충: 7z · RAR 의 한글 비밀번호 풀기 - 7-Zip 표준 입력을 UTF-8 로 (검토 대기)
관리자 지시 (10/18): -sccUTF-8 로 표준 입력을 UTF-8 로 보내 보고, 되면 그것을 쓴다.
- 10/18 실제 7z.exe 시험: 7z 형식 (머리까지 암호) 의 한글 비밀번호는 -sccUTF-8 + UTF-8 표준 입력이면 풀림 (exit 0).
  앱의 _run7z 는 -sccUTF-8 을 붙이면서 표준 입력은 Dart 기본 (시스템 코드 페이지 = CP949) 으로 보내 실패하고 있었다 (CP949 + -sccUTF-8 = exit 2 확인).
  → pr.stdin.encoding = utf8. 7z · RAR 의 목록 · 풀기 · 비밀번호 모두 같은 길 (RAR 은 만들 수 없어 실제 RAR 로는 시험 못 함 - 같은 비밀번호 읽기)
- ZIP 만들기는 그대로: 7-Zip 의 ZIP 은 영문 밖 비밀번호를 (표준 입력 · 명령 줄 모두) 받지 않아 archive AES + 512MB 제한 + 경고 (관리자 승인).
  ZIP 풀기는 archive (앞 커밋의 UTF-8 바이트 고침) - 명령 줄 -p 는 쓰지 않음 (프로세스 목록에 보임, 관리자 지시)

시험: archive_ops 14 통과 (새: 한글 비밀번호 7z - 틀린 비밀번호 · 목록 · 풀기. 시험 압축만 명령 줄 -p 로 만듦).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:13:09 +09:00
RyuJongJin 16f33b3b71 WIP zip_aes: 비밀번호 ZIP (AES-256) 만들기 · 한글 비밀번호 ZIP 풀기 고침 (검토 대기)
관리자 지시 (10/17, k): 86 [ZIP 으로 묶기] 창에 "비밀번호" 칸. 비밀번호는 기억 · 기록하지 않음.
- 창: [비밀번호 (선택)] (보기 · 숨기기) → 넣으면 [비밀번호 다시 넣기], 같아야 [암호로 묶기]. 잊으면 못 푼다 · 이름 목록은 암호화 안 됨 안내
- 영문 · 숫자 · 기호 비밀번호: 동봉 7-Zip (Windows 7z.exe · Android lib7zz) 로 -tzip -mem=AES256 -mcu=on, 비밀번호는 표준 입력으로만.
  흘려 가며 암호화해 큰 동영상도 메모리에 다 올리지 않음. 넣을 파일은 앱이 고름 (링크 · junction 빼고, 고른 것 기준 상대 이름)
- 한글 등 영문 밖 글자 비밀번호: Windows 7-Zip 은 받지 않는다 (표준 입력 · 명령 줄 모두 "매개 변수가 틀립니다", 10/17 시험)
  → archive 의 AES-256 (WinZip AE) 으로. 파일을 통째로 메모리에 올리므로 512MB 넘는 파일이 있으면 미리 알리고 묶지 않음 (내 판단).
  창에 "이 앱에서는 풀리지만 7-Zip 등 다른 프로그램에서는 풀리지 않을 수 있음" 안내 (막지는 않음)
- 버그 고침: archive 4.3.0 은 비밀번호를 password.codeUnits 를 바이트로 잘라 써서 한글이 깨졌다 (다른 프로그램과 맞지 않음).
  UTF-8 바이트로 넘기고 (zipPasswordBytes), 풀 때는 UTF-8 로 먼저 · 틀리면 예전 방식으로 한 번 더
- -mcu=on: 7-Zip 이 만든 ZIP 의 한글 이름을 UTF-8 로 (안 하면 앱 · Android 에서 이름이 깨졌다 - 시험에서 찾음)

따로 알릴 것 (이 커밋에서 고치지 않음): 86 의 7z · RAR 풀기도 비밀번호에 한글이 있으면 Windows 7-Zip 이 표준 입력을 받지 않아 실패할 것 (관리자 판단 필요).

시험: archive_ops (영문 비밀번호 → 7-Zip, 한글 비밀번호 → archive: 앱 풀기 · 틀린 비밀번호 · 7-Zip 풀기, 취소 · 실패 · 빈 비밀번호),
archive_ui (두 번 같아야 누름 · 한글 안내 · [취소] 는 아무 일 없음), licenses - 25 통과, analyze 문제 없음, l10n 빠짐 0.
위젯 시험 안에서는 7-Zip 이 가짜 시간 때문에 끝나지 않아 창 동작만 본다 (만들기 · 풀기는 단위 시험). 7z 의 표준 입력 비밀번호는 실제 7z.exe 로 확인.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:09:10 +09:00
RyuJongJin c2c43825d6 WIP X-plore ⚖ 3개 (관리자 결정): 표시 동그라미 늘 보이기 · 밀기 = 창 바꾸기 (X-plore 스타일 처음 값 켬) · X-plore 식 폴더 표시 (설정만) (검토 대기)
관리자 결정 (10/18, 사용자 "네가 판단해서"):
- 표시 동그라미 늘 보이기: X-plore 스타일에서 처음 값 켬 (설정으로 끔). 다른 스타일 · Rsync 는 지금대로 (선택 모드에서만).
  고른 것이 있으면 아래 표시 줄도. 줄 누르기는 그대로 (열기 · 고르기), 동그라미를 누르면 표시.
- 밀기 = 창 바꾸기: X-plore 스타일 탐색기 안에서 처음 값 켬 (설정). 두 창이면 다른 창이 활성, 한 창이면 숨은 창과 바꿔 보임 (알림).
  탐색기 안쪽이 먼저 받으므로 화면 사이 이동은 위 막대에서 밀기 (10/8 결정 유지).
- 폴더 표시 X-plore 식 (1번 폴더 · 2번 안의 파일 전체 · 3번 풀기): 설정으로만, 처음 값은 지금 규칙 (자신 + 안 전체) -
  고르는 범위가 바뀌면 지우기 · 이동 대상이 바뀌는 데이터 문제라 (관리자 결정).
- 시험: X-plore ⚖ 2개 + 예전 시험 2곳 (동그라미가 평소에 안 보임 → X-plore 스타일은 보임, 끄면 예전대로) 고침.
  탐색기 시험 15개 파일 202개 통과. test_temp_check 의 남은 폴더 2개는 같은 때 돌던 다른 세션 시험의 것 (끝난 뒤 사라짐 - 이 시험들은 tearDown 에서 지움).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 23:45:57 +09:00
RyuJongJin 00fac175b9 WIP 시험: 안전 저장소 (DPAPI) 시험이 사용자 secrets.dat 를 건드리지 않게 - 임시 폴더로, 보통 회귀에 넣음 (검토 대기)
관리자 지시 (10/17): integration_test/secret_store_platform_test 는 Windows 에서 사용자의 앱 데이터 폴더
(%APPDATA%\com.jj\jj_mkvmaker\secrets.dat) 에 시험 열쇠를 쓰고 지웠다 (값은 지웠지만 파일을 다시 쓰고, 그동안 다른 실행과 겹칠 수 있음).
- SecretStore.dpapiAt(dir) (@visibleForTesting): DPAPI 저장소를 정한 폴더의 secrets.dat 로. 앱 동작은 그대로 (platform() 은 앱 데이터 폴더)
- test/secret_store_dpapi_test.dart (새, 보통 flutter test 에서 돎 - DPAPI 는 FFI 라 플러그인 없이 됨):
  쓰기 · 읽기 · 바꾸기 · 지우기 · 파일에 평문 없음 · .tmp 안 남음, 130 두 창 번갈아 쓰기, 풀 수 없는 파일은 보관 뒤 새로 시작 (40-5).
  끝날 때마다 사용자 secrets.dat 의 바뀐 때 · 크기가 그대로인지 확인
- integration_test 의 같은 시험: Windows 는 임시 폴더 저장소로, Android 는 기기 Keystore 그대로 (시험 열쇠만 쓰고 지움)

시험: secret_store_dpapi 3 · secret_store · secret_issue 15 통과, analyze 문제 없음, 사용자 secrets.dat 생기지 않음 · jj_secret* 흔적 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 23:43:41 +09:00
RyuJongJin eefcb18ab8 WIP sftp_next: SFTP · SMB 동영상을 받지 않고 스트리밍 - 같은 폴더의 다음 동영상 · 그 자막 (검토 대기)
관리자 지시 (10/17, 88 1차에서 빠진 것): 88 1차는 고른 파일 하나를 임시 폴더로 다 받은 뒤 그것만 틀어
다음 파일로 넘어가지 않았다 (playFiles 가 목록을 [받은 파일] 하나로 줄임).
- lib/core/remote_stream.dart: 이 기기 안 (127.0.0.1, 빈 포트) 의 작은 HTTP 다리. 플레이어 (mpv) 의 Range 요청을
  SFTP 는 SftpFile.read(offset, length), SMB 는 readChunk(offset) 로 그 부분만 읽어 준다 - 건너뛰기 · 다음 파일 모두 받지 않고 바로
- media_kit_full_player._media: SFTP · SMB 는 다리 주소로 (예전: vLocalCopy 로 다 받기)
- playFiles: 같은 폴더 목록을 그대로 넘김 (preparePlayback 은 이미 원격 형제 목록을 만들고 있었음).
  자막은 플레이어가 차례가 올 때마다 그 파일 것을 받는다 (externalSubtitlesFor 의 원격 분기 - 이미 있던 것)
- SftpRemote · SmbRemote 에 openReadFrom (시작 · 길이). openRead 는 그것을 처음부터 부름 (동작 그대로)
- WebDAV 는 바꾸지 않음 (이미 주소 + 인증 헤더로 스트리밍 · 다음 파일 · 자막)

내 판단 (보안): 다리는 127.0.0.1 에만 열고 이 기기에서 온 요청만, 주소에 실행마다 바뀌는 무작위 비밀값
(DNS · Tor 다리와 같은 판단, a4 10/11), 등록한 파일만 번호로 (경로를 주소로 받지 않음), GET · HEAD 만, 주소에 비밀번호 없음.
외부 프로그램으로 여는 것은 그대로 (받아서 넘김).

시험: sftp_stream_test 2 (실제 SFTP 서버 paramiko - 부분 읽기, 같은 폴더 목록, Range 206 · 416 · HEAD,
다음 파일, 틀린 비밀값 · 번호 404, PUT 405, 다음 파일의 자막) · sftp · webdav · player · smb 37 통과, analyze 문제 없음.
SMB 는 Android 플랫폼 채널이라 이 PC 에서 실제 시험 못 함 (기기 확인 필요). 실제 재생 (mpv 가 다리로 여는 것) 도 기기 · Windows 시험판 확인 필요.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 23:35:54 +09:00
RyuJongJin 94ecd7b25e WIP 89 Wi-Fi 공유: 고른 폴더를 같은 Wi-Fi 의 다른 기기 브라우저에서 보기 · 받기 (읽기 전용) (검토 대기)
사용자 결정 (10/9): 기본 꺼짐 · 읽기 전용 · 접속 비밀번호. 관리자 지시 (10/17) 대로:
- 앱 안 작은 HTTP 서버 (dart:io, 새 서버 패키지 없음). 켤 때마다 6자리 코드를 새로 만들고 주소 · QR · 코드를 화면에
- 고른 폴더 밖은 막음: ../ · 역슬래시 · 드라이브 · ':' · 링크 · junction (목록에도 안 보임), 실제 경로가 폴더 안인지 다시 확인
- GET · HEAD 만 (그 밖은 405). 올리기 · 지우기 · 이름 바꾸기 없음
- 저절로 끄기: 설정 10 / 30 (처음 값) / 60 / 120분 / 끄지 않음, [+30분]. 앱을 끄면 함께 꺼짐
- 접속 기록은 화면에만 (파일에 남기지 않음). 작업 기록에는 켜고 끈 것만, 코드 · 주소 없음. 로그인 뒤 주소에서 코드를 뺌
- Android: 켜져 있는 동안 작업 알림 서비스 ("Wi-Fi 공유 켜짐" - 코드 · 주소는 잠금 화면에 보일 수 있어 넣지 않음)
- 탐색기: 폴더 길게 누르기 [Wi-Fi 로 공유] (이 기기의 폴더만) · ⋮ 메뉴 · 버튼 구성, 켜져 있는 동안 위에 "Wi-Fi 공유 중" 줄 ([끄기])

내 판단 (보안):
- 코드를 틀리면 한 기기는 5번에 1분 막고, 모두 30번이면 공유를 끔 (6자리 무작위 대입 막기)
- 사설망 · 링크 로컬 · Tailscale (100.64/10) · 이 기기에서 온 요청만 받음
- 공유한 파일은 격리해 보냄 (CSP sandbox · nosniff, HTML · SVG · JS 는 내려받기로). 앱 화면에도 스크립트 없음 (CSP)
- 쿠키는 HttpOnly · SameSite=Strict, 코드 비교는 시간 차가 없게

라이선스: qr 3.0.2 (BSD-3-Clause, 원래 간접 의존성) 를 직접 의존성으로 - 두 고지 · licenses_test.
시험: wifi_share_test 8 (401 · 429 · 끄기 · 403/404 · 링크 · Range · 격리 · 저절로 끄기 · 사설망), wifi_share_page_test (412 폭),
keep_alive · licenses. analyze 문제 없음, l10n 빠짐 0, test_temp_check: jj_wifi* 흔적 0 (같은 때 cd 의 explorer 시험 폴더가 섞여 보임).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 23:20:24 +09:00
RyuJongJin 23d3da8483 WIP X-plore c: X-plore 스타일 트리 맨 위에 저장 장치 · 서버 · 즐겨찾기 (위 줄 탭 · ★ 는 그대로) (검토 대기)
15 비교표 P1 c. X-plore 는 저장 장치 · 서버 · 즐겨찾기가 한 트리의 맨 위 항목이다.
- X-plore 스타일에서 지금 저장 장치를 그 자리에 펼친 채, 다른 저장 장치 · WebDAV 서버를 접힌 줄로 앞뒤에 (탭 순서대로),
  맨 끝에 "★ 즐겨찾기" (누르면 펼침 · 즐겨찾기를 누르면 그 폴더로). 다른 저장 장치 줄을 누르면 그 저장 장치로.
- 위 줄 탭 · ★ 메뉴는 그대로 (관리자 결정 "함께"). 다른 스타일 · Rsync 화면은 그대로.
- 설정 "저장 장치 · 서버 · 즐겨찾기를 트리 맨 위에" (처음 값 켬, X-plore 스타일에서만 보임).
- 줄 위젯은 따로 (_extraRow) - 파일 줄의 고르기 · 끌기 · 메뉴는 이 줄에 없음. 폴더로 스크롤 (_reveal) 은 실제 트리 줄만.
- 시험: explorer_test "X-plore c" + 탐색기 시험 15개 파일 200개 통과.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 23:15:55 +09:00
RyuJongJin 0ce26e9124 WIP X-plore d: 설정 "펼친 폴더를 다시 누르면 접기" (처음 값 끔 - 189 그대로) (검토 대기)
15 비교표 P1 d. X-plore 는 폴더를 다시 누르면 접는다. 189 (관리자 지시) 는 이름으로 접지 않게 해 두었으므로 설정으로만 넣는다.
켜면 펼쳐 둔 지금 폴더를 다시 누를 때 접고, 두 번 누르기의 두 번째 (짧은 사이) 는 접지 않는다 (목록이 움직이지 않게).
내 판단: 처음 값 끔 (189 와 부딪힘). 시험: explorer_test "X-plore d", 189 시험 그대로 통과.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 23:07:34 +09:00
RyuJongJin f9cd5f98c9 WIP X-plore b: 가운데 버튼 길게 누르기 = 짝 동작 (복사 ↔ 이동, 삭제 → 영구 삭제로 묻기) (검토 대기)
15 비교표 P1 b (X-plore 의 버튼 길게 누르기). 확인 창은 그대로 - 영구 삭제는 처음 고른 칸만 영구로 (Shift+Delete 와 같음).
풍선 도움말에 "길게 누르면 …". Rsync 화면은 없음. 시험: explorer_test "X-plore b".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 23:05:26 +09:00
RyuJongJin 818b4a0615 WIP X-plore a: 한 창일 때 복사 · 이동 갈 곳 - 설정 "숨은 다른 창으로" 면 위에 갈 곳 줄 · 바로 복사 · 이동 (처음 값은 지금대로) (검토 대기)
사용자 "파일 탐색기는 X-plore 와 최대한 같게" (관리자 3a, 15 비교표 P1 a).
- X-plore 는 대상 창이 숨어도 파란 화살표 + 대상 이름을 보이고 그리로 복사 · 이동한다. 우리 한 창은 담아 두었다가 붙여넣기.
- 설정 "한 창일 때 복사 · 이동": 담아 두고 붙여넣기 (처음 값, 지금 그대로) / 숨은 다른 창으로.
  숨은 다른 창으로면 맨 위에 "→ 복사 · 이동 갈 곳: 폴더" 와 [지금 폴더로] · [다른 폴더], [복사] · [이동] 은 그 폴더로 (확인 창 그대로).
- 내 판단: 동작이 바뀌는 것이라 처음 값은 지금대로 (관리자 결정 - 예전과 부딪히는 것은 설정으로).
- 시험: explorer_test "X-plore a".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 23:03:26 +09:00
RyuJongJin e5a115d450 WIP 88 SMB 3/3: Android SMB (smbj 0.15.0, 플랫폼 채널) - 서버 설정 · 탭 · 받아서 열기 · 고지 (미완 - 기기 확인 안 됨)
사용자 결정 88-1 (Windows 는 운영체제 그대로) · 88-4 (SMB3 암호화는 허용 + 안 되면 경고) 대로 Android 만.
- android/.../SmbBridge.kt (채널 jj_mkvmaker/smb, 뒤 스레드): smbj (SMB2 · SMB3, SMB1 없음) · Bouncy Castle 보안 제공자
  (Android 기본에는 NTLM 의 MD4 가 없음) · 암호화 요청 (withEncryptData). 서버마다 연결 하나를 다시 씀, 파일은 손잡이로 1MB 조각.
  오류를 종류로 (auth · unreachable · noShare · smb1 · permission · notFound). R8 keep 규칙 (smbj · mbassador · bouncycastle).
- lib/core/smb.dart: SmbServer (비밀번호는 안전 저장소 smb.pw.<id> 에만) · SmbRemote (RemoteClient) · SmbRegistry.
  저장된 비밀번호 없음은 서버에 묻지 않고 알림, 게스트 (아이디 없음) 허용, info() 로 암호화 여부.
- vfs: smb:// 를 같은 원격 길로 (🗄), 재생 · 다른 앱은 받아서 (noStreamUrl = SFTP · SMB).
- 화면: 환경 설정 > 파일 탐색기 > SMB (Android 만) - 서버 · 포트 · 공유 · 시작 폴더 · 아이디 (비우면 게스트) · 비밀번호 · 도메인,
  [연결 확인] 에서 암호화 안 되면 경고. 탐색기 탭 · ⋮ 메뉴 [SMB 서버 추가] · 탭 길게 누르기 = 고치기 · 폴더 고르는 곳.
  179 바꿈: Android 에서 smb:// · \서버\공유 를 넣으면 "SMB 서버 추가로 넣으면 탭으로" 안내와 [SMB 서버 추가] (넣은 주소로 채움) · [WebDAV 서버 추가].
- 최상 · 마스터 초기화의 저장된 비밀번호 지우기에 SMB 도.
- 라이선스: SMBJ_LICENSES.txt (smbj LICENSE_HEADER · Bouncy Castle · SLF4J · MBassador 원문 + Apache 2.0 전문, smbj 는 NOTICE 파일이 없음),
  라이선스 화면 · Android 고지, 판이 바뀌면 시험이 알림.
- 시험: smb_test (가짜 채널 - 1MB 조각 쓰기 · 읽기 · 목록 · 이름 바꾸기 · 지우기 · 시작 폴더 · 오류 글 · 저장된 비밀번호 없음 · 설정 · 주소 풀기),
  179 시험 고침. 전체 837 통과 · 17 건너뜀, 새로 남은 임시 폴더 0. APK 빌드 (Kotlin 컴파일 · R8) 통과.
- 시험이 잡은 버그: 큰 조각 하나가 오면 1MB 로 나누지 않고 통째로 보냈다 → 나눔.
- 미완: 실제 smbj 연결 (Android 기기 + SMB 서버) 은 확인하지 못함 - 노트 10+ 등에서 PC 공유 폴더로 확인 필요.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:55:59 +09:00
RyuJongJin d00df74257 WIP 최상 · 마스터 초기화 "저장된 비밀번호 · API 키" 가 AI 서비스 API 키도 지우게 (버그, 관리자 결정) (검토 대기)
관리자 결정 (10/17): 문구는 "비밀번호 · API 키" 인데 AI 서비스 API 키 (ai.*) 는 남았다 (예전부터) → 함께 지운다.
SFTP 비밀 값 (비밀번호 · 개인 키 · 키 암호) 은 88 1/3 에서 이미 넣음. 목록 · 아이디 · 주소 · 믿은 서버 키는 남김.
시험: 실제 SettingsStore + 메모리 안전 저장소로 os. · dav. · sftp. · ai. 값 7개를 넣고 지운 뒤 하나도 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:32:14 +09:00
RyuJongJin 9a786f3496 WIP 88 SFTP 2/3: 화면 - 환경 설정 SFTP 서버 · 탐색기 탭 · 처음 키 확인 · 고르는 곳 · 라이선스 고지 (미완)
- 환경 설정 > 파일 탐색기 > SFTP (lib/ui/sftp_settings.dart): 추가 · 고치기 · 지우기 (쓰는 곳 알림), 서버 주소 · 포트 · 아이디 ·
  비밀번호 · 개인 키 파일 고르기 (내용만 안전 저장소에, 원래 경로는 저장하지 않음) · 키 암호 · 시작 폴더 · 오래된 ssh-rsa 허용,
  [연결 확인] 에서 처음 보는 키는 지문을 보이고 [믿고 연결], 믿은 키 보기 · [믿은 키 지우기] (마스터 비밀번호 뒤).
- 파일 탐색기 · Rsync 화면: SFTP 서버 탭 (🔑), 탭 길게 누르기 = 고치기, 끝 버튼에 [SFTP 서버 추가] (좁으면 ⋮ 메뉴에).
  처음 연결: 목록 대신 "처음 연결하는 서버입니다 · 지문" 안내와 [지문 확인하고 연결] → 확인 창 [믿고 연결] → 지문을 설정에 저장하고 읽음.
  바뀐 키: 막고 예전 · 지금 지문, [서버 설정 고치기]. 경로 복사는 sftp://아이디@서버:포트/경로 (비밀번호 없음).
- 실시간 동기화 · 모니터링의 폴더 고르기에 SFTP 서버도. 화면 경로 "🔑 서버 › 폴더".
- WebDAV 에만 맞던 글 6개를 "WebDAV · SFTP" 로 (풀기 · ZIP · 분석 · MKV 목록 · Rsync 안내 · 빠른 보기). 탐색기 ⋮ 이름 "경로 입력 · 네트워크 서버".
- 파일 고르기: 확장자 목록이 비면 모든 파일 (확장자가 없는 개인 키 파일 - Windows · Android).
- 라이선스: dartssh2 4.1.0 (MIT) 를 두 고지에 (pointycastle · pinenacl · asn1lib 원문은 패키지 라이선스 화면), 판이 바뀌면 시험이 알림.
- 번역 en · ja · zh-Hans (빠진 번역 0).
- 시험: sftp_explorer_test (시험 서버로 처음 연결 → 지문 → 믿고 연결 → 목록 · 지문 저장), test/support/sftp_server.dart 로 서버 띄우기 공용,
  글이 바뀐 시험 3곳 고침. 전체: 이 4곳 말고 통과, 새로 남은 임시 폴더 0 (move_settled 1번은 powershell 실행 "액세스 거부" - 다시 돌려 통과).
- 내 판단: 끝 버튼 줄에 [SFTP 서버 추가] 아이콘을 하나 더 (WebDAV 와 따로 바로 보이게).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:28:35 +09:00
RyuJongJin b5918b7aa2 WIP 88 SFTP 1/3: 핵심 (dartssh2) - sftp:// 경로 · 서버 설정 · 안전 저장소 · 처음 키 확인 (TOFU) · 오래된 ssh-rsa · 시험 서버 (미완)
사용자 "진행" (10/17, 관리자 3a 전달) - 88 설계안의 제안값:
1) Windows SMB 는 운영체제 그대로 2) 키 암호는 안전 저장소 3) ssh-rsa 기본 끔 · 서버마다 켬 4) SMB3 암호화 (SMB 때) 5) 서버 rsync 는 다음 판.

이 커밋 (화면은 아직 없음 - 설정 화면 · 탐색기 탭은 다음 커밋):
- lib/core/remote_fs.dart: 원격 저장소 공통 동작 (RemoteClient). WebDAV (DavClient) 도 이것으로.
- lib/core/sftp.dart: SftpServer (설정, 비밀 값은 toJson 에 없음) · SftpRemote (dartssh2, 서버마다 연결 하나를 다시 씀) · SftpRegistry.
  - 호스트 키: 믿은 지문이 없으면 연결하지 않고 지문 (SHA256:…) 을 알림 (SftpHostKeyException) → 화면이 묻고 저장.
    바뀌면 막고 예전 · 지금 지문을 알림 (중간자 공격 안내).
  - 비밀번호 · keyboard-interactive · 개인 키 (암호), 키 암호 틀림 · 키를 못 읽음 · 저장된 비밀번호 없음 · 닿지 않음 · 권한 · 없는 경로를 사람 말로.
  - 오래된 서버: 처음엔 SHA-1 을 쓰지 않고 "오래된 ssh-rsa 허용" 을 안내, 서버마다 켜면 ssh-rsa · SHA-1 키 교환.
  - DNS 강제를 따라 직접 연결 (집 안 이름 · 주소는 기기 DNS), 시작 폴더 (비우면 홈).
  - 소켓 끊김의 쓰기 실패가 처리되지 않은 오류로 올라오지 않게.
- vfs: sftp:// 를 dav:// 와 같은 원격 길로 (isDav = 원격, isWebDav · isSftp 따로). 화면 "🔑 서버/경로", 받아서 열기는 jj_sftp.
- 설정: sftpServers (비밀번호 · 개인 키 · 키 암호는 안전 저장소 sftp.pw/key/kp.<id> 에만, 평문 파일에 쓰지 않음), .bak · 깨진 파일 가림,
  최상 비밀번호 초기화의 "저장된 비밀번호 지우기" 에 SFTP 도.
- 재생 · 다른 앱: SFTP 는 스트리밍 주소가 없어 고른 파일만 받아서 (진행 · 취소) 틀고 같은 폴더 자막도 받음 (다음 파일로 넘어가지 않음 - 1차).
- 시험: test/sftp_test.dart 7개 (설정 · 경로 + paramiko 시험 서버: TOFU · 바뀐 키 · 틀린 비밀번호 · 닿지 않음 · vfs 전체 · 키 로그인 · ssh-rsa).
  tool/sftp_test_server.py (paramiko 3.5.1 - 5.0 은 SHA-1 을 뺌), venv 는 ..\tools\sftp_test_py (없으면 건너뜀).
- 라이선스 고지 · 화면 · Android SMB 는 다음 커밋.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:11:07 +09:00
RyuJongJin 13c8e5f5bf WIP Windows 시험판 판 번호: 시험판은 시험 번호 (--build-number 년월일+순번) · make_zip 에 검사 (관리자 지시) (검토 대기)
관리자 지시 (10/17): 시험판 972 의 Windows 가 FileVersion 2026.10.12+1 이라 정식 판 v2026.10.12_001 과 똑같이 보였다
(버전 기록 · 되살리기 창이 헷갈림 - 예전에 겪은 문제). Android 는 versionCode 261012972 라 v2026.10.12_972.
- tool/check_windows_build.ps1: Lib\jj_mkvmaker.exe 의 ProductVersion 을 읽어 시험판인데 번호 < 1000 (정식 번호) 이면 실패,
  -Release 인데 시험 번호면 실패 (check_apk 와 같은 꼴).
- tool/make_zip.ps1: 묶기 전에 위 검사. 정식 판 zip 은 이제 -Release 를 붙인다.
- 시험판 빌드: flutter build windows --release --build-number 261012972 (APK versionCode 와 같은 번호).
  release\test\b6_windows_05074ec_972 를 이렇게 다시 빌드함 (예전 폴더는 그대로).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:06:48 +09:00
RyuJongJin 05074ec4bd WIP DNS: 너무 긴 이름은 이름 탓으로 - "닿지 않음" 안내 · 작업 기록에 사이트 이름이 남지 않게 (15 지적) (검토 대기)
15 지적 · 관리자 10/17: "주소 이름이 너무 깁니다: <이름>" 이 작업 기록으로 가 연 사이트 이름이 남았다.
원인 (버그): 서버마다 묻다 실패한 끝에 "DNS 서버 (…) 에 닿지 않습니다: 주소 이름이 너무 깁니다: <이름>" (닿지 않음) 으로
바뀌어 기록되고, DNS 서버에 닿지 않는다는 안내까지 떴다 (DoH 도 같음).
고침: 묻기 전에 이름을 보고 이름 문제는 그대로 알린다 (닿지 않음 아님 → 기록 안 함 · 안내 안 띄움).
화면 (그 다운로드 줄) 에는 지금처럼 "주소 이름이 너무 깁니다: <이름>".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:00:19 +09:00
RyuJongJin cd50edd666 WIP Windows 통합 시험 회귀 (지시 10): 바뀐 동작 · 바깥 주소에 맞게 시험 3곳 고침 (검토 대기)
ab4d935 에서 integration_test 전부를 하나씩 -d windows 로 돌려 찾은 것 (앱 동작은 바꾸지 않음):
- browser_real: 137 뒤 [다운로드] 는 동영상을 찾은 페이지만 꽉 찬 버튼 (못 찾으면 테두리만) - 시험이 꽉 찬 버튼만 찾다 실패.
- tor_dns_real: 파일 링크 시험 주소 (dist.torproject.org 15.0.24 .asc) 가 404 (예전 판을 지움) → 오래 남는 archive 의 같은 파일.
- windows_check_shot 37: 서버만 (\NAS) · 없는 폴더는 창을 닫지 않고 칸 아래 알림 (146 · 37-2) - 시험이 창을 닫지 않아
  다음 입력 · 다음 크기에서 남은 창이 가렸다 → [취소] 로 닫고 다음으로.
(그 밖의 실패 6개는 회귀를 돌린 내 스크립트가 캡처 폴더를 미리 만들지 않은 탓 - 만든 뒤 모두 통과)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:00:19 +09:00
RyuJongJin ab4d935fca WIP 관리자 결정: 거절 기록은 방식 주소:포트만 · DoH 주소는 호스트만 · 시험 임시 폴더 흔적 0 + 검사 (검토 대기)
관리자 결정 (10/17, 개인정보):
- DNS 프록시 거절 기록: 요청 줄 대신 "방식 주소:포트" 만 (경로 · 질의 (? 뒤) 는 남기지 않음, requestLineForLog).
- DoH 주소: 작업 기록 · app.log · 안내 · 다운로드 실패 이유에는 호스트만 ("https://dns.nextdns.io/…", DnsConfig.label · hideUrlPaths).
  DoH 오류 원문 안의 주소도 가림. 설정 화면은 그대로.

시험 임시 폴더 (%TEMP%\jj_*) - 전체 시험마다 8개가 남았다:
- 앱 버그 (버그 수정): AI 해상도 올리기 upscale4x 가 실패 · 취소하면 jj_up_ 임시 폴더를 지우지 않음 → 지움.
  품질 올리기 (묶음6 내 코드) 는 4배 결과 폴더를 지우지 않음 → 저장했든 못 했든 지움. 사용자 기기의 임시 폴더에도 남던 것.
- 시험: 책갈피 저장이 뒤에서 늦게 끝나 폴더를 다시 만들던 3곳 (jj_bmf_ · jj_bm_ · jj_nav_) 은 파일 끝에 기다렸다 지움
  (test/support/temp_dirs.dart). ai_upscale_test 는 받은 결과 폴더를 지움.
- 검사: tool/test_temp_check.ps1 - 전체 시험 앞뒤로 %TEMP% 를 비교해 시험 소스의 jj… 이름으로 새로 남은 것이 있으면 실패.
  (시험 파일들이 동시에 돌아 파일마다 스스로 확인할 수 없어 전체 실행 앞뒤로 본다.) 이미 있던 폴더는 건드리지 않음.
전체 822 통과 · 17 건너뜀, 새로 남은 임시 폴더 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:53:56 +09:00
RyuJongJin a223a10378 WIP Windows 통합 시험 (지시 9): 82 끌어 놓기 · 75 마우스 4 · 5 번을 실제 빌드에서 가짜 포인터로 (검토 대기)
integration_test/windows_b6_input_test.dart (flutter test ... -d windows, 실제 마우스 · 키보드는 쓰지 않음):
- 82: 6px 는 끌기 아님 (끄는 그림 · 묻기 없음), 12px 부터 끌기. 같은 창 위 = 빨간 테두리, 다른 창의 폴더 줄 = 그 줄 강조,
  다른 창의 파일 줄 = 창 전체 강조. 놓으면 묻기 창 (처음 고른 것 [복사]) → [이동] → 확인 → [되돌리기] 로 원래 자리.
- 75: 마우스가 올라간 창의 4 번 (뒤로) · 5 번 (앞으로), 다른 창이 활성이어도 그 창만. Alt+← / → 는 활성 창. 왼쪽 단추는 이동 아님.
두 번 돌려 모두 통과.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:33:06 +09:00
RyuJongJin a6b3f13e62 WIP 릴리스 전 점검 (지시 8): DNS 실패 기록에서 사이트 이름 뺌 · archive 고지 · 큰 글자 넘침 3곳 (검토 대기)
- 개인정보: DNS 강제 프록시의 "DNS 실패 (사이트): 까닭" 이 연 사이트 이름을 app.log 에 남겼다 → 까닭 (닿지 않은 DNS 서버) 만.
- 라이선스: ZIP 풀기 · 묶기의 archive 4.3.0 (MIT, Brendan Duncan) 을 두 고지 (Windows · Android) 에 이름으로. 판이 바뀌면 시험이 알림.
- 접근성 (253 시험을 묶음6 새 창으로 넓힘, 412×900 · 900×412 · 글자 1.6배, 누르는 것이 화면 폭 안인지도 봄):
  · 78 이름 일괄 창: 대소문자 고르기가 가장 긴 항목 폭이라 창을 넘음 → 한 줄 전체 (isExpanded). 낮은 화면에서 아래가 넘침 → 창 안 스크롤.
    "대소문자 무시" 글은 줄 바꿈.
  · 86 풀기 확인 창: "같은 이름이 있으면 [고르기]" 가 화면 밖으로 밀림 (넘침 오류 없이 누를 수 없었다) → 자리가 없으면 다음 줄.
  · 85 작은 표시: "복사 · 이동 멈춤 - 눌러서 보기" 가 412 를 넘음 → 두 줄까지.
버그 수정 (사용자 동작은 배치만 바뀜). 시험: 253 의 settle 이 시험 시계를 앞으로 돌리게 (열리는 움직임이 끝나야 넘침이 보임).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:18:14 +09:00
RyuJongJin 0f3133cc68 WIP 15 캡처 지적 ①~④: 영어 단수 · 좁은 화면 풀 곳 세로 목록 · 찾기/분석 줄은 시작한 폴더 기준 짧은 경로 · 캡처 DEBUG 띠 끔 (검토 대기)
관리자 지시 (15 캡처 지적, 버그 수정):
① 영어 "1 problems" · "1 names": englishSingular (61) 에 problems · names · paths 를 더하고,
   수와 명사 사이 꾸미는 말 하나도 넘게 ("1 incomplete files" → "1 incomplete file"). 78 영어 글 "until fixed".
② 86 풀 곳: 폭 720 미만이면 칩 대신 세로 라디오 목록 (글이 다 보임). ZIP 묶기의 넣을 곳도 같은 도우미.
③ 79 찾기 결과 · 87 큰 파일 줄: 시작한 폴더 이름부터의 상대 경로 (shortUnder), 길면 가운데 줄임 (앞 … 마지막 두 단계),
   전체 경로는 마우스를 올리거나 그 줄을 길게 누르면 (Tooltip).
④ 캡처 시험 debugShowCheckedModeBanner: false.

내 판단: ① 은 개수별 글을 따로 두지 않고 기존 61 의 단수 처리를 넓힘 (같은 꼴의 다른 글도 함께 고쳐짐).
② 를 ZIP 묶기 창에도 적용 (같은 모양).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:57:25 +09:00
RyuJongJin bb95be3589 WIP 78 이름 일괄 창: 이름 틀 설명을 3줄까지 (폭 412 일본어에서 예가 잘림) (검토 대기)
화면 캡처 (shots_b6_dc8a5f0) 에서 찾음. 버그 수정.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:42:31 +09:00
RyuJongJin dc8a5f0b56 WIP 화면 캡처에서 찾은 것: 85 멈춤 표시는 경고 아이콘 · 78 이름 일괄 창의 칸 이름을 짧게 (폭 412 일본어 잘림) (검토 대기)
- 85: 멈춘 (실패한) 작은 표시가 꽉 찬 진행 고리라 "끝났다" 로 보였다 → 빨간 경고 아이콘.
- 78: 폭 412 · 일본어에서 "名前の形式 (拡張子は…" · "置き換える文字 (空なら…" 로 칸 이름이 잘렸다 →
  이름은 "이름 틀" · "바꿀 글" 로 짧게, "확장자는 그대로" 는 아래 설명 줄 (2줄까지), "비우면 지움" 은 칸 안 안내로.
- 내 판단 (화면 캡처를 보고 고침, 동작은 같음).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:39:00 +09:00
RyuJongJin 3b6d50f409 WIP 시험: 묶음6 화면 캡처 (Windows 통합 시험, 412 · 960 × ko · en · ja) + 85 작은 표시를 TransferProgressChip 으로 (검토 대기)
- 관리자 지시 5: 기기 없이 15 가 볼 수 있게 integration_test/windows_b6_shot_test.dart (JJ_SHOT_DIR 에 PNG):
  78 이름 일괄 창 (겹침 빨간 줄) · 79 찾기 결과 (고른 줄 · 아래 할 일 줄) · 81 빠른 보기 (자막 글) · 82 놓기 묻기 창 ·
  85 작은 표시 (보통 · 멈춤) · 86 풀기 확인 창 (밖으로 나가는 경로 · 링크 · 쓸 수 없는 이름 · 덮어쓰기 경고) · 87 분석 (큰 폴더 · 큰 파일).
  시험 파일은 임시 폴더에만 (끝나면 지움), 설정은 메모리에만.
- 85 의 작은 표시를 공개 위젯 TransferProgressChip 으로 뺐다 (모양 · 동작 같음) - 멈춤 상태를 실제 실패 없이 캡처하려고.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:19:46 +09:00
RyuJongJin e0466d6168 WIP 86 Android: 7-Zip 25.01 을 공식 소스에서 NDK 로 빌드 (lib7zz.so) - Android 에서도 7z · RAR 풀기 (검토 대기)
- 관리자 지시 4: 7z2501-src.7z (SHA-256 2aed39b8…0181 고정, 고치지 않음) 의 CPP/7zip/Bundles/Alone2 (7zz) 를 NDK clang
  (aarch64-linux-android24) 으로 빌드 - tool/build_7zz_android.sh. 어셈블리 끔, Windows 에서 빌드할 때 makefile 이 MinGW 로
  여기지 않게 SystemDrive · SYSTEMDRIVE 를 비움, 새 clang 경고로 실패하지 않게 경고 묶음 끔. libc++ 는 정적 (libc · libm · libdl 만 씀).
  결과 third_party/7zip/android/arm64-v8a/lib7zz.so (2.8MB) + License.txt.
- APK: jniLibs 에 third_party/7zip/android, third_party_libs.txt · check_apk 목록에 lib7zz.so (check_apk 통과 - 실행 파일 5개).
- 앱: bundledSevenZip 이 Android 에서는 네이티브 라이브러리 폴더의 lib7zz.so (rsync 와 같은 방식). 있으면 "다른 앱으로 열기" 안내
  대신 실제 풀기 - 같은 검사 (zip slip · 링크 · 이름 · 공간 · 비밀번호 표준 입력) 그대로. 없으면 예전처럼 안내.
- 고지: THIRD_PARTY_NOTICES_ANDROID 에 7-Zip 25.01 (LGPL 2.1+ · BSD · unRAR 제한, 소스 주소 · 빌드 방법 · 대응 소스 = 태그).
- 시험: licenses_test (Android 고지 · 빌드 스크립트 해시 · APK 목록). Android 기기에서 실제로 푸는 것은 15 의 기기 확인 필요.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:17:01 +09:00
RyuJongJin 47144981d0 WIP 87 저장 공간 분석: 고른 폴더 · 드라이브의 큰 폴더 · 큰 파일 차례, 누르면 탐색기에서 그 자리로 (검토 대기)
- 관리자 지시: 고른 폴더나 드라이브의 큰 폴더 · 큰 파일 차례. 80 의 폴더 크기 규칙 (링크 · junction 은 따라가지 않고 수만,
  이미 센 폴더 (실제 경로) 는 다시 세지 않음, 취소) 을 그대로.
- 여는 곳: 폴더 길게 누르기 [저장 공간 분석], ⋮ 메뉴 [저장 공간 분석] (지금 폴더 - 드라이브 맨 위면 드라이브 전체).
- 한 번 훑으며 폴더마다 크기 (안쪽 합) · 파일 수와 큰 파일 200개를 모은다 → 폴더를 누르면 그 안으로 바로 (다시 읽지 않음),
  [위 폴더] · ← 로 나옴. "큰 폴더" (막대 = 지금 폴더 안 비율) · "큰 파일" (지금 폴더 아래).
  읽지 못한 폴더 수 · 세지 않은 링크 수를 알린다. 읽는 동안 폴더 수 · 크기, [취소] (화면을 닫아도 멈춤).
- 누르면 (파일) · [그 자리로] (폴더) 탐색기에서 그 자리로 - 지우기는 탐색기의 지우기 (확인 · 휴지통) 로만 (관리자 판단).
- WebDAV 는 직접 시킬 때만 (이 메뉴) 하고, 먼저 "서버에서 모두 읽어 오래 걸릴 수 있습니다" 를 묻는다.
- 시험: storage_scan_test (합 · 차례 · junction (위 폴더 고리 포함) 을 세지 않음 · 취소 · 화면 들어가기 · 큰 파일 · 돌려주는 경로),
  explorer_test 87 (길게 누르기 → 큰 파일 → 탐색기 그 자리).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:55:42 +09:00
RyuJongJin c440b688ea WIP 86 보충 (관리자 · 15): 덮어쓰기에도 받는 쪽이 더 새것이면 그대로 · Windows 에서 쓸 수 없는 이름은 미리 건너뜀 · 릴리스에 7-Zip 꼭 (검토 대기)
- [덮어쓰기] 를 골라도 설정 "받는 쪽이 더 새것이면 건너뛰기" (229, 처음 켜짐) 를 따른다: 이미 있는 파일이 압축 안의 것보다
  2초 넘게 새것이면 덮지 않는다 (ZIP 은 DOS 시각, 7z 는 목록의 Modified 로 견줘 목록 파일에서 뺌). 끝 알림 "n개는 더 새 파일이라 그대로 둠".
  확인 창에 [덮어쓰기] 를 고르면 "덮어쓴 파일은 되돌릴 수 없습니다" (휴지통에 남지 않음).
- 압축 안 이름이 이 기기에서 쓸 수 없으면 (Windows: CON · NUL · ? * " < > | · 끝 점 · 빈칸 - 78 의 nameProblem) 풀기 전에 걸러
  확인 창에 "풀 수 없는 이름 n개 (건너뜀)" - 나머지는 그대로 푼다 (풀다 실패하거나 7-Zip 이 다른 이름으로 바꾸지 않게).
  Android 는 지금처럼.
- 릴리스: make_zip.ps1 이 Lib\7zip\7z.exe · 7z.dll · License.txt 가 없으면 실패. CMake 는 없으면 경고 (개발 빌드는 그대로).
- (번역 파일에는 다음 커밋 87 의 글도 함께 들어 있다 - 쓰이지 않는 열쇠)
- 시험: archive_ops_test (쓸 수 없는 이름 · ZIP 과 실제 7z 의 받는 쪽이 더 새 파일).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:47:39 +09:00
RyuJongJin 219510fec0 WIP 86 압축: ZIP 만들기 · 풀기, 7z · RAR 풀기 (Windows - 7-Zip 25.01 동봉), 풀기 전 검사 + 품질 올리기 다듬기 정도 설정 (검토 대기)
86 (관리자 지시 · 승인):
- ZIP 만들기 (archive 4.3.0, MIT): 길게 누르기 [ZIP 으로 묶기] (표시한 것 모두 · 바깥 것만) - 이름 · 압축 정도
  (빠르게 1 · 보통 6 (처음 값) · 작게 9, 설정 zipLevel 로 기억) · 넣을 곳 (지금 폴더 · 다른 창). 링크 · junction 은 넣지 않음.
  .jjpart 에 쓰고 다 되면 이름 바꾸기 - 취소 · 실패면 만들다 만 ZIP 이 남지 않음. 같은 이름이면 "이름 (2).zip".
- 풀기: 길게 누르기 [압축 풀기] (ZIP · 7z · RAR), 7z · RAR 은 열면 풀기, 보기의 ZIP 화면 [풀기] 도 같은 흐름.
  풀 곳 (압축 옆 새 폴더 · 압축이 있는 폴더 · 다른 창), 같은 이름 (이름 바꾸기 · 건너뛰기 · 덮어쓰기 - 처음은 복사 설정대로), 진행 창 · 취소.
- 7z · RAR: 7-Zip 25.01 (7z.exe · 7z.dll) 을 실행 파일 옆 7zip\ 에 동봉 (tool/fetch_7zip.ps1 - 공식 MSI, SHA-256 고정,
  관리자 설치로 풀기만). RAR 은 풀기만 (unRAR 라이선스) - RAR 만들기 선택지 없음.
  Android 는 아직 (lib7zz.so 빌드는 마감 뒤로) - 막지 않고 "다른 앱으로 열기" 안내.
- 보안 (관리자 판단) - 셋 다 목록을 먼저 읽고 검사:
  · 밖으로 나가는 경로 (../ · 절대 경로 · 드라이브 · UNC · ':') 는 막고 확인 창 · 끝 알림에 (zip slip).
  · 압축 안의 심볼릭 링크 · junction 은 만들지 않고 건너뛰고 알림.
  · 풀린 크기가 대상의 빈 공간보다 크면 풀기 전에 알리고 묻는다 ([그래도 풀기]).
  · 비밀번호: 보이지 않는 칸으로 묻고, 7-Zip 에는 명령 줄이 아니라 표준 입력으로만 (작업 기록 · app.log 에 없음).
    ZIP 은 가운데 목록에서 암호 표시를 읽어, 비밀번호 없이 암호 글자를 그대로 쓰지 않는다. 틀리면 다시 묻는다.
- 라이선스: assets/licenses/7ZIP_LICENSE.txt (원문 - LGPL 2.1+ · BSD 3/2-clause · unRAR 제한), 라이선스 화면 (7-Zip · LGPL),
  THIRD_PARTY_NOTICES (판 · 소스 7z2501-src.7z · RAR 은 만들지 않음), 설치 폴더 7zip\ 에 License.txt · LGPL-2.1.txt.
- 내 판단: 진행은 탐색기 진행 패널이 아니라 진행 창 (취소) - 진행 패널은 복사 · 이동 (TransferJob) 전용이라.
보충 (관리자 지시): [품질 올리기] 다듬기 정도 설정 - 약하게 0.15 / 보통 0.25 (처음 값) / 강하게 0.4. fetch 스크립트에 받은 주소 · 판 · 날짜.
- 시험: archive_ops_test (막는 이름 · 계획 · ZIP 만들기 · 풀기 · 같은 이름 · zip slip · 비밀번호 · 취소, 실제 7-Zip 목록 · 풀기 ·
  표준 입력 비밀번호 · 머리 암호 · 틀린 비밀번호), archive_ui_test (확인 창 · 취소 · 다른 앱 안내 · 묶기), licenses_test (7-Zip),
  ai_quality_test (다듬기 정도).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:38:26 +09:00
RyuJongJin eee38aa2fd WIP AI 해상도 올리기: 배율 "1배 (선명하게)" · 보기 화면 [품질 올리기] (그림 → 그림 약하게 + 해상도 올리기, 처음 숨김) (검토 대기)
관리자 판단 (10/17): 답이 없는 사용자 질문이라 둘 다 넣고 기본 꺼짐.
(a) 환경 설정 > AI 해상도 올리기 > 배율에 "1배 (선명하게)" - 크기는 그대로, 모델로 선명하게만 (4배 결과를 원본 크기로).
    처음 값은 지금 그대로 2배. 이름은 "이름_x1". 확인 창 글도 "크기는 그대로 두고 선명하게".
(b) 보기 화면 [품질 올리기 (이 장)] - 설정 "품질 올리기 단추 보이기" (처음 값 끔) 를 켰을 때만.
    1/2 AI 그림 (그림 → 그림, 바꾸는 정도 0.25 · 원본 비율 · 긴 쪽 1024 까지 · 64 배수) 으로 다듬고
    2/2 해상도 올리기 (설정의 배율 - 원본 크기 기준). 새 파일 "이름_q" (덮어쓰지 않음, 원본 그대로).
    AI 그림 모델이 없으면 "환경 설정 > AI 그림에서 받으라" 는 안내. 진행 창 · 취소, 작업 (AiJobs) 으로 작업 알림에.
  - 내 판단: 바꾸는 정도 0.25 · 다듬기 글 (high quality, detailed …) 은 고정 (설정 없음).
- 시험: ai_quality_test (설정 · 1배 크기 · "_q" 이름 · 그림 → 그림 크기 · 모델 없음 안내).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:05:26 +09:00
RyuJongJin 863b5e2ea0 WIP 82 보충 (15 지적): 같은 이름을 덮어쓴 파일은 "덮어쓴 파일 n개는 되돌리지 않습니다" (폴더라고 하지 않게) + 78 시험 흔들림 (검토 대기)
- 끌어 놓기 이동에서 같은 이름을 덮어쓴 파일이 "합친 폴더 n개는 무엇을 옮겼는지 몰라…" 로 나왔다. 파일이면 따로 센다.
  되돌리지 않는 까닭: 원래 파일은 이미 덮어써져 없고, 되돌리면 대상에 아무것도 남지 않는다. 되돌릴 것이 하나도 없으면
  [되돌리기] 알림을 띄우지 않고 끝난 알림 ([폴더 열기]) 그대로.
- 78 시험: 알림은 목록을 다시 읽은 뒤에 뜨므로 파일이 보일 때가 아니라 알림까지 기다린다 (전체 시험 중 한 번 흔들림).
- 시험: explorer_test 82 (새 파일 하나 + 덮어쓴 파일 하나 → "덮어쓴 파일 1개", "합친 폴더" 없음).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:50:22 +09:00
RyuJongJin 53a1b47a2c WIP 85 파일 탐색기 진행 패널 숨기기 · 작은 표시 · Android 알림 진행률 · 끝나면 [폴더 열기] (검토 대기)
- 관리자 지시 (F14): 복사 진행 패널을 숨길 수 있게 (Android 는 알림줄에 진행), 끝나면 [폴더 열기].
- 패널 오른쪽 위 [숨기기] → 오른쪽 아래 작은 표시 "복사 · 이동 n개 · 45%" (누르면 다시 펼침). 숨김은 설정으로 기억 (처음 값: 보임).
  관리자 판단: 숨겨도 묻는 창 (확인 · 같은 이름) 과 실패 알림은 늘 뜬다 (패널과 따로). 멈춘 (실패한) 것이 있으면 작은 표시가
  빨갛게 "복사 · 이동 멈춤 - 눌러서 보기".
- Android 작업 알림: "복사 · 이동 n개 · 45%" (CopyCenter.activeProgress), 다운로드가 없으면 진행 막대도 복사 · 이동 진행률.
- 끝난 알림에 [폴더 열기] - 앱 탐색기에서 넣은 폴더로 (그 폴더를 보는 창, 없으면 지금 창).
- 시험: keep_alive_test (복사 진행률 글 · 막대 · 다운로드가 먼저), explorer_test 85 ([폴더 열기] · [숨기기] → 작은 표시 → 다시).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:36:59 +09:00
RyuJongJin 5cdf57d7ff WIP 시험 흔들림: batch_test 239 (취소 창 · 지금 것만 취소) - 정해진 횟수 대신 될 때까지 기다리기 (검토 대기)
- 관리자 지적: 전체 시험 중 한 번 실패 (따로 돌리면 통과). 실제 FFmpeg 인코딩을 4초 · 20초 안에 기다리던 것이
  PC 가 바쁠 때 넘쳤다. 조건이 될 때까지 (최대 90초) 기다리고, 시험 시간 한도를 4분으로.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:19:23 +09:00
RyuJongJin 58380e4ae0 WIP 81 보충 (15 지적 · 관리자 판단): 빠른 보기의 글 미리 보기가 CP949 · UTF-16 · UTF-8 BOM 도 읽게 (검토 대기)
- 예전: UTF-8 만 보고 깨진 글자가 있으면 "미리 볼 수 없음" - 한국어 .smi (대부분 CP949) · 메모장의 UTF-16 글이 안 보였다.
- 자막과 같은 글자 코드 판별 (detectCharset · decodeText) 을 그대로 써서 MKV 화면과 같게 판단한다
  (UTF-8 (BOM) · UTF-16 (BOM) · CP949 · Shift-JIS · GBK).
- 그래도 깨진 글자가 많거나 (2 + 글자 수의 1% 넘음) 글이 아니면 (NUL) "미리 볼 수 없음 (글자 코드를 알 수 없습니다)".
  64KB 끝에서 잘린 글자는 빼고 본다.
- 시험: CP949 .smi · UTF-16 (BOM) .srt · UTF-8 BOM .txt · 글이 아닌 파일.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:19:23 +09:00
RyuJongJin 39dab23138 WIP 82 파일 탐색기 창 사이 끌어 놓기: 마우스 8px 끌기 · 터치 ≡ 손잡이, 놓을 곳 강조, 묻기 (복사 · 이동), 이동 [되돌리기] (검토 대기)
- 관리자 지시 (F11) · 판단: 창 사이 끌어 놓기로 복사 · 이동 (태블릿 · PC). 미리 보기 · 확인 · 되돌리기.
- 끌기: PC 마우스는 줄을 8px 넘게 움직였을 때만 (한 번 누르기 = 고르기 · 두 번 = 열기는 그대로).
  터치는 선택 모드에서 표시한 줄의 ≡ 손잡이 (길게 누르기 메뉴는 그대로). 누른 줄이 표시한 것이면 표시한 것 모두 (바깥 것만).
- 놓기: 다른 창의 폴더 줄 = 그 폴더 안, 창의 빈 곳 = 그 창의 지금 폴더. 놓을 수 있으면 강조, 못 놓는 곳 (같은 창 · 자기 안 ·
  원본 폴더 자신) 은 빨간 테두리이고 놓아도 아무 일 없음. WebDAV 창도 지금의 복사 · 이동으로.
- 설정 "끌어 놓기 기본": 묻기 (처음 값) / 복사 / 이동. 묻기 창은 [복사] 가 처음 고름 (Enter) · [이동] · [취소].
  어느 쪽이든 지금의 복사 · 이동 확인 창 (같은 이름 등) · 진행 패널을 그대로 탄다.
- 이동만 끝난 알림에 [되돌리기] (10초): 실제로 옮긴 것만 원래 자리로 (원래 폴더마다 지금의 이동 - 같은 이름이면 지금의 같은 이름 창).
  대상에 새로 생긴 항목은 통째로, 이미 있던 폴더에 합친 것은 앱이 옮긴 파일만 (TransferJob.movedFiles) -
  그 폴더 전체를 옮기지 않는다. robocopy 로 합친 폴더는 무엇을 옮겼는지 몰라 되돌리지 않고 알린다. 복사 되돌리기는 없음.
- 내 판단: 끝난 알림 (n개 옮김) 은 [되돌리기] 알림으로 바로 바꾼다 (진행 막대가 내려가기를 기다리지 않게).
- 시험: explorer_test 82 (놓는 곳 규칙, 4px 는 누르기, 같은 창 · 묻기 [취소] 는 아무 일 없음,
  이동 → 되돌리기 - 합친 폴더의 원래 파일은 그대로 · 옮긴 파일만 돌아옴).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:12:04 +09:00
RyuJongJin b1f20914a0 WIP 81 파일 탐색기 빠른 보기: Space (TC 는 F3) · 길게 누르기 [미리 보기] - 동영상 썸네일 · 길이 · 코덱, 그림, 글 앞 64KB (검토 대기)
- 관리자 지시 (F10): 미리 보기 창 / PC 에서 Space 로 빠른 보기 (썸네일 · 길이 · 코덱).
  관리자 판단: 띄우는 창 (옆에 붙는 칸은 넓은 화면용으로 나중에 따로).
- 여는 곳: Windows 탐색기 · X-plore 모양은 Space (빠른 필터가 닫혀 있을 때만 - 칸에 치는 중이면 빈칸), TC 모양은 F3 (Space 는 표시 그대로),
  길게 누르기 [미리 보기] (파일).
- 동영상 · 음악: 큰 썸네일 (가로 640, jj_thumbs 에 따로 기억) · 길이 · 영상 코덱 · 해상도 · 소리 (코덱 · 언어) · 자막 트랙.
  ffprobe 는 기다리는 동안 "정보를 읽는 중…", 20초 넘으면 "읽지 못했습니다" (창은 멈추지 않음).
- 그림: 그림 그대로. 글 파일 (txt · srt · ass · smi · log · json …): 앞 64KB, UTF-8 이 아니면 "미리 볼 수 없음".
- WebDAV: 썸네일 · ffprobe 없이 이름 · 크기 · 날짜만 (비밀번호를 명령 줄에 넣지 않게 - 관리자 판단).
- ← → (↑ ↓) 로 같은 폴더의 앞 · 다음 (지금 목록 차례 - 필터 · 정렬 반영), Space · Esc 로 닫기, Enter · [열기 · 재생].
- 시험: quick_look_test (글 앞부분 · CP949 · 64KB 경계, ← → · 열기 · Space 닫기), explorer_test 81 (Space · 필터 중 빈칸 · TC F3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 12:50:14 +09:00
RyuJongJin 60336d342d WIP 83 보충: 스타일 설명의 단축키 줄은 PC 에서만 (15 지적 · 194 와 같게) (검토 대기)
- 키보드가 없는 폰 · 태블릿에서는 쓸모없는 긴 글이었다. Windows (desk) 에서만 보인다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 12:35:15 +09:00
RyuJongJin a780139eeb WIP P0 파일 탐색기: 글자 칸에 치는 중에는 파일 단축키가 키를 가로채지 않게 (15 지적 · 관리자 P0) (검토 대기)
- 예전: 화면의 CallbackShortcuts 가 글자 칸 (빠른 필터 등) 보다 먼저 키를 받아, 칸에서 Delete 를 누르면 파일 지우기 창이 뜨고
  Ctrl+A · C · X · V · Space · F8 · Backspace 도 파일 동작으로 쓰였다 (Ctrl+V 는 파일을 지금 폴더에 붙여넣을 수 있었다).
  Delete · F2 · Backspace 는 83 전부터 있던 것.
- 이제 단축키는 Focus.onKeyEvent 로 받아, 글자 칸에 포커스가 있으면 넘긴다 (칸의 기본 동작). 모든 모양 · Rsync 화면 공통.
  Esc 는 지금처럼 필터 칸이 받아 필터를 닫는다.
- 찾기 · 이름 바꾸기 · 경로 입력은 창 (다른 경로) 이라 이 단축키 아래에 있지 않다.
- 시험: explorer_test P0 (windows · xplore · totalcmd 각각 - 필터 칸에서 Ctrl+A 는 글 전체 선택, Ctrl+C · V · Delete · Space · F8 ·
  Backspace 가 파일을 표시 · 담기 · 지우기 하지 않고 칸이 그대로, Esc 로 닫힘).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 12:34:15 +09:00
RyuJongJin 5411558779 WIP 83 파일 탐색기 스타일별 단축키: Total Commander F5~F8 · Insert/Space 표시, Windows 탐색기 Ctrl+C · X · V · A (검토 대기)
- 관리자 지시 (F12): 스타일에 맞는 단축키.
- Total Commander 모양: F5 복사 · F6 이동 · F7 새 폴더 · F8 지우기 · Insert (표시하고 다음 줄로) · Space (표시) · Ctrl+R 새로 고침.
  Space 는 이 모양에서 빠른 필터를 열지 않는다 (필터가 열려 있으면 그대로 글자).
- Windows 탐색기 · X-plore 모양: Ctrl+C (담기) · Ctrl+X (잘라내기) · Ctrl+V (지금 폴더에 붙여넣기) · Ctrl+A (지금 폴더 모두 표시) · F5 새로 고침 (예전과 같음).
- 모든 모양 공통 (예전과 같음): Delete · Shift+Delete · F2 · Esc · Ctrl+F · Backspace · Alt+← → · Tab.
- Rsync 화면은 스타일 단축키를 쓰지 않는다 (F5 새로 고침 그대로).
- 내 판단: TC 모양에서는 F5 가 복사라 새로 고침을 Ctrl+R 로 (Total Commander 와 같게).
- 환경 설정 > 파일 탐색기 > 스타일 설명에 단축키를 적었다.
- 시험: explorer_test 83 (TC: Insert · Space 표시 · Space 가 필터를 열지 않음 · F5 복사 창 · F7 새 폴더 / Windows: Ctrl+A · Ctrl+C).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 12:17:04 +09:00
RyuJongJin 5007d30afa WIP 80 보충 (15 지적 · 관리자 판단 ① ②): 폴더 크기에서 링크 · junction 은 따라가지 않음 · 화면에서 사라진 자동 계산은 멈춤 (검토 대기)
① 링크 · junction 을 따라가 세던 것 (같은 것을 여러 번 세어 크게 나오고, 위 폴더를 가리키는 고리면 끝나지 않을 수 있었다 -
   실제 예: wt_*\third_party (junction) · .plugin_symlinks):
  - 크기를 셀 때 로컬 폴더는 list(followLinks: false) 로 직접 읽고, 심볼릭 링크 · junction 은 0 으로 세고 수만 센다.
    "1.2GB · 링크 3개 제외" 로 보인다 (214 의 링크 처리와 같은 판단).
  - 안전장치: 이미 센 폴더 (실제 경로) 는 다시 세지 않는다.
② 다른 폴더로 가도 기다리던 계산이 끝까지 돌던 것:
  - 화면을 위해 걸어 둔 자동 계산은 그 폴더가 어느 창에도 보이지 않으면 대기 줄에서 빼고, 세는 중이면 멈춘다 (결과는 남기지 않음).
  - 길게 누르기 [폴더 크기 계산] 으로 직접 시킨 것만 끝까지 - 아래 알림 [취소] · 같은 메뉴 "폴더 크기 계산 멈추기".
  - WebDAV 는 자동으로 하지 않는다. 직접 시키면 "서버에서 안의 것을 모두 읽어 오래 걸릴 수 있습니다" 를 알린다 (관리자 판단).
- 시험: folder_size_test (Windows 에 실제 junction · 심볼릭 링크 · 위 폴더 고리 junction, 화면이 바뀌면 자동 계산 멈춤 · 사용자가 시킨 것은 끝까지 · 취소).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 12:01:06 +09:00
RyuJongJin 1b988cd84c WIP 84 파일 탐색기 정렬: 열 머리를 누르면 그 기준 · 다시 누르면 거꾸로, 설정 "정렬을 폴더마다 기억" (검토 대기)
- 관리자 지시 (F13): 열 머리를 누르면 그 기준으로 정렬, 폴더마다 정렬을 기억 (선택).
- Windows 탐색기 · Total Commander 모양의 열 머리 (이름 · 날짜 · 유형/확장자 · 크기) 를 누르면 그 기준 (오름차순),
  같은 열을 다시 누르면 거꾸로. 지금 정렬 열은 강조색 + ▲ ▼. (X-plore 모양은 열이 없어 예전처럼 [정렬 기준])
- 설정 "정렬을 폴더마다 기억" (처음: 끔 - 예전처럼 모든 폴더가 같은 정렬). 켜면 열 머리 · [정렬 기준] 이 지금 창의 폴더에만 쓰고
  (정렬 창에 "이 폴더에만: …"), 기억하지 않은 폴더는 전체 정렬. 300개까지 (오래된 것부터 지움). 틀린 값은 비움.
- 80 의 자동 폴더 크기 · 크기가 나온 뒤 다시 줄 세우기도 그 폴더의 정렬을 따른다.
- 시험: explorer_test 84 (열 머리 · 거꾸로 · ▼, 폴더마다 · 저장 · 틀린 값).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:53:38 +09:00
RyuJongJin a15f6c25af WIP 78 보충 (15 확인 · 관리자 판단 a · b): Windows 에서 쓸 수 없는 이름 미리 막기 · 임시 이름은 고리일 때만 · 동기화에서 임시 이름 빼기 (검토 대기)
a 이름 검사 nameProblem (일괄 미리 보기 · 한 이름 바꾸기 · 새 폴더 공통):
  - 어디서나: 빈 이름 · . · .. · \ / : * ? " < > | · 제어 문자 (예전과 같음).
  - Windows 의 로컬 경로 (WebDAV 는 빼고): CON · PRN · AUX · NUL · COM1~9 · LPT1~9 (확장자가 붙어도), 끝이 점 · 빈칸, 255자 넘음.
    예전에는 미리 보기가 괜찮다고 보인 뒤 [바꾸기] 에서 실패해 되돌려지거나 '.' 이 말없이 빠질 수 있었다.
  - Android · WebDAV 는 지금처럼 (관리자 판단).
b 임시 이름 (~jjren_ · ~jjback_) 이 실시간 동기화 · rsync 에 섞이지 않게:
  - 일괄 바꾸기는 새 이름 자리가 비는 차례대로 바로 바꾸고, 고른 것끼리 고리로 엮였을 때 (a↔b) 만 하나를 임시 이름으로 비켜 둔다.
    되돌리기 (실패 · [되돌리기]) 도 같은 방식.
  - lsync: 감시 (Windows 폴더 감시) · 맞출 것 세기 (로컬 · WebDAV) · 지울 것 세기 · 앱이 맞추기 (로컬 · WebDAV) 에서 뺀다.
    rsync 인수 --exclude=~jjren_* --exclude=~jjback_*, robocopy /XF. 맞추기 미리 보기에서도 뺀다.
  - 탐색기 · Rsync 화면의 rsync 복사 · 이동 (rsyncArgs) 에도 같은 제외 (화면의 명령 줄에도 보임). robocopy 복사 · 이동은 그대로.
- 시험: 이름 검사 (예약 · 끝 점 · 빈칸 · 256자, Windows 규칙일 때만), 이어진 이름은 임시 이름 없이 · 고리는 하나만,
  실패 되돌림, lsync 가 임시 이름을 세지 않음 · 인수에 제외, rsync 인수 기대값.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:42:57 +09:00
RyuJongJin ef1cb069c1 WIP 80 파일 탐색기 폴더 크기: 필요할 때 계산 · 기억, 크기순 정렬 + 79-a [그 자리로] 뒤에도 돌아가기 줄 (검토 대기)
80 (관리자 지시 F9): 폴더 크기를 목록에 표시하고 크기순으로 정렬 (필요할 때 계산하고 기억).
- FolderSizes: 안의 파일 크기 합, 한 번에 하나씩 (디스크 · NAS 를 몰아 읽지 않게), 이번 실행 동안 기억.
  읽지 못한 폴더가 있으면 "1.2GB+". 다시 읽는 폴더 (새로 고침 · 복사 · 이동 · 지우기) 는 그 폴더 · 안쪽 · 위 폴더 크기를 잊는다.
- 설정 "폴더 크기": 크기순 정렬일 때만 (처음 값) / 늘 계산 / 계산 안 함. 길게 누르기 [폴더 크기 계산] 은 늘 된다.
  - 내 판단: 자동 계산은 지금 보고 있는 폴더 바로 안의 폴더만 - 트리에 보이는 위 폴더들 (C:\ · 사용자 폴더 …) 까지 하면
    드라이브 전체를 읽느라 정작 보고 있는 폴더가 뒤로 밀렸다 (시험에서 발견).
- 보이는 곳: Windows 탐색기 모양은 크기 열, TC 모양은 <DIR> 자리, X-plore · 좁은 화면은 폴더 이름 아래. 계산 중 "…".
- 크기순 정렬은 아는 폴더 크기로 (모르는 폴더는 가장 작게, 폴더는 늘 위 - 예전 그대로). 크기가 나오면 다시 줄 세운다.
- 측정은 바깥 영역 (Zone.root) 에서 - 화면을 그리다 맡긴 계산이 그 영역에 묶이지 않게 (위젯 시험에서 읽기가 끝나지 않았다).
79-a (관리자 판단 10/17): [그 자리로] 로 나가도 [찾기 결과로 돌아가기] 줄을 남긴다.
- 시험: folder_size_test (합 · 기억 · 잊기 · "+" · 정렬), explorer_test 80 (크기순 자동 · 새로 고침 뒤 다시 · "안 함" + 길게 누르기), 79-a 그 자리로.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:33:07 +09:00
RyuJongJin 60d4db7583 WIP 79 보충 (15 지적 · 관리자 판단 a~d): 폴더와 안의 파일을 함께 골라도 한 번만 · 합계에 폴더 따로 · [찾기 결과로 돌아가기] · "지금까지 찾은 n개 모두" (검토 대기)
c (데이터) 찾기 결과에서 폴더와 그 안의 파일을 함께 고르면 안의 것은 빼고 바깥 것만 (outermostPaths) -
  안쪽 파일을 두 번 처리하거나 이동 뒤 "없음" 오류가 나고 확인 창 개수가 부풀 수 있었다.
  탐색기의 복사 · 이동 · 지우기 시작에서도 같은 정리를 한다 (탐색기 표시는 이미 정리돼 있어 동작은 같음).
b 아래 줄 합계: "파일 n개 · 크기 / 폴더 m개 (크기 미포함)". 지우기 확인 창에 "그중 폴더 m개 - 안의 파일 · 폴더도 모두 함께".
a 복사 · 이동 · 지우기 뒤 결과 화면은 닫힌 채로, 탐색기 위에 [찾기 결과로 돌아가기] 줄 (✕ 로 닫기).
  다시 찾지 않고 결과를 열며, 지운 것은 빼고 옮긴 것은 새 자리로 (고른 폴더와 함께 옮겨진 안쪽 것도).
  다음 찾기 · ✕ · 결과 화면을 그냥 닫으면 없어진다. 처음 찾기가 끝나기 전 결과였으면 그렇다고 적는다.
  - 내 판단: [그 자리로] (reveal) 로 나가면 돌아가기 줄을 남기지 않는다 (탐색이라서).
d 찾는 중 전체 고르기 단추 글: "지금까지 찾은 n개 모두", 다 찾으면 "모두".
- 시험: explorer_test 79-c 복사 · 이동 · 지우기 (한 번만 · 오류 없음 · 확인 창 개수 · 돌아가기), explorer_search_test (outermostPaths · 단추 글 · 합계).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:13:21 +09:00
RyuJongJin 9032e8c4d7 WIP 78 파일 탐색기 일괄 이름 바꾸기: 이름 틀 [N] · 번호 [C] · 찾아 바꾸기 · 대소문자, 미리 보기 · 겹침 검사, 실패하면 되돌림 · [되돌리기] (검토 대기)
- 관리자 지시 (F7): 번호 [C] · 찾아 바꾸기 · 대소문자, 미리 보기, 되돌리기 (예: "Show S01E01…").
  데이터를 바꾸는 기능이라 미리 보기 · 확인 · 되돌리기를 둔다.
- 여럿을 고르고 [이름 변경] (버튼 · F2 · 길게 누르기 아이콘 줄 "n개 이름 일괄 바꾸기") → 일괄 이름 바꾸기 창.
  하나면 예전 이름 변경 그대로.
  - 77 에서는 여럿일 때 [이름 변경] 이 꺼져 있었다 → 이제 일괄 창을 연다 (77 시험의 기대값을 바꿈).
- 규칙 (차례대로): 이름 틀 (확장자 뺀 이름, [N] 원래 이름 · [C] 번호 시작 · 간격 · 자릿수) → 찾아 바꾸기 (대소문자 무시 기본)
  → 대소문자 (그대로 · 모두 작게 · 모두 크게 · 단어 첫 글자만). 파일 확장자는 그대로 (대소문자 바꾸기만 따름).
- 미리 보기: 원래 → 새 이름, 문제 (쓸 수 없는 이름 · 고른 것끼리 같은 이름 · 고르지 않은 것과 같은 이름, 대소문자 무시)
  는 빨갛게 보이고 하나라도 있으면 [바꾸기] 를 막는다. 고른 것끼리 맞바꾸기 (a↔b) 는 된다.
- 번호는 목록에 보이는 차례대로.
- 바꾸기는 모두 임시 이름을 거쳐 두 단계로 (맞바꾸기 때문). 도중에 실패하면 이미 바꾼 것을 원래 이름으로 되돌리고
  창에 이유 · 되돌리지 못한 것을 보인다.
- 끝나면 아래 알림 "n개의 이름을 바꿨습니다" + [되돌리기] (10초).
- 내 판단: 규칙은 창을 열 때마다 처음 값 ([N] · 1 · 1 · 2자리) - 설정에 저장하지 않음.
- 시험: batch_rename_test (규칙 · 미리 보기 · 맞바꾸기 · 실패 되돌림 · 되돌리기 · 창), explorer_test 78 (고르기 → 바꾸기 → 되돌리기).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:07:48 +09:00
RyuJongJin e39068cc9f WIP MKV 화면 ②③ 보충 (15 확인 · 관리자 판단): [›] 뒤 바탕 · 폰 세로 빈 작업 기록은 한 줄 높이 (검토 대기)
② [›] · [‹] 를 짧은 흐림 (20) + 바탕색 칸 + 단추 바탕 (panelHigh) 위에 - 폭 412 에서 잘린 세 번째 버튼 글자가
   [›] 뒤로 비쳐 보였다 (관리자 지시).
③ 관리자 1차 판단: 폰 세로 (좁은 화면) 에서 작업 기록이 비었으면 칸을 안내 한 줄 높이 (40) 로,
   기록이 생기면 예전 높이 (140 까지). 정보는 숨기지 않고 빈 자리만 없앤다. 넓은 화면 · 인코딩 줄은 그대로.
- 시험: 비었을 때 40 → 기록이 생기면 140, 넓은 화면은 140.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:50:01 +09:00
RyuJongJin 31a06c74ed WIP 79 파일 탐색기 찾기 결과를 목록 화면으로: 크기 · 바뀐 날 · 종류 조건, 고르고 그대로 복사 · 이동 · 지우기, 2000개마다 [더 보기] (검토 대기)
- 관리자 지시 (F8): 찾기 결과를 목록으로 보여 그대로 복사 · 지우기 · 재생, 크기 · 날짜 · 확장자 조건
  ("1GB 넘는 30일 지난 영상"), 2000개에서 멈추면 "더 보기".
- 찾기 창: 이름 (비워도 됨, * ?) · 크기 이상 (MB/GB) · 바뀐 날 (아무 때 / N일 지남 / N일 안) · 종류
  (모두 · 동영상 · 음악 · 자막 · 그림 · 확장자 직접). 크기 · 날짜 · 종류를 넣으면 파일만 찾는다.
  마지막 조건은 이번 실행 동안 다음 찾기 창의 처음 값.
- 결과 화면 (예전 작은 창 _SearchDialog 를 바꿈): 경로 · 크기 · 바뀐 날, 누르면 파일은 열기 · 재생 (결과 화면 그대로),
  폴더는 그 자리로, 줄마다 [그 자리로]. 체크 · 길게 누르기로 고르면 아래 줄에 개수 · 합계 크기와
  [복사 → 다른 창] [이동 → 다른 창] [지우기] - 고른 것만, 탐색기와 같은 확인 창 · 진행 패널 · 휴지통 (154 · 155 · 65).
- 2000개마다 멈추고 [2000개 더 보기]. 딱 2000개면 멈췄다고 하지 않는다 (다음 것이 올 때만).
- searchFiles 에 accept (조건) - 이름 없이 조건만으로도 찾는다. 이름도 조건도 없으면 예전처럼 찾지 않음.
- 시험: explorer_search_test (조건 · accept · 더 보기 · 열기 · 고르기 · 폴더), explorer_test 79 (종류만으로 찾아 다른 창으로 복사).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:48:42 +09:00
RyuJongJin e658de6691 WIP MKV 화면 ① 보충: 넓은 화면에서 목록이 비었으면 [동영상 추가] 안내 그대로 (검토 대기)
- 8833c93 에서 넓은 화면은 고른 것이 없으면 늘 "고른 동영상이 없습니다" 를 보여, 빈 목록일 때도
  [동영상 추가] 안내가 사라졌다 (ui_test "동영상 추가 후 화면 표시" 가 잡음). 좁은 화면과 같게 나눈다.
- 시험: 빈 목록 안내를 412 · 1280 둘 다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:33:54 +09:00
RyuJongJin 8833c93bf8 WIP MKV 화면 (15 지적 ①②③): 고른 것 없음 안내 · 폰 세로 버튼 줄 [›] · 목록은 내용만큼 + 설정 "폰 세로 목록 크기" (검토 대기)
① 동영상이 있는데 고른 것이 없으면 빈 목록 안내 ("동영상 추가로 파일을 선택하세요") 대신
   "고른 동영상이 없습니다 · 목록에서 동영상을 누르면 여기에 자세히 보입니다" (넓은 · 좁은 화면 모두).
   첫 줄 자동 고르기는 하지 않음 (관리자 판단).
② 폰 세로 (폭 412) 위 버튼 줄이 화면보다 길면 끝에 흐림 · [›] (밀었으면 [‹]), 누르면 한 화면쯤 민다
   (새 위젯 ScrollMore). 예전에는 세 번째 버튼이 잘린 채 더 있다는 표시가 없었다 (버그).
③ 폰 세로 목록 높이: 화면 32% 고정 → 관리자 판단대로 목록은 내용만큼, 자세히 칸 최소 높이를 남기고
   남는 공간은 목록에. 설정 "폰 세로 목록 크기" 자동 (처음 값) / 작게 (25%) / 크게 (55%), 모르는 값은 자동.
   - 내 판단: 자동의 자세히 칸 최소 = 고른 동영상이 있으면 62% (261 · 266 의 자막 버튼 보호, 예전 68% 와 비슷),
     없으면 안내 자리 120.
   - 내 판단: 작업 기록 칸이 비었으면 "작업 기록이 아직 없습니다 …" 를 적는다 (✕ 만 있는 빈 칸으로 보였다, 높이는 그대로).
- 시험 mkv_compact_list_test (412 · 1280, 작게 < 크게 < 자동, [›] 누르기, 빈 기록 안내), 번역 en · ja · zh-Hans.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:24:00 +09:00
RyuJongJin a2cb535f1e WIP 263 다운로드: DNS 서버에 닿지 않아 실패하면 줄에 이유 · [시스템 DNS 로 바꾸기] (검토 대기)
- 관리자 지시 (42 가 찾던 것): DNS 강제 프록시를 거친 yt-dlp · aria2 는 "Tunnel connection failed" 만 남겨
  줄에는 "연결할 수 없습니다" 뿐이라 이유 · 할 일을 몰랐다.
- 실패한 그때 DNS 강제 + "닿지 않음" 상태이고 오류가 연결 · 프록시 실패이면 줄에 "DNS 서버 (…) 에 닿지 않아 받지 못했습니다".
  404 · 로그인 같은 다른 실패는 DNS 탓으로 보지 않는다.
- 아직 DNS 강제면 [시스템 DNS 로 바꾸기] (설정에 저장, 버튼은 사라짐). 다시 받기는 [다시 시도] 로 사용자가 (내 판단: 몰래 다시 받지 않음).
- 번역 en · ja · zh-Hans, 시험 download_error_test (판단 · 줄 · 버튼).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:55:14 +09:00
RyuJongJin f596ef7253 WIP 266 폰 세로 MKV 화면: DNS 닿지 않음 줄을 한 줄로 (글 줄임 · 길게 누르면 전체 · 버튼 같은 줄) - 세부 정보의 자막 버튼이 밀려나지 않게 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:49:36 +09:00
RyuJongJin 1c6b008332 WIP 77 길게 누르기 메뉴: 위 아이콘 줄 (복사 · 이동 · 이름 변경 · 삭제 · 공유), 여럿이면 "n개 항목" · 삭제도 표시한 것 모두, 경로 복사 · 즐겨찾기에 추가, Android 공유 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:49:34 +09:00
RyuJongJin 8228d04a6e WIP 264 · 265 이동 · 복사 끝: 알림에 "n개는 원본에 남음" · 펼쳐 둔 원본 · 대상 안쪽까지 다시 읽기 (없어진 폴더는 접음) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:49:33 +09:00
RyuJongJin 92919aae93 WIP 262 MKV 끝나는 순간 줄 ■: 같은 자리에 오는 ⟳ 는 묶음이 끝날 때까지 안 눌림, 집계는 끝났을 때의 결과로 · 시작 못 한 것만 취소됨 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:49:32 +09:00
RyuJongJin 320025c5fa WIP 76 보충: 목록 줄에 항목 경로 키 - 줄 목록이 바뀔 때 누른 줄의 두 번 누르기 기다림이 다른 항목으로 넘어가지 않게 (setState during build) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:49:31 +09:00
RyuJongJin 1eef4ff65f WIP 76 파일 탐색기 빠른 필터: 창 위 필터 버튼 · Ctrl+F · 글자 바로 치기, 지금 폴더 안을 이름으로 좁힘, 고르기는 보이는 것만, Esc 로 닫기 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:49:30 +09:00
RyuJongJin a3e898c66d WIP 184-2 보충: generic 추출기 (직접 파일 · .unknown_video) 이고 합치지 않은 경우만 일반 파일로 - 사이트 추출기의 후처리 실패는 그대로 실패 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:49:28 +09:00
RyuJongJin f9d3596bae WIP 261 폰 세로 MKV 세부 정보: 자막 버튼을 칩 바로 아래로 - 감지한 언어 줄 · 이동 버튼이 [AI 자막 만들기] 를 밀어내지 않게 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:49:28 +09:00
RyuJongJin 8937c17617 WIP 260 환경 설정 > 자막: 받은 AI 자막 모델 (Whisper · NLLB) 크기 · [지우기], 작업 중에는 못 지움 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:49:27 +09:00
RyuJongJin 99d446cea6 WIP 259 번역 모델 (NLLB 912MB) 은 번역이 실제로 필요할 때만 받기 - 자동 감지 + 한 언어면 감지한 원어가 다를 때 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:49:26 +09:00
RyuJongJin 12db3d43b1 WIP 184-2 확장자 없는 주소의 동영상이 아닌 파일: 후처리 실패해도 받은 파일을 원래 이름으로 남기고 완료 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:49:25 +09:00
RyuJongJin a4a5c4289a WIP 229 폴더 충돌 (WebDAV): 합칠 때 안쪽의 받는 쪽이 더 새 파일 건너뜀 · 이동은 건너뛴 원본 남김 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:49:22 +09:00
RyuJongJin 62e8d52031 WIP 시험 흔들림: sync_problem 71 (만들다 만 파일 수가 늦게) · dns TC (두 번 왕복 시간) · 두 번 누르기 (가짜 시간 간격) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:49:21 +09:00
RyuJongJin a5017965cb WIP 256 폰 가로 MKV 화면: 인코딩 줄은 한 줄 (설명 · 경고 앞), 작업 기록은 작게 - 목록이 한 줄만 보이던 것 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:49:20 +09:00
RyuJongJin cab577ec8a WIP 255 세로 폰 환경 설정: 묶음 바로가기를 한 줄로 (옆으로 밀기) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:49:19 +09:00
RyuJongJin ed5a1407c1 WIP 다운로드 줄이 자동 추가로 빠질 때 이유 알림 · [보기] (931 관리자 판단) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:49:18 +09:00
RyuJongJin 2d2f7055ba WIP 253 글자 크게 (1.6배) 해도 넘치지 않게: 탐색기 줄 높이 · 설정 고르기 상자 · 위 막대 · 인코딩 · 모니터링 · 언어 · 화면 전체 시험 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:49:12 +09:00
RyuJongJin 6d322b1726 WIP 185 시험: 다운로드 취소 창의 [계속 받기] (downloads_ui_test 기대값) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:48:45 +09:00
RyuJongJin 6a782b2fdb WIP 다음 판: 199 권한 앱 목록 안내 · 188 버튼 글 맞춤 · 206 이동 버튼 · 181 · 182 mp3 재생 · 184 직접 파일 일반 받기 · 185 [계속 받기] (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:48:44 +09:00
RyuJongJin 3327d9bf9d 버전 2026.10.12_001
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:13:19 +09:00
RyuJongJin 1be9f58248 폰 세로 MKV 위 막대 진행: "2/3 · 37%" 짧은 꼴 · 폭을 미리 잡아 [+ 동영상 추가] 를 가리지 않게
a4 의 934 기기 확인 · 관리자 지시 (10/12): 폰 세로에서 진행이 "37%" 만 보여 몇 번째인지 몰랐고, 동영상 추가 [+] 를
반쯤 가렸다. 위 막대는 작업이 시작될 때 재는데 그때는 진행률이 없어 글이 없다가, 나중에 글이 생기며 잰 폭보다 넓어졌다.
- 아이콘만 보이는 좁은 꼴에서 여러 개면 "n/전체 · %" · 폭은 늘 같게 (여러 개 72 · 하나 36).
- 여러 개를 만드는지가 바뀌면 위 막대를 다시 잰다.
- 시험: 폰 세로 411×800, 3개 만드는 중 진행률이 생긴 뒤에도 "n/3 · 37%" · [+] 의 보이는 부분과 겹치지 않음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:15:38 +09:00
RyuJongJin 031e8d3f84 폰 세로 모니터링 버튼 줄 넘침 (257) · 지운 쌍의 "동기화 멈춤" 알림이 남던 것 (258)
a4 의 934 기기 확인 · 관리자 지시 (10/12, 버그 수정):
- 257: 폰 세로 모니터링 > lsync 카드의 버튼 줄이 화면 밖으로 넘쳐 [고치기] · [지우기] 를 누를 수 없었다 → Wrap 으로 줄 바꿈
  (복사 카드 버튼 줄도). 같은 폭 (411) 시험에서 함께 넘치던 곳도 고침: lsync 탭 위 "다시 켤 때 동기화" 줄,
  카드 머리의 방식 칩 (남는 폭만큼 · … · 전체는 마우스), 일정 글.
- 258: 멈춘 lsync 쌍을 지워도 작업 알림 "⚠ 동기화 멈춤 1개" 가 앱을 다시 켤 때까지 남았다 → 쌍 목록이 바뀌면
  지켜보지 않는 쌍 (지움 · 끔 · 멈춤) 의 멈춤 기록을 지운다.
- 시험: 411 폭에서 넘침 없음 · 버튼이 화면 안 (고치기 전에는 실패) · 지우면 멈춤 기록이 바로 사라짐.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:15:38 +09:00
RyuJongJin 4ad66dd5a1 통합: fix_mkv (c41954b: P0 MKV 다시 만들기 · 묶음 집계, 229 폴더 덮어쓰기) 를 합침
관리자 지시 (10/12, 마지막 후보). 겹친 곳 없이 자동으로 합쳐짐 (번역 json 3개만 양쪽이 고침 - 정렬 · 모양 그대로 확인).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:56:17 +09:00
RyuJongJin c350499b79 248 다듬기: 아래 DNS 알림은 상태가 풀리면 닫고, 같은 안내를 보이는 화면 (브라우저 덮개 · 설정 DNS 칸) 에서는 띄우지 않음
a4 의 933 기기 확인 · 관리자 지시 (10/12, 이번 릴리스 전에):
- DNS 를 고친 뒤 (정한 서버가 다시 답함 · 설정을 바꿈) 에도 옛 문구 "DNS 서버 (192.0.2.1) 에 닿지 않아…" 가 남아
  설정의 DNS 칸을 가렸다 → 알림 내용 위젯 (DnsFailureNotice) 이 상태를 듣다가 풀리면 스스로 닫는다
  (Flutter 의 알림 닫기는 "지금 보이는 알림" 에만 해야 해서, 보이는 동안 그려지는 내용이 닫는다).
- 브라우저 덮개와 함께 떠 [시스템 DNS 로 바꾸기] 가 두 개 보였다 → 덮개 · 환경 설정의 DNS 칸이 열려 있는 동안은
  띄우지 않고, 떠 있다가 그런 화면이 열리면 닫는다 (NetGuard.noticeBlockers). 상태는 설정 · 작업 기록 줄 (249) 에 그대로.
- 버튼이 있는 알림이 저절로 사라지지 않는 것 (248 처음 지시) 은 그대로.
- 시험: 상태 풀림 → 닫힘 · 막는 화면이 있으면 안 뜸 · 뜬 뒤 열리면 닫힘 · 설정 DNS 칸 · 덮개가 막는 수를 올리고 내림.
  실제 Edge 통합 시험 다시 통과.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:47:50 +09:00
RyuJongJin c41954b3ea 폴더를 덮어쓰기로 합쳐도 안쪽의 받는 쪽이 더 새 파일은 건너뜀 · 같은 이름 창에 미리 알림 (229, 931)
- 데이터 (버그, 기기 확인): 폴더 [덮어쓰기] (합치기) 가 안쪽의 더 새 파일을 예전 것으로 덮었다 - 설정 '받는 쪽이 더 새것이면 건너뛰기' 를 안쪽에도
- 이동이면 건너뛴 원본은 남기고 옮긴 것만 지움 ('n개는 원본에 남음'), 같은 이름 창의 폴더 줄에 '안쪽에 받는 쪽이 더 새 파일 n개'
- 맨 위 파일 하나를 사용자가 [덮어쓰기] 로 고른 것은 그대로 따름

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:31:41 +09:00
RyuJongJin bf4b27a5f6 MKV 다시 만들기: 임시 이름에 만들고 다 됐을 때만 바꿔 넣음 · 이미 있으면 묻기 · 묶음 집계 · 끝난 뒤 취소 무시 (P0, 931)
- 데이터 (버그, 기기 확인 · 10.10 에도 있음): FFmpeg -y 가 처음부터 결과 MKV 에 써서, 다시 만들다 취소 · 실패하면 예전 완성본이 사라졌다
  → 이름.mkv.jjpart 에 만들고 다 된 뒤에만 storage.replaceFile, 취소 · 실패면 임시 파일만 지움
- 결과가 이미 있으면 시작 전에 묻기 (덮어쓰기 · 건너뛰기 · 다른 이름) + 환경 설정 '만들 MKV 가 이미 있으면' (처음 값 묻기)
- 240 집계: 이번 묶음의 이전 상태를 비우고, 시작 못 한 것은 취소로 셈
- 끝난 (코드 0) 뒤에 온 취소는 무시 - 성공으로 (Windows · Android)
- 폰 세로 작업 중 [취소] · 진행을 늘 보이는 자리에, 진행 글 'MKV 3개 중 2번째 · 36%'
- 시험: 가짜 FFmpeg 가 출력을 만들게 (queue_test), 모든 파일이 '있는' 가짜 저장소에서는 덮어쓰기 (ui_test)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:31:41 +09:00
RyuJongJin f7d73da301 250 고침 2: Android 에서 덮개가 뜨자마자 걷히던 것 - 덮개는 사용자가 옮길 때만 걷음 · 기록의 예외 이름 빼기
a4 의 932 기기 확인 (노트 10+): 작업 기록에 "웹 페이지를 열지 못함 (…): net::ERR_TUNNEL_CONNECTION_FAILED" 는 남는데
덮개가 보이지 않았다 (버그 수정).
- 원인: Android 는 본문 오류 (onReceivedError) 뒤에 엔진의 오류 페이지를 또 읽어 진행률이 0 → 100 으로 한 번 더 돈다.
  "새로 읽기 시작 (진행률이 1 아래로)" 에서 덮개를 걷어, 뜨자마자 걷혔다. Windows (Edge) 는 이 단계가 없어 통과했다.
- 덮개는 사용자가 옮길 때만 걷는다: 주소 · 뒤로 · 앞으로 · 새로고침 · [다시 시도] (WebNav 를 감싸 알림 - _NavWatch).
  덮개가 웹 뷰를 덮고 있어 그 밖의 길로는 옮겨지지 않는다.
- 덮개를 띄우면 작업 기록에 "DNS 안내 덮개를 띄움 (…)" - 기기에서 "띄웠는데 안 보임" 과 "띄우지 않음" 을 가리게.
- DNS 실패 글에 예외 이름 ("TimeoutException:") 을 빼고 이유만 (a4 932).
- 시험: Android 순서 (본문 오류 → 오류 페이지 진행률 0 → 100) 뒤에도 덮개가 남음. 실제 Edge 통합 시험 다시 통과.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:25:27 +09:00
RyuJongJin 0b0f2617ae 250 고침: 덮개를 띄우면 웹 뷰가 새로 만들어져 덮개가 바로 걷히던 것 · 엔진의 본문 오류로도 판단
a4 의 931 기기 확인 (노트 10+): DNS 192.0.2.1 에서 브라우저에 덮개가 보이지 않고 ERR_TUNNEL_CONNECTION_FAILED 만 (버그 수정).
- 원인: 덮개가 없을 때는 웹 뷰를, 있을 때는 Stack 안의 웹 뷰를 돌려 트리 모양이 바뀌었다 → 웹 뷰가 새로 만들어져
  페이지를 다시 읽고, 다시 읽기 시작에서 덮개가 걷혔다. Windows 실제 Edge 통합 시험에서 재현
  ("WindowsInAppWebViewController was used after being disposed"). 늘 같은 Stack 모양으로.
- 엔진이 알려 주는 본문 오류 (onReceivedError, Edge · Android) 도 받아 판단한다: 그 이름의 실패 기록이 있거나,
  DNS 서버에 지금 닿지 않는 중에 프록시 터널 실패 (TUNNEL · PROXY) 면 덮개. 오류 뒤에 오는 읽기 끝이 덮개를 지우지 않는다.
  DNS 강제 중의 본문 오류는 작업 기록에 남긴다 (기기에서 원인을 볼 수 있게).
- 시험: 덮개를 띄워도 웹 뷰를 다시 만들지 않음 · 본문 오류 경로 · DNS 와 상관없는 오류는 엔진 화면 그대로.
  실제 Edge: integration_test/dns_block_real_test.dart (덮개 → [다시 시도] → 다시 덮개) 통과.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:59:07 +09:00
RyuJongJin 614584c78c 193 dnsForced 알림: 8.8.8.8 이 막힌 회사 · 학교망에서는 [시스템 DNS 로 바꾸기] 를 누르라고 한 줄 더
관리자 지시 (10/12, 이번 판에). 버튼 이름은 각 언어의 실제 버튼 글과 같게.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:29:25 +09:00
RyuJongJin 120d20fb86 통합: 묶음5 의 마지막 끝 (433f5b1: 192 · 193 · 194 · 196~198) 을 합침 · 194 · 193 을 Tor · DNS 에도
관리자 지시 (10/12, 이번 판에 넣을 마지막 끝). 풀었던 곳:
- 번역 json: 세 판으로 열쇠마다 합침 (서로 다르게 바뀐 열쇠 0).
- browser_page import: key_hint (194) 와 cef_setup · settings_page · tor_page (tor_dns) 를 모두.
양쪽을 맞춘 것 (작업 세션 판단):
- 194 (단축키 안내는 PC 에서만): Tor 화면의 이전 · 다음 페이지 · 새로고침도 keyHint.
- 193 (그 설정이 없던 예전 판에서 올라오면 바뀐 동작을 한 번 알림): DNS 강제 (처음 값 직접 지정 8.8.8.8 · 8.8.4.4) 가
  없던 판 (10.10 까지) 에서 올라오면 이름을 묻는 서버가 바뀐다 → 'dnsForced' 알림 (되돌리는 곳과 함께). 이미 고른 사람은 알리지 않음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:19:59 +09:00
RyuJongJin 433f5b187a 감지한 언어 알림은 닫을 때까지 · MKV 세부 정보에도 [다른 언어로 다시] (198 보충)
- 관리자 지시: 248 과 같게 버튼이 있는 안내는 닫을 때까지, 알림을 놓쳐도 세부 정보에서 다시 할 수 있게

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:01:45 +09:00
RyuJongJin 36bbee615d 원어를 자동 감지했으면 끝난 뒤 감지한 언어 · [다른 언어로 다시] (198)
- 사용자 편의 (기기 확인): 영어 음성을 한국어로 감지해 영어 발음을 한글로 적은 자막 - 막지는 못하므로 알리고 고른 원어로 다시 만들 수 있게

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:01:45 +09:00
RyuJongJin 65c049d043 작업 기록의 경로는 기기 구분자로 · AI 예상 시간 '약 1~1분' 고침 (196 · 197)
- 196 (버그): Android 에서도 'jj_mkv\이름' - p.join
- 197 (버그): 짧은 동영상의 '약 1~1분' → '1분 안', 같은 수면 하나

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:01:45 +09:00
RyuJongJin 6cb2dde089 그 설정이 없던 예전 판에서 올라오면 탐색기 한 번 누르기 · 화면 방향 · 좌우 밀기 · 백그라운드를 한 번 알림 (193)
- 사용자 편의 (기기 확인, P11 10.03 → 10.10): 한 번 눌렀는데 바로 열려 놀람 - 예전 판에는 키가 없어 알림 조건에 걸리지 않았다. 처음 설치는 알리지 않음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:01:44 +09:00
RyuJongJin 624076f787 예전 판에서 올라오면 바뀐 동작을 한 번 알림: 받는 쪽이 더 새것이면 건너뜀 · robocopy 링크 (193)
- 사용자 편의 (관리자 지시): 기존 사용자에게 동작이 바뀐 것을 '이번 업데이트로 바뀐 기본 설정' 창에

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:01:44 +09:00
RyuJongJin e32cd7d648 단축키 안내 '(Alt+←)' 등은 PC 에서만 - 태블릿 · 폰은 이름만 (194)
- 사용자 편의 · 접근성 (기기 확인): 키보드가 없는 태블릿에 Alt 안내가 보이고 TalkBack 도 읽었다 - keyHint

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:01:43 +09:00
RyuJongJin 69b8ab52ad 해상도 올리기 메모리: 이 기기에서 같은 값이면 같다고 알림 (192)
- 사용자 편의 (기기 확인, P11): 메모리가 작은 기기는 적게 · 보통 · 많이가 모두 가장 작은 타일이라 같은 값이 세 번 보였다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:01:42 +09:00
RyuJongJin 55f5676cf5 통합: 묶음5 의 새 끝 (e81fa6e: 243~246 · 233 · 242 · 241 · 251 · 252 · 시험 흔들림 calm) 을 합침
관리자 지시 (10/12). 겹친 곳 없이 자동으로 합쳐짐 (번역 json · app_actions · home_page · settings_page).
번역 json 은 정렬 · 모양 그대로 확인, 빠진 번역 0, analyze 0, 전체 시험 646 통과.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:49:18 +09:00
RyuJongJin 6c61b8b82d 250 웹 브라우저: DNS 서버에 닿지 않아 못 연 페이지는 이유 · [시스템 DNS 로 바꾸기] [다시 시도]
관리자 지시 (10/12, a4 자유 점검): 엔진 기본 오류 "net::ERR_TUNNEL_CONNECTION_FAILED" 만 보여 안내를 닫은 뒤에는
이유를 알 수 없었다. 앱 안 프록시가 기록한 실패 (NetGuard.dnsUnreachableFor: 이 페이지를 읽기 시작한 뒤 그 이름이
DNS 서버에 닿지 않음 · 아직 다시 답하지 않음) 로 판단해, 읽기가 끝나면 웹 뷰 위에 앱이 그린다.
Edge · Chrome 엔진 · Android 모두 같은 길 (엔진마다 다른 오류 콜백에 기대지 않음). 새로 읽기 시작하면 걷힌다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:49:18 +09:00
RyuJongJin ba39f4bc92 check_apk: 시험판의 versionCode 가 정식 판 (git 태그) 과 같으면 실패 · 정식 판은 -Release
관리자 지시 (10/12): 918 이 --build-number 없이 빌드되어 정식 판 v2026.10.10_001 (261010001) 과 같았다 -
사용자 기기에 깔리면 정식 판으로 보이고 새 버전 확인도 "최신과 같음". aapt2 로 versionCode 를 읽어 태그
(v년.월.일_번호, build.gradle.kts 와 같은 셈) 와 견준다. 통과 줄에 versionCode 도 보인다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:49:18 +09:00
RyuJongJin f0fad6c0e4 249 보충: MKV 화면 작업 기록 위에도 "DNS 서버 (…) 에 닿지 않음 · [시스템 DNS 로 바꾸기]"
a4 의 918 기기 확인 (노트 10+): 작업 현황 (MKV 화면 아래 작업 기록) 에는 줄이 없었다 - WorkPanel 은 브라우저의
오른쪽 작업 현황이고, MKV 화면 아래는 작업 기록 (_LogPanel) 이었다. 작업 기록을 열거나 숨겨도 그 위에 보인다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:49:18 +09:00
RyuJongJin e81fa6e5ba 다운로드 화면 시험을 언어 · 폭별로 · 설정 맨 위 글 · 영어 탐색기 배치 이름 (246 · 251 · 252)
- 246 시험: ko · en · ja × 폭 800 · 1200 (P11 확인에서 영어 · 일본어는 체크가 안 보였음)
- 251 (기기 확인): 영어 설정 맨 위 글이 잘림 - 두 줄까지 · 'Saved automatically'
- 252 (기기 확인): 영어 'Side ⇆' 뜻이 잘 오지 않음 - 'Side by side ⇆' · 'Side by side ⇆ / Stacked ⇅'

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 02:06:46 +09:00
RyuJongJin 8bfcb7ceda MKV 위 막대: 어림으로 두 줄을 정하던 것을 빼고 실제로 잰 것만 (242 보충)
- 버그: 900 에서 한 줄에 들어가는데 어림이 두 줄로 바꿨다 (scale_menu_test)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 02:06:46 +09:00
RyuJongJin a20a7bfe79 시험 흔들림: 탐색기 시험의 pumpAndSettle 을 calm (목록 읽기가 끝난 뒤) 으로
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 02:06:46 +09:00
RyuJongJin 70e6071991 이름 없는 아이콘 버튼에 TalkBack 이름 · 화면 시험 (241)
- 접근성 (기기 확인): 바깥 Tooltip 으로 감싼 아이콘 버튼은 Android 에 이름이 가지 않았다 - 버튼 자체의 tooltip, 탐색기 펼치기 화살표 이름, 설정 줄 MergeSemantics
- a11y_names_test (다섯 화면 · 세 폭) 로 찾은 넘침도 고침: 모니터링 디스크 줄, MKV 인코딩 줄 (960 보다 좁으면 줄 바꿈 - 내 판단, 관리자 승인)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 02:06:45 +09:00
RyuJongJin 2239e70013 다운로드 화면 버튼 줄: 좁으면 다음 줄로 · '완료 시 자동 동영상 추가' 띄어쓰기 (246)
- 버그 (기기 확인): 옆으로 미는 줄이라 앞쪽 글이 잘려 '전체 선택' 과 겹쳤다 (영어 · 일본어는 아예 안 보임) - Wrap

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 02:06:45 +09:00
RyuJongJin c2c523a260 MKV 위 막대: 오른쪽 묶음을 실제로 재어 모자라면 두 줄 · 넘쳐도 화면 크기 칸을 덮지 않게 (242)
- 버그 (기기 확인, 노트 10+ 가로): 작업 중 폴더 버튼이 화면 크기 '100%' 를 덮었다 - 어림한 폭이 실제보다 작았음
- AppTopBar trailing (CustomMultiChildLayout 으로 실제 폭) · minMiddleWidth 로 두 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 02:06:45 +09:00
RyuJongJin 5a0bc0ec50 이동 카드 저장 창은 '이 이동을' · '받는 쪽이 더 새것이면 건너뛰기' 를 Rsync 탭에도 (233)
- 233 (버그): 이동 카드인데 '이 복사를' 로 묻던 것
- 관리자 지시: 229 설정이 Rsync 화면 · 모니터링에서도 쓰이므로 Rsync 설정 탭에서도 같은 스위치

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 02:06:44 +09:00
RyuJongJin 4cad606fe1 시험 흔들림: WebDAV 즐겨찾기 시험은 창이 뜰 때까지 기다림
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 02:06:44 +09:00
RyuJongJin 01ef79f86f 앱이 직접 할 때의 파일 오류도 화면 언어로 (245)
- 버그 (기기 확인): 현재 방식 실패 글이 'Cannot delete file …' 영어 - 원본을 못 지웠으면 rsync 때 (215) 와 같은 글, 그 밖도 짧은 한글 + 경로 + OS 오류

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 02:06:44 +09:00
RyuJongJin 58bc8fa35b lsync 로 옮길 때 원본을 지우지 않음 안내 · 대상에만 있는 것을 사실대로 (243 · 244)
- 243 (사용자 편의, 기기 확인): 이동 카드를 lsync 로 옮기면 원본이 남아 헷갈렸다 (107: 실시간 동기화는 복사로 맞춤) - 확인 창에 알리고 쌍에 '복사로 맞춤'
- 244 (버그, 기기 확인): 대상에만 있는 파일이 있는데 '같습니다' - 대상에만 있는 수를 세어 사실대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 02:06:43 +09:00
RyuJongJin cdfcf360c4 통합: tor_dns (Tor 브라우저 · DNS 강제 · 228 · 247~249) 를 묶음5 (aa7a1af) 에 합침
관리자 지시 (10/12, 다음 릴리스 준비). 겹친 곳은 양쪽 뜻을 살려 풀었다:
- 번역 json (en · ja · zh): 세 판 (공통 · 양쪽) 으로 열쇠마다 합침 (서로 다른 값으로 바뀐 열쇠 0개).
- 225 (모르는 컴포넌트 · 화면 순서 남기기, 양쪽에 따로 들어감) 를 하나로:
  settings.fromJson 은 tor_dns 쪽 (문자열만 남김), 순서 바꾸기는 묶음5 의 AppComponent.reorder (모르는 화면은 그 자리에)
  + tor_dns 의 fullOrder (Tor 는 웹 브라우저 바로 뒤, 켜지 않은 Tor 는 순서에 넣지 않음).
  시험은 둘 다 남기고, 이제 tor 를 아는 판이라 "모르는 화면" 은 future_screen 으로.
- build.gradle.kts: Tor jniLibs 폴더 + 195 의 third_party_libs.txt 거르기에 tor 폴더 포함, 목록에 libtor.so 추가.
  check_apk.ps1 도 tor 폴더를 본다.
- 201 (브라우저 위 막대 두 줄) 을 Tor 화면에도 (minMiddleWidth 380).
- live_sync · sync_tools · explorer_page · 흔들림 시험 (165 등) 은 tor_dns 가 건드리지 않아 묶음5 그대로.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 01:53:32 +09:00
RyuJongJin 6470368c2e 닿지 않는 DNS: 새 버전 확인 실패를 사용자 글로 · 안내는 닫을 때까지 · 상태를 설정 · 작업 현황에 (247 · 248 · 249)
테스터 a4 의 914 기기 확인 · 관리자 지시 (10/12, 버그 수정 · 편의):
- 247: 새 버전 확인 실패가 영어 원문 (HttpException: Proxy failed to establish tunnel (502 ...)) 이던 것.
  DNS 강제 프록시에서 실패했으면 "DNS 서버 (…) 에 닿지 않아 새 버전을 확인하지 못했습니다" (원문은 작업 기록).
- 248: [시스템 DNS 로 바꾸기] 가 붙은 안내가 12초 만에 사라지던 것 → 사용자가 닫거나 누를 때까지.
- 249: 안내가 1분마다 다시 뜨던 것 → 처음 실패 때 한 번. 상태는 서버가 다시 답하거나 DNS 설정을 바꿀 때까지
  환경 설정 > 네트워크 · DNS 와 작업 현황에 "DNS 서버 (…) 에 닿지 않음 (시각부터) · [시스템 DNS 로 바꾸기]".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 01:27:00 +09:00
RyuJongJin 893f6759ff 중복 실행 막기 (228): 데이터 폴더의 잠금 파일 (instance.lock, 핸들 단위) · 포트 0 + 비밀값 · 응답 없으면 묻기
관리자 검토 (10/12): 이름 있는 뮤텍스는 쥔 OS 스레드가 끝나면 버려진 상태가 되어 다음 실행이 첫 실행으로
판단할 수 있다 → 데이터 폴더의 instance.lock 을 파일 핸들 잠금 (LockFileEx) 으로 앱이 끝날 때까지 쥔다.
스레드와 상관없고 프로세스가 죽으면 Windows 가 푼다. GetLastError · FFI 를 쓰지 않는다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 01:26:59 +09:00
RyuJongJin aa7a1af27c 모르는 컴포넌트 (다른 판의 Tor 등) 는 설정 · 화면 순서에서 지우지 않음 (225)
- 데이터 (설정): 다른 판으로 오가면 Tor 컴포넌트 켜짐이 사라졌다 - 모르는 id 를 남기고 (40), 순서를 바꿀 때도 그 자리에 (AppComponent.reorder)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:51:45 +09:00
RyuJongJin 3a9f09a6eb 실시간 동기화: dispose 뒤에 맞추기가 끝나도 알리지 않음
- 버그 (관리자 전체 시험에서 발견): 앱을 끄거나 화면을 닫는 중에 맞추기가 끝나면 used after disposed 오류
- 시험은 정해진 4초가 아니라 맞추기가 끝날 때까지 기다리고, 끝난 뒤 지움 (흔들림)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:51:44 +09:00
RyuJongJin 037ddf502a 탐색기: 앱으로 돌아오면 보이는 폴더를 다시 읽기 (232)
- 버그 (기기 확인): 다른 앱에서 바꾸고 돌아와도 예전 목록이 남았다 - 돌아올 때 [새로 고침] 과 같게 (WebDAV 는 빼고 - 관리자 판단 승인)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:51:44 +09:00
RyuJongJin b0c3a4cee2 240 화면 시험: 여럿이 돌 때 ■ 창 · 줄의 ■
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:51:43 +09:00
RyuJongJin 54c4d6f97f MKV 취소 창은 창을 연 때의 동영상만 취소 · 여럿이 돌면 묻기 · 줄마다 ■ (239 · 240)
- 239 (버그, 기기 확인): 창을 보는 사이 다음 동영상이 시작하면 [지금 것만 취소] 가 그 다음 것을 취소했다 - FFmpeg 실행에 꼬리표 (MediaTool tag · cancelTagged), 이미 끝났으면 알리고 다시 묻기
- 240 (사용자 편의, 기기 확인): 여럿이 동시에 돌면 [그대로 두기] · [모두 취소 (n개)], 목록 줄의 ■ 로 하나만

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:51:43 +09:00
RyuJongJin 976f4633aa robocopy 복사도 링크 · junction 을 따라가지 않음 (/XJ 처음 값)
- 데이터 (관리자 지시): 따라가면 원본 바깥 파일을 복사하거나 고리 junction 을 끝없이 돈다 - rsync -a 처럼 복사 · 이동 · lsync 모두 /XJ
- 옵션 칸에 사용자가 넣은 링크 옵션 (/XJ · /XJD · /XJF · /SJ · /SL) 은 그대로. 명령 줄 · 미리 보기도 같게 (robocopyExtraOptions)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:51:43 +09:00
RyuJongJin b9739fd71c 모니터링 카드 실패 줄 [자세히]: 원문 (명령 줄 포함) 보기 · 복사 (237 보충)
- 사용자 편의 (관리자 지시): 원인을 물어볼 때 쓰게 원문을 CopyTask.lastDetail 에 두고 보기 · 복사 (비밀은 가림)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:51:42 +09:00
RyuJongJin f1206ae1f2 robocopy 이동은 원본 안의 링크 · junction 을 따라가지 않음 (/XJ, 데이터)
- 데이터 (버그, 개발 중 Windows 에서 재현): robocopy /E /MOVE 가 junction 을 따라가 원본 바깥 폴더의 파일을 옮기고 지웠다
- 이동에 /XJ - 링크는 원본에 남긴다 (앱이 직접 할 때 214 와 같게). rsync 는 링크를 링크로 옮겨 문제없음 (확인)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:51:42 +09:00
RyuJongJin d86a929ba1 미리 보기: 원본의 링크 · junction 은 따라가지 않음 (231)
- 버그: 미리 보기가 링크 안으로 들어가 실제로는 건너뛰는 (214) 바깥 파일을 옮길 것으로 보였다
- 앱이 직접 (현재 방식 · WebDAV) 이면 '링크 · junction - 따라가지 않고 건너뜀', rsync 는 링크 하나로 셈

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:51:42 +09:00
RyuJongJin 620cd63e34 Chrome 엔진: 관리자 권한이면 시작 안 함 (설정으로 허용) · 20초 안전망 · 인증 없는 숨긴 문 (207) · DNS 강제를 앱 시작 맨 앞에
- 207 원인: Chromium 은 관리자 권한으로 실행되면 권한을 낮춰 다시 띄우려고 스스로 끝난다 (CefInitialize false, 종료 코드 38).
  관리자 판단 (보안 우선): 관리자 권한이면 Chrome 엔진을 시작하지 않고 이유 · [Edge 엔진으로 바꾸기] · [그래도 Chrome 엔진 쓰기…] (경고 확인).
  환경 설정 > 브라우저 "관리자 권한일 때도 Chrome 엔진 쓰기 (권장하지 않음)" (처음 꺼짐) 를 켜면 --do-not-de-elevate (webview_cef 패치 7).
- 20초 안에 준비되지 않으면 "Chrome 엔진을 시작하지 못했습니다" · [Edge 엔진으로 바꾸기] · [다시 시도]. 시작 중에 닫을 때의 LateInitializationError 고침.
- Chrome 엔진은 프록시 인증에 답하지 않아 (CEF 149 windowless 에서 GetAuthCredentials 가 불리지 않음, 확인) 인증 없는 문을
  실행마다 고른 127.x.y.z 의 빈 포트에, Chrome 엔진 웹 뷰가 있는 동안만 연다 (관리자 결정). 듣지 않던 옛 인증 패치는 걷어냄.
- DNS 강제: HttpOverrides 를 main · runAndroid · runSecondWindow 맨 앞에서 건다. 그 전에 만들어 두던 HttpClient
  (새 버전 확인 · 구성 요소 · 도구 받기 · 자막 검색) 가 정한 DNS 를 거치지 않고 시스템 DNS 로 나가던 것 (닿지 않는 DNS 시험판에서 발견).
- 안내 문구: DNS 서버에 닿지 않음은 예외 원문 대신 무엇을 확인할지, Tor 연결이 2분 넘게 멈추면 몇 % 에서 멈췄는지와 [다시 시도].
- 시험: cef_elevation_test (4), dns_proxy_test (Chrome 엔진 문 · 일찍 만든 HttpClient 도 정한 DNS).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:23:20 +09:00
RyuJongJin 41d4d8afbf 모니터링 [실행] 뒤 옵션 칸 포커스 풀기 · 실패 글에 예외 이름 없이 (236 · 237)
- 236 (버그, 기기 확인): 실행 뒤 포커스가 옵션 칸에 남아 키보드가 올라와 끝 알림을 가렸다
- 237 (사용자 편의, 기기 확인): 카드 실패 글에서 'ProcessException:' · Command 줄을 뺌 (userErrorText). 원문은 작업 기록에

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:11:34 +09:00
RyuJongJin 90246717ec robocopy 도 '받는 쪽이 더 새것이면 건너뛰기' 를 따름 (/XO) (229)
- 데이터 (버그, 관리자 지적): robocopy 는 기본으로 예전 원본 (Older) 으로 대상의 새 파일을 덮어썼다 - 설정이 켜져 있으면 /XO
- 건너뛴 원본은 남기고 알림, 미리 보기 · 명령 줄도 같게. 설정 설명 글에 robocopy 를 넣음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:11:33 +09:00
RyuJongJin d8ce0eb302 robocopy 이동은 만든 시각 · 속성만 다른 같은 파일도 옮김 · 카드에 원본에 남은 수 · 명령 줄 (217 · 235 · 238)
- 데이터 (버그, 기기 확인): robocopy 가 'Modified' (만든 시각만 다름) · 'Tweaked' 를 /IS 로도 건너뛰어 원본에 남기고 성공처럼 보였다 - 이동에 /IT /IM 도 붙임
- 235 (버그): 이동 뒤 원본에 남은 수를 카드에 (CopyTask.lastLeft, 주황 경고) · 파일 수는 옮긴 수로 (doneFiles)
- 238 (버그): 확인 창 · 카드 명령 줄에 실제로 붙는 /MOVE /IS /IT /IM
- 시험 기대값: sync_tools_test · dismiss_test 의 robocopy 옵션 (관리자 승인)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:11:32 +09:00
RyuJongJin d991ef91c1 시험 흔들림: 여러 개 표시 시험은 목록 다시 읽기가 끝난 뒤 새 폴더 · 이름 변경
- 무거운 전체 실행에서 pumpAndSettle 이 창들의 다시 읽기 진행 막대 때문에 시간 초과 - idle() 로 실제 시간 기다림 (시험만, 관리자 승인)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:11:32 +09:00
RyuJongJin 8c2ddc3385 이동 확인 창의 명령 줄은 실제로 실행할 것 (230)
- 버그 (테스터 지적): 모니터링 이동 카드 [실행] 확인 창이 칸의 옵션을 그대로 보여 --remove-source-files 가 빠져 보였다
- effectiveRunText 를 sync_preview_view 로 옮겨 확인 창도 같은 글을 쓴다 (원본 지우기 옵션은 이동일 때만)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:11:31 +09:00
RyuJongJin c070486c84 앱 직접 복사 · 이동은 받는 쪽이 더 새것이면 건너뜀 · 미리 보기 경고 (229)
- 데이터 (버그): 현재 방식 · WebDAV 의 이동이 받는 쪽의 더 새 파일을 예전 원본으로 덮어쓰고 원본까지 지웠다
- 환경 설정 '받는 쪽이 더 새것이면 건너뛰기' (copySkipNewer, 처음 값 켜짐 - 관리자 판단). rsync -u 와 같음
- 건너뛴 파일은 이동이라도 원본에 남기고 'n개는 원본에 남음' 으로 알림
- 미리 보기: 건너뛰는 것 (받는 쪽이 더 새것 - 건너뜀), 더 새 파일을 덮어쓸 때 주황 굵은 경고
- 시험 sync_preview_test 기대값: -u 일 때 leftold.txt 는 목록에서 빠지지 않고 skipNewer 로 보임 (관리자 승인)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:47:58 +09:00
RyuJongJin 27d50891d0 MKV 묶음 취소: 지금 것만 / 남은 것 모두 / 그대로 · 취소됨 상태 (186 · 187)
- 186 (사용자 편의, 기기 확인 지적): 묶음 실행 중 취소를 누르면 기다리는 항목이 있을 때 세 가지 중 고르기
- 187 (버그): 취소한 항목이 실패로 보이던 것 - JobStatus.cancelled, 회색으로 보이고 기록에 성공 · 취소 · 실패 / 전체

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:47:57 +09:00
RyuJongJin c9c4f031b5 폰 가로 브라우저 · 다운로드 주소 칸: 모자라면 위 막대 두 줄 (201 · 200)
- 버그 (기기 확인 지적): 가로 폰에서 주소 칸이 너무 좁거나 보이지 않던 것
- AppTopBar 가 가운데 폭을 재고 minMiddleWidth (브라우저 380 · 다운로드 420) 보다 좁으면 두 줄로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:47:57 +09:00
RyuJongJin 487d955a99 Rsync 화면은 rsync 로 · lsync 로 옮길 때 이동 유지 (218 · 219)
- 218 (버그): Rsync 화면에서 만든 작업이 기억된 다른 방법으로 돌던 것 - 늘 rsync 로
- 219 (버그): 모니터링에서 lsync 로 옮길 때 이동 · 안의 것 · 원본 정리 · 한 번에를 그대로 넘김

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:47:57 +09:00
RyuJongJin 00d78f4853 dns TC 시험: 실패하면 오류 · 가짜 DNS 기록을 남김
전체 실행에서만 한 번씩 실패한 "잘린 답 (TC) 은 TCP 로 다시 묻는다" 의 원인을 잡으려고, 실패하면 예외 · 종류 · errno 와
가짜 DNS 의 bind 시도 · TCP 받음 기록 · 질문을 출력한다 (wt_mgr 세 번 · wt_tor 두 번 전체 실행에서는 재현되지 않음).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:33:11 +09:00
RyuJongJin c0397e9fe1 미리 보기에 덮어쓸 것을 눈에 띄게 (211)
관리자 지시: 대상에 내용이 다른 같은 이름이 있으면 목록에 "~ 이름" 으로만 보였다. 요약을 "덮어씀 n" 으로,
"받는 쪽에서 n개를 덮어씁니다 (내용이 다른 같은 이름 - 원래 것은 없어짐)" 주황 줄과 목록의 "(덮어씀)" 을 더한다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:14:25 +09:00
RyuJongJin 64ce867612 robocopy 이동은 같은 파일도 포함 (/IS) (217)
관리자 지시 (Windows 실제 확인): 대상에 같은 파일이 있으면 robocopy /MOVE 는 건너뛰고 원본에 남긴다 (종료 코드 0 → "옮겼습니다").
이동일 때 /IS 를 붙여 옮긴다 (사용자가 이미 넣었으면 그대로). 속성만 다른 "수정됨" 파일은 /IS · /IT 로도 남아, 216 의 "원본에 남음" 으로 알린다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:14:25 +09:00
RyuJongJin b0d7f021f7 rsync 실패 글에 원본을 못 지운 이유 · 오류 줄 (215)
관리자 지시: 실패 글이 출력의 앞쪽 8줄이라 전송한 파일 이름만 보이고 "Permission denied" · "rsync error … (code 23)" 가 빠지기 쉬웠다.
오류 출력 (stderr) 을 따로 모아 오류 · 권한 줄을 끝쪽부터 보이고, 원본을 못 지웠으면 (failed to remove 등) 쉬운 말 한 줄을 앞에 붙인다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:14:24 +09:00
RyuJongJin 44663ec2b9 이동 뒤 원본에 남은 파일을 다시 세어 알리기 (216) · 끝 알림에 이동 / 복사 · 개수 (212)
- 216 (관리자 지시, 205 점검에서 찾음): -u · --exclude · 대상이 더 새것 등으로 건너뛴 파일은 원본에 남는데 "끝" 으로만 보였다.
  이동이 끝나면 원본에 남은 파일을 다시 세어 "n개는 원본에 남음 (건너뜀)" 으로 알린다 (끝 알림 · 모니터링 카드 · 작업 기록).
- 212 (관리자 지시): 끝 알림을 "이동 끝: n개 · 원본 → 대상" / "복사 끝: n개 · …" 로.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:14:23 +09:00
RyuJongJin 37d7c0da31 모니터링 카드에 실제로 실행할 것 · 이동 / 복사 (209)
관리자 지시: 카드의 옵션 칸은 저장된 원문이라 이동 카드는 "-avPog", 복사 카드는 "… --remove-source-files" (실제로는 빠짐) 로 거꾸로 읽혔다.
칸 옆에 "이동 · 실제 실행: rsync -avPog --remove-source-files" / "복사 · 실제 실행: rsync -avPog" 를 보이고,
복사 카드 칸에 원본 지우기 옵션이 있으면 쓰지 않는다고 알린다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:14:23 +09:00
RyuJongJin e4b6e33a97 모르는 구성 요소 보존 (225) · Tor 대기 표시 (226) · 막은 파일 주소 되돌림 (227) · 포트 다시 잡기
- 225 (기기 확인 a4): Tor 를 모르는 판을 거치면 구성 요소 'tor' 가 지워지던 것. 설정을 읽을 때 components · navOrder 의
  모르는 id 를 버리지 않고 그대로 둔다 (40 과 같은 원칙). 화면 · 버튼은 아는 것만. 화면 순서를 바꿔 저장할 때도 모르는 id 를 남긴다.
- 226: Tor 화면을 떠난 뒤 tor 를 켜 두는 동안 "Tor 대기 중 (n분 뒤 끔)" 을 환경 설정 > Tor 와 일반 브라우저 띠에 [지금 끄기] 와 함께.
- 227: Tor 화면에서 막은 파일 링크 뒤 주소 칸을 마지막으로 보인 페이지 주소로 되돌린다.
- 포트 (관리자 지적): Windows 는 TCP · UDP 마다 막아 둔 포트 범위 (Hyper-V · WSL) 가 있어 번호를 정해 bind 하면 10013 이 날 수 있다.
  시험의 가짜 DNS 는 TCP 를 OS 가 고른 번호로 먼저 열고 그 번호로 UDP (안 되면 다시). aria2 는 넘긴 번호를 열지 못해 끝나면 새 번호로 다시 (2번).
  앱 안 프록시 · Tor 다리 (같은 번호 → 실패하면 OS 가 고름), Tor (auto), Android 웹 뷰 문 (0) 은 이미 그렇다.
- analysis_options: packages/whisper_ggml 도 분석에서 뺌 (자기 analysis_options 가 flutter_lints 를 찾지 못함).
- 시험: components_test 1 · tor_test 2 (226 · 227).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:56:22 +09:00
RyuJongJin f59e2f75a1 이동 경고 문구 (222) · 자동 두 쪽 나누기는 두 쪽 스캔일 때만 (223) · 보는 동안 지운 그림 (224)
- 222 (관리자 지시): 원본이 대상 안일 때 이동이면 "옮겨집니다 (원본 파일은 지워지고, 원본 폴더는 아래에서 고른 대로)" - 예전에는 이동일 때도 "복사됩니다 · 원본 폴더 그대로".
- 223 (관리자 지시): 보기 화면 "두 쪽 나눠 보기: 자동" 이 평범한 가로 사진도 반으로 잘랐다. ZIP · CBZ · PDF 묶음의 가로 그림은 예전처럼 나누고,
  폴더의 그림은 다른 그림 대부분 (3/4 이상, 2장 이상) 이 세로일 때만 나눈다. 나눠 볼 때 위 막대 맨 앞에 [두 쪽 나눔 끄기]. 설정 값 · 처음 값은 그대로.
- 224 (관리자 지시): 보기 화면이 앱으로 돌아올 때 폴더의 그림 중 없어진 것을 목록에서 빼고, 보던 그림이 없어졌으면 "파일이 없어졌습니다" 와 다음 그림, 다 없으면 닫는다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:43:32 +09:00
RyuJongJin 3ffaf23955 설정 글 칸이 입력 중에 끊기지 않게 · 키보드가 열리면 위 줄 접기 (213) · 좁은 창 위 줄 기준 480 (221)
- 213 (관리자 지시, 입력이 안 됨): 설정의 rsync · robocopy 옵션 칸이 값이 기본값과 같아지거나 달라지는 순간 새로 만들어져 포커스를 잃었다.
  칸마다 컨트롤러를 두고 고정 key, ↺ 는 칸의 글을 바꾼다.
- 213: 폰 가로에서 키보드가 열려 남는 높이가 360 미만이면 설정 화면의 위 줄 둘 (막대 · 묶음 바로가기) 을 접는다.
- 221 (관리자 지시): 폰 세로 (창 약 400dp) 에서 창 위 줄이 넓은 배치라 탭 칸이 130dp 남짓이어서 두 번째 저장소 (SD 카드) 가 끝에서 잘려 보였다.
  넓은 배치 기준을 320 → 480 으로 올려, 그보다 좁으면 끝 버튼을 [⋮] 하나로 모으고 탭 칸을 넓힌다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:43:32 +09:00
RyuJongJin 697235073e Tor · DNS 보완: 앱 안 프록시 인증 · Android 웹 뷰 문 · Tor 화면 떠남 정리 · 다운로드 막기
관리자 결정 (10/11) · 기기 확인 (노트 10+, 911 시험판 1~6 통과) 에 따라 0d1ea93 을 보완.
- 앱 안 프록시 (DNS 강제 · Tor 다리) 인증: 실행마다 새 무작위 비밀값 (저장 안 함) 으로 Basic Proxy-Authorization, 틀리면 407.
  HttpClient (findProxy) · yt-dlp (--proxy) · aria2 (--all-proxy-user/passwd) · Edge (WebView2 BasicAuthenticationRequested) ·
  Chrome 엔진 (webview_cef 패치 7, GetAuthCredentials) 이 이 프록시에만 답한다. 127.0.0.1 에만 묶음.
- Android 웹 뷰는 프록시 인증에 답하지 못해 (기기 확인) 인증 없는 "문" 을 따로: 실행마다 고른 127.x.y.z 의 빈 포트,
  웹 뷰 화면 (일반 브라우저 · Tor 화면) 이 맨 앞에 보이고 앱이 앞일 때만 연다 (그 밖엔 닫힌 포트 127.0.0.1:9).
  "다른 화면으로 가면 소리 멈춤" 을 끈 사용자는 일반 브라우저가 살아 있는 동안 DNS 문 유지 (사용자 설정을 따름). Tor 문은 따로.
- Tor 화면을 떠나면 (다른 화면 · 앱이 뒤로) 바로 웹 뷰 · 쿠키 정리, 프록시 · 다리 되돌림. tor 는 설정 시간 (처음 2분, 0 = 바로) 뒤 끔.
  남은 Tor 웹 뷰가 있는 동안은 Tor 문 · 닫힌 포트만 건다 (앱이 뒤에 있어 화면을 그리지 않아도 일반 쪽으로 나가지 않게).
- Tor 화면 파일 링크는 받지 않고 안내 (Windows: flutter_inappwebview_windows 0.6.0 사본에 DownloadStarting, JJ_PATCH.md · 고지).
- 시험: web_proxy_gate_test (새), tor_test · dns_proxy_test 보강, integration_test tor_dns_real_test 보강 · cef_dns_real_test (새, 207 확인용).
- analysis_options: packages/flutter_inappwebview_windows 를 분석에서 뺌 (다른 고친 외부 부품과 같게).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:36:32 +09:00
RyuJongJin 954b86b84c 모니터링 [실행] 의 이동도 미리 보기 · 확인 (210)
관리자 지시 (데이터): 모니터링 카드의 [실행] 은 따로 묻는 창이 없어 이동 (원본 지우기) 을 곧바로 했다.
이동이면 "원본을 지우는 작업입니다 (이동)" 창에 옮길 목록을 보이고, 목록이 나온 뒤에만 [실행]. 복사만은 지금처럼 바로.
탐색기 이동 · Rsync 화면 · [최종 정리] 는 이미 자기 확인 창이 있어 두 번 묻지 않는다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:39:51 +09:00
RyuJongJin cd00d3ce3a 앱이 직접 하는 이동은 대상에 맞춘 원본 파일만 지운다 (214)
관리자 지시 (P0 데이터, 205 점검에서 찾음): WebDAV 가 끼거나 현재 방식으로 하는 "안의 것" 이동이 맞춘 뒤 원본 폴더를 다시 읽어 모든 파일을 지웠다.
- 맞추기 (mirror) 가 지금 복사했거나 같다고 확인한 원본 파일만 알리고 (onSettled), 그 파일만 지운다.
  -u 로 건너뛴 것 (대상이 더 새것) · 맞추는 동안 새로 생긴 것 · 맞춘 뒤 바뀐 것은 원본에 남긴다.
- 이동의 같음은 크기 + 바뀐 때 (±2초) 로만 본다 (WebDAV 의 "크기 같고 대상이 더 새것" 은 다시 보낸다 - 내용이 다른데 원본만 지우지 않게)
- 로컬 원본의 링크 · junction 은 따라가지 않는다 (WebDAV 로 맞출 때도)
- 시험: 건너뛴 것 · 도중에 생긴 것 · 링크 (로컬 · WebDAV) · 크기만 같고 내용이 다른 대상

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:39:51 +09:00
RyuJongJin 3ad8becafa 폴더 안의 것을 바로 위 폴더로 이동이 조용히 안 되던 것 (205)
사용자 지적 "rsync 로 이동이 안 되는 경우가 있어" (버그 수정): Rsync 화면 (원본/ → 대상/) 에서 바로 위 폴더로 이동하면
실행 때의 검사가 폴더째 옮기는 규칙 ("이미 이 폴더에 있습니다") 으로 막아, 알림 없이 아무 일도 하지 않았다.
안의 것을 옮길 때 (contents) 는 이 검사를 하지 않는다. 그래도 시작하지 못하면 이유를 화면 · 작업 기록에 알린다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:39:50 +09:00
RyuJongJin 996491547b 좁은 Rsync 화면은 창 하나씩 (191) · 방향 실행 버튼을 앞에 (208)
- 191 (관리자 지시): 좌우 두 창인데 폭이 560 보다 좁으면 (폴드 접은 화면 · 폰 세로) 창이 140~330px 라 폴더 이름이 잘렸다.
  창 하나씩 보이고 위에 [왼쪽 · 폴더 | 오른쪽 · 폴더] 로 바꿔 본다. 버튼 줄은 가로, 이름은 창 배치대로 (좌 → 우).
- 208 (관리자 지시): Rsync 버튼 순서를 [좌 → 우 · 좌 ← 우 · 좌 ⇄ 우 · 모니터링 · …] 로 - 좁아서 [더 보기] 로 가는 것은 뒤쪽

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:39:50 +09:00
RyuJongJin a94009524c Windows 경로 입력의 smb:// · cifs:// 를 \서버\공유 로 (190)
관리자 지시 (179 보충): 주소가 틀린 줄 알지 않게, Windows 는 smb://서버/공유 (cifs:// 도) 를 \서버\공유 로 바꿔 연다.
%20 같은 주소 글자는 풀고 한글은 그대로. 바꿔도 못 열면 "Windows 의 네트워크 공유는 \서버\공유 형식 (바꾼 경로)" 를 알린다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:39:50 +09:00
RyuJongJin df508e3d2d 탐색기 탭 줄이 넘쳐도 경로 입력 · WebDAV (202) · 모르는 크기 · 날짜 "—" (203) · E10 시험 건너뛰기
- 202 (관리자 지시, 사용 막힘): 빈 드라이브가 많은 PC 에서 창 위 줄이 넘쳐 [경로 입력] · WebDAV 를 누를 수 없었다.
  [← → ★] · 저장 장치 탭 (이것만 옆으로, 마우스 휠 · 끌기) · [WebDAV · 경로 입력] (늘 보임) 으로 나눈다.
  비어 있거나 연결 안 된 드라이브는 숨기지 않고 뒤쪽으로. 창이 320 보다 좁으면 끝 버튼은 [⋮] 하나로.
- 203: 잠긴 시스템 파일의 크기 · 바뀐 날 ("-1B · 1970-01-01") 은 "—"
- 시험: 권한 없는 폴더 (E10) 가 읽히는 환경 (Git Bash · MSYS 는 백업 권한을 켬) 에서는 이유를 남기고 건너뜀.
  explorer_parallel_test 37 · 122 는 202 때문에 [경로 입력] 을 못 찾던 것 - 이제 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:39:50 +09:00
RyuJongJin 850b148d9e ARMv8.0 기기용 AI 엔진 · 신호별 문구 · 받기 전 검사 · APK 네이티브 목록 (195)
관리자 지시 (정식 판 문제, P11 = Snapdragon 662 에서 해상도 올리기가 "코드 -4" 로 바로 멈춤): sd-cli 를 armv8.2 (dotprod · fp16 · i8mm) 로만
빌드해 dotprod · fp16 이 없는 CPU 에서 SIGILL 로 죽었다.
- tool/build_sdcpp_android.sh [v82|v80]: v80 (armv8-a) 판 libsdcli_v80.so 를 더한다 (APK +15MB).
  앱은 /proc/cpuinfo 의 모든 코어 Features 에 asimddp · fphp 가 있을 때만 빠른 판, 아니면 (모름 포함) v80 판.
- 엔진이 신호로 죽으면: SIGILL → "이 기기의 CPU 가 이 AI 엔진을 지원하지 않습니다", SIGKILL → 메모리, 그 밖은 코드만 (메모리 · 파일 탓이라고 하지 않음)
- 모델을 받기 전에 엔진을 --help 로 한 번 실행해 보고, 못 돌면 받지 않고 이유를 알린다. 결과는 작업 기록에 한 줄 ("AI 엔진 확인: 판 · 결과")
- APK 에 넣을 third_party 실행 파일을 android/app/third_party_libs.txt 로 정한다 (gradle 은 목록 밖을 빼고, check_apk.ps1 이 목록과 APK 를 비교)
- 기기 확인 (a4): P11 "libsdcli_v80.so · 통과", 해상도 올리기 약 5분 30초 · 저장. 노트 10+ (dotprod 있음) 는 빠른 판 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:39:50 +09:00
RyuJongJin 7df43a750e 트리 화살표 누르는 영역: 줄 높이 전체 · Android 는 폭 44 (189 보충)
189 로 접기 · 펼치기를 화살표로만 하게 되어, 아이콘 크기 (18px) 만 눌리던 것을 넓혔다 (관리자 지시: 손가락으로 누르기 충분히).
Windows 는 폭 20 그대로 (배치가 바뀌지 않게). 시험: Android 흉내로 영역 크기 · 가장자리를 눌러도 펼침.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:19:37 +09:00
RyuJongJin a7632a9bed 탐색기 뒤로 · 앞으로를 실제 Windows 에서 한 칸씩 (75) · 이름으로 접지 않기 (189)
- 75 (버그 수정, 실제 Windows 빌드에서 재현): 창 위의 마우스 4 · 5 누름을 창과 화면이 둘 다 받아 두 칸씩 움직였다
  (받는 곳마다 좌표를 바꾼 새 이벤트라 identical 로 막히지 않음) → 누른 손가락 · 시각으로 한 번만.
  이동이 끝나기 전에 다시 불리면 무시 (내역에서 두 칸이 빠지지 않게).
- 75: 지운 폴더로 뒤로 · 앞으로 하면 그 상위 폴더를 두 창 모두 다시 읽는다 (트리에 지운 폴더가 남지 않게)
- 189 (관리자 지시): 폴더 이름을 누르면 그 폴더로 (닫혀 있으면 펼침), 이름으로는 접지 않는다.
  다시 누르거나 두 번 눌러도 아래 줄이 움직이지 않게. 접기 · 펼치기는 화살표로 (Windows · Android 같게)
- 시험: 이름을 다시 · 두 번 눌러도 그대로, 화살표로 접기. 지운 폴더가 트리에서도 사라짐

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:48:20 +09:00
RyuJongJin 3639aa9cbf APK 검사: dex 에 GeneratedPluginRegistrant 가 있는지 (tool/check_apk.ps1)
묶음5 시험판 (a192ef6) 이 켜자마자 꺼졌다 ('JNI is not initialized … too early'). 생성 파일 GeneratedPluginRegistrant.java 를 지운 뒤
flutter build apk --no-pub 로 빌드해 플러그인 등록이 dex 에서 빠진 것 (버그 수정 - 빌드 절차). 빌드 뒤 이 검사로 막는다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:49:04 +09:00
RyuJongJin e9f7e5d819 복사 · 이동해도 목록 자리 · 스크롤 그대로 (180) · [새로 고침] 두 창 모두 (183)
- 창 (왼쪽 · 오른쪽 · 파일 목록) 마다 같은 GlobalKey: 창 크기 비율이 바뀌어 두 창이 가로 ↔ 세로로 바뀌어도 목록을 새로 만들지 않는다 (스크롤이 맨 위로 가던 것)
- 읽는 중 막대는 목록 위에 겹쳐 그린다 (끼어들어 목록이 밀리지 않게)
- 폴더 + 파일 목록: 다시 읽는 동안 앞의 목록을 그대로 (처음 읽을 때만 빙글이)
- 복사 · 이동이 끝나면 표시만 지우고 선택 모드는 그대로. Esc 로 선택 모드 · 표시 풀기 (뒤로 키는 전과 같이)
- 보던 폴더로 스크롤: 줄이 막 늘어난 때의 낡은 끝 값으로 자르지 않는다
- 183: [새로 고침] 은 두 창 모두, 목록을 비우지 않고 새로 읽은 것으로 바꾼다
- 시험: explorer_steady_test (매 프레임 자리 · 스크롤, 배치 전환, 선택 모드 · Esc, 두 창 새로 고침). 37-2 는 고정 횟수 대신 될 때까지 기다림

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:49:04 +09:00
RyuJongJin 5e5182178b 저장 줄 · 복사 창 안내 · Android SMB 안내 (176 · 178 보충, 179)
- [보기] 로 비교 후 저장하면 작업 현황 줄이 "저장했습니다: 경로" + [열기] (저장하지 않으면 "…: 저장하지 않았습니다")
- 복사 창: 설정 옵션의 원본 지우기 옵션 (/MOV · --remove-source-files 등) 은 복사에서 쓰지 않는다고 알림 (이동은 [이동])
- 179: Android 경로 입력에 smb:// · \서버 를 넣으면 "네트워크 공유 (SMB) 는 아직 지원하지 않습니다. WebDAV 탭을 쓰세요" + [WebDAV 서버 추가]
- 시험: 저장 줄 · 복사 창 안내 · SMB 안내

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:49:04 +09:00
RyuJongJin e6556a086e 시험: 흔들림 고침 (dismiss 105 · 165 드라이브)
- dismiss_test 105: 창이 뜨기 전에도 "비교하는 중" 이 없어 조건이 먼저 참이 되었다 → 창 제목이 뜨고 비교가 끝날 때까지 기다린다
- explorer_drives_test 165: 걸린 시간 (5초, 전체 시험 중 바쁘면 넘김) 대신 응답 없는 드라이브가 끝내 대답하지 않은 채로 목록이 떴는지 본다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:49:04 +09:00
RyuJongJin 36de96dd0b 해상도 올리기 메모리 설정 (176 보충)
- 환경 설정 > AI: 해상도 올리기 메모리 적게 (20%) · 보통 (30%, 처음 값) · 많이 (45%) - Android CPU 타일 크기
- 수준마다 이 기기에서의 타일 · 예상 메모리, 예시 시간 (16GB 폰)
- 빠르기 기록 열쇠에 메모리 설정을 넣어 다른 설정의 시간으로 어림하지 않음
- 모르는 설정 값은 보통

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:49:03 +09:00
RyuJongJin d6f3de130c Rsync 확인 창: 1280×720 에서 체크 · 경고가 버튼에 가리지 않게
- 미리 보기 높이를 화면 높이의 28% (140 이상) 로
- [원본 파일 지우기] 체크 · 경고를 미리 보기 위로
- 시험: 1280×720 에서 체크 · 경고가 버튼보다 위

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:49:03 +09:00
RyuJongJin 4f10cf167f 파일 탐색기 창마다 뒤로 · 앞으로 (75)
- 창마다 가 본 폴더 내역 (50개). 머리 줄 [←] [→], Alt+← / Alt+→, 마우스 4 · 5번 버튼
- 두 창일 때 마우스 4 · 5번은 마우스가 올라간 창에 적용하고 그 창을 활성으로 (창 밖이면 활성 창)
- 갈 폴더가 지워졌거나 열 수 없으면 그 항목은 버리고 지금 폴더는 그대로, 이유를 알림
- 시험: 버튼 · 단축키 · 마우스, 두 창 + 마우스 버튼, 지워진 폴더로 앞으로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:49:03 +09:00
RyuJongJin a7ef14382c 파일 탐색기 고르기 도구 (74) · 붙여넣기 줄에 남기기 · WebDAV 즐겨찾기 시험 (73 보충)
74: 고르기 막대에 "n개 표시함 · 합계 크기" (폴더는 개수만). [고르기 도구 ▾]: 반전 · 패턴으로 고르기 (*.srt, ?, ;, 대소문자 무시) ·
같은 확장자 모두. 범위: PC Shift+누르기, 폰은 첫 항목을 표시한 뒤 마지막 항목을 길게 눌러 [여기까지 범위 고르기] (시작점은 창마다).
73 보충: 복사 · 이동으로 담아 둔 것이 있으면 [붙여넣기] 는 좁아도 줄에 (pinned). WebDAV 폴더 즐겨찾기 - 마스터 문을 지나야 열림.
explorer_test · explorer_webdav_test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:49:03 +09:00
RyuJongJin fe4ca6adb2 파일 탐색기 즐겨찾기 (73): 지금 폴더 넣기 · 창 위에서 한 번에 · Rsync 짝
- 창 맨 위 줄 맨 앞 ★ (즐겨찾기한 폴더면 채움) · 버튼 줄 [즐겨찾기] (처음 값 순서 · Rsync 버튼 줄에도).
- 즐겨찾기 창: 누르면 그 창이 그 폴더로, [지금 폴더 추가] (표시 이름 고침), 이름 바꾸기 · 빼기 (폴더는 그대로),
  바로 가기 (다운로드 · MKV 결과 폴더).
- Rsync 화면: [지금 짝 추가] 로 "왼쪽 ⇄ 오른쪽", 누르면 두 창이 함께. 설정 folderBookmarks.
- explorer_test (넣기 · 가기 · 빼기 · Rsync 짝 · 저장). 버튼이 늘어 기존 시험 두 곳 고침 (처음 값 순서 · [더 보기] 안의 [붙여넣기]).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:49:03 +09:00
RyuJongJin 0d1ea93fd3 Tor 브라우저 (컴포넌트) · DNS 강제 (기본 8.8.8.8 / 8.8.4.4)
사용자 요청 (10/10): "Tor 브라우저를 현재 브라우저 옆에 추가 · 설정에서 컴포넌트처럼 추가 ·
DNS 주소를 강제로 내가 설정한 주소로, 기본 8.8.8.8" (보조 8.8.4.4 는 관리자 결정).

DNS 강제 (환경 설정 > 네트워크 · DNS: 시스템 DNS (끄기) / 직접 지정 (기본) / DNS over HTTPS)
- 앱 안 HTTP 프록시 (127.0.0.1, CONNECT + http) 가 정한 서버로 이름을 푼다 (UDP, 잘리면 TCP, DoH,
  TTL 동안 기억). 닿지 않으면 기기 DNS 로 몰래 바꾸지 않고 502 + 알림 [시스템 DNS 로 바꾸기].
- 따르는 것: 앱의 HttpClient (HttpOverrides: 업데이트 · 컴포넌트 · AI 모델 · WebDAV · 번역 · 자막),
  Edge (--proxy-server) · Chrome 엔진 (webview_cef 패치 6), Android 웹 뷰 (ProxyController),
  yt-dlp --proxy, aria2 --all-proxy (+ c-ares 가 있으면 --async-dns-server).
- IP 주소 · 같은 집 안 이름 (점 없는 이름, *.local, Tailscale *.ts.net 등) 은 기기 DNS (내 판단: NAS · WebDAV 가 그대로 열리게).

Tor 브라우저 (컴포넌트 'tor', 처음 꺼짐, 화면은 웹 브라우저 바로 뒤)
- Windows: 공식 Tor Expert Bundle 15.0.24 (tor 0.4.9.13) 을 받아 SHA-256 확인
  (e9dc6ccc…2b65) → 앱 데이터 components/tor. Android: 같은 판의 libTor.so 를 APK 에 (tool/fetch_tor_android.ps1).
- tor 를 자식 프로세스로 (SOCKS · 제어 포트는 빈 포트, 쿠키 인증, __OwningControllerProcess), Tor 화면을 닫으면 끔.
- 웹 뷰 → Tor 다리 (HTTP 프록시 → SOCKS5, 이름은 Tor 에 넘김). Tor 100% · 프록시 적용 · 전용 프로필이
  모두 된 뒤에만 웹 뷰를 만든다. WebRTC 끔, 권한 거절, 검색은 DuckDuckGo.
- Windows: 따로 WebView2 환경 (tor_webview, InPrivate). Android: 웹 뷰 프록시가 앱 전체라 Tor 화면이
  열려 있는 동안 일반 브라우저도 Tor (안내 띠, 관리자 결정), androidx.webkit 프로필 'jj_tor'
  (flutter_inappwebview_android 패치 jjProfile). 기능이 없으면 화면을 열지 않음.
- 상태 줄 (연결 중 % / 연결됨 / 실패 + 다시 시도), [새 경로] (NEWNYM), 처음 열 때 "공식 Tor Browser 아님" 안내.
- 라이선스: TOR_LICENSES.txt (Tor BSD-3 · Libevent · OpenSSL · zlib · zstd), tor 실행 파일은 GPL 빌드 → GPL-3.0.

시험: dns_proxy_test (가짜 DNS · 프록시 · SOCKS 다리), tor_test (새는 것 막기 · 번들 해시 · torrc),
integration_test/tor_dns_real_test (Windows 실제: 8.8.8.8 로 풀기, 닿지 않는 DNS 는 실패,
check.torproject.org "Congratulations").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:10:17 +09:00
RyuJongJin f86697c634 버전 2026.10.10_001
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:24:24 +09:00
RyuJongJin ae51fdfa34 원본 지우기 옵션: 확인 창에 보이는 것과 실제 실행을 같게 (178, 데이터)
환경 설정의 rsync 옵션에 --remove-source-files 가 있으면 [좌 → 우] 확인 창은 [원본 파일 지우기] 꺼짐 · [실행] 인데
실행하면 원본이 지워졌다 (사용자는 복사라고 믿음).
- rsyncArgs · robocopyRuns: 원본을 지우는 옵션 (--remove-source-files · --remove-sent-files · /MOV · /MOVE) 은 옵션 글에서 늘 빼고
  move (이동 · [원본 파일 지우기]) 일 때만 넣는다 - 탐색기 복사 · Rsync 확인 창 · 모니터링 다시 실행 모두.
- Rsync 확인 창: 설정 (기억한) 옵션에 원본 지우기가 있으면 체크를 켠 채로 · [이동] · 경고. 끄면 명령 줄 · 실행에서 빠진다.
  ⇄ 는 원본을 지우지 않는다고 알린다. 복사 확인 창 · 모니터링 카드의 옵션 줄도 실제 쓰는 것으로.
- sync_tools_test · explorer_webdav_test (끄면 원본이 남음).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 06:14:11 +09:00
RyuJongJin d5bb200d00 explorer_test 흔들림: Rsync 화면의 새 폴더가 골라질 때까지 기다리기
전체 시험 중 바쁠 때 새 폴더가 보인 직후 (골라지기 전) 확인해 실패했다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 06:14:11 +09:00
RyuJongJin 36e724afc1 AI 해상도 올리기 [이 장] 도 작업으로 · 큰 그림은 메모리에 맞는 타일 (176)
한 장 올리기가 작업 알림 · 앞 서비스 없이 돌아, 앱이 뒤에 있으면 Android 가 꺼 결과가 사라질 수 있었다 (S10: 엔진 6.6GB · 약 4분).
- AiJobs.runTask (1장) 로 - 작업 알림 · 앞 서비스 · 작업 현황. 다른 AI 일이 돌면 막는다.
- 진행 창 [뒤에서 계속]: 창만 닫고 계속. 화면에 남아 있으면 끝난 뒤 비교 화면, 떠났으면 작업 현황 끝 줄 [보기] (비교 → 저장,
  한 번만) 와 Android 끝 알림. 다음 일을 시작하면 남겨 둔 임시 결과를 지운다.
- Android CPU 타일: 기기 메모리 (/proc/meminfo) 의 20% 안에 드는 가장 큰 타일 (512 · 384 · 320 · 256 · 192), 모르면 큰 그림만 256.
- upscale_job_test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 06:14:10 +09:00
RyuJongJin e46c73a317 맨 위 줄: 지금 화면 버튼 아래 밑줄
파란 아이콘 · 옅은 바탕에 더해 3px 밑줄 - 색만으로 알아보기 어려운 사람도 어디인지 알게 (좁은 · 넓은 화면 모두). batch_test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:32:54 +09:00
RyuJongJin a66f3a8823 파일 탐색기 같은 이름 창: 두 파일의 크기 · 바뀐 날 · 어느 쪽이 새것인지 (172)
"doc.txt: 보낼 것 3B · 2026-10-09 12:00 / 있는 것 3B · 2026-10-01 08:00 (보낼 것이 더 새것)" (3개까지, 폴더는 "폴더").
_findClashes 가 vStat 으로 두 쪽을 함께 읽는다 (WebDAV 포함). explorer_conflict_test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:25:48 +09:00
RyuJongJin f805403641 버전 고르기 창: 좁은 화면에서는 목록 위 · 바뀐 내용 아래 (168)
창 안 너비가 560 보다 좁으면 목록 (높이 40%, 최대 200) 과 고른 버전 · 바뀐 내용을 위아래로. 넓으면 예전처럼 나란히.
update_ui_test (400×800 · 1200×900).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:11:33 +09:00
RyuJongJin 968d62979c 프로그램 정보에 버전 · [최신 버전 확인], 결과는 버튼 옆에 (164)
환경 설정 > 프로그램 정보 맨 위에 "JJ_MKVMaker v<지금>" 과 [최신 버전 확인]. 결과 (최신 · 새 버전 · 확인 못 함) 는 알림이 아니라
버튼 옆에, 확인하는 동안 도는 표시. 업데이트 묶음의 버튼도 같은 위젯 (UpdateCheckButton). 새 버전이면 안내 창은 예전처럼.
checkForUpdate 에 onResult. update_ui_test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:06:06 +09:00
RyuJongJin ecb1cf93b7 여러 장 AI 작업의 진행 줄: "10장 중 3장째 · 남은 약 …" (161)
폴더 · ZIP 전체 해상도 올리기와 AI 그림 여러 장. Android 진행 알림 · 작업 현황 · AI 그림 화면에 같은 줄.
제목에 붙이던 "3/10" · "남은 n장" 대신 AiJobs.total 로 센다. ai_upscale_test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:56:00 +09:00
RyuJongJin afdbcfb2d7 웹 브라우저: 동영상을 못 찾은 페이지의 다운로드 버튼은 테두리만 (137)
- 덜 눈에 띄게 하려던 tonal 버튼이 이 테마에서 채움 버튼과 거의 같아 보였다 → OutlinedButton · IconButton.outlined.
- YouTube 첫 화면 · 구독 목록은 미리보기 <video> 가 있어 동영상 있음으로 보였다 → 동영상 주소가 정해진 사이트
  (YouTube · Vimeo · TikTok · 치지직 등) 는 주소로만, 그 밖은 페이지 안의 <video> 로 (downloadLooksUseful).
- browser_test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:45:49 +09:00
RyuJongJin b1f2b1aef4 Android 업데이트 설치: 첫 시도가 다른 오류로 실패하면 1초 뒤 한 번 더 · 원문은 작업 기록에 (170 보충)
폴드에서 첫 시도만 실패하고 두 번째에 바로 된 일 (접은 화면 · 화면 전환 중으로 보임) - 사용자가 두 번 누르지 않아도 되게.
화면에 보이지 않는 첫 시도의 실패 원문 · "허용 필요로 왔지만 허용돼 있음" 은 app.log 에 남겨 다음에 진짜 원인을 볼 수 있게.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:44:18 +09:00
RyuJongJin bf72557fc3 AI 해상도 올리기 비교 화면: 저장하지 않고 닫으면 묻기 (160)
닫으면 올린 그림이 지워지므로 [닫기] · 뒤로 키 · 뒤로 몸짓 모두 "저장하지 않고 닫을까요?" ([취소] · [저장하지 않고 닫기] · [저장]).
위쪽 [저장] 은 묻지 않는다. 시험하려고 UpscaleCompareView 로 공개. upscale_compare_test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:44:17 +09:00
RyuJongJin 06b6169ad8 Android 업데이트: 설치가 허용돼 있는데 "설치 허용 필요" 창이 뜨던 것 (170)
폴드 (Android 16 · 허용 allow) 에서 첫 설치 시도에 앱의 "설치 허용 필요" 창이 떴다 (Android 설정 화면은 열리지 않음 -
허용 문제가 아니라 다른 오류). Kotlin 이 installApk 안의 IllegalStateException 을 모두 "허용 필요" 로 보냈기 때문.
- Kotlin: 허용이 꺼져 설정 화면을 연 경우만 InstallPermissionNeededException → PERMISSION, 다른 오류는 "예외 이름: 원문" (INSTALL).
- 채널 canInstallPackages. Dart: PERMISSION 이 와도 허용돼 있으면 창 없이 다시 연다 (한 번).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:30:29 +09:00
RyuJongJin 76f8ab18da 버전 고르기 창: 처음엔 최신 버전을 골라 둔다 (169)
이 기기용 파일이 있는 것 중 가장 새 버전 (없으면 지금 버전). 이 창을 여는 사람은 대개 업데이트하려는 것.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:30:29 +09:00
RyuJongJin 3a220ab073 [MKV 만들기] 와 AI 버튼 구별 · AI 작업 전에 계획 보이기 (138)
[MKV 만들기] 와 [자막 만들기 & MKV 만들기] 를 헷갈려 오래 걸리는 AI 자막이 돌던 것.
- AI 버튼 (자막 만들기 · 자막 & MKV) 은 보라 테두리 · 글자 · ✨ 아이콘, [MKV 만들기] 는 청록 채움 · ▶.
  좁은 화면에서는 AI 버튼을 맨 뒤 구분선 너머로 (넓은 화면은 왼쪽 · 오른쪽으로 원래 떨어져 있음).
- AI 설정 창 맨 위에 계획: 할 일 · 대상 수 · 자막 언어 · 예상 시간 (영상 1시간 ≈ 음성인식 15~30분 + 번역 언어마다 10~20분).
- 환경 설정에서 "매번 묻기" 를 끈 경우는 사용자 설정대로 묻지 않되, 3초 알림
  "AI 자막 (동영상 n개 · 약 m분) 을 시작합니다 [취소]" 뒤에 시작해 잘못 누른 것을 되돌릴 수 있게.
- ai_confirm_test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:12:14 +09:00
RyuJongJin 8c1ef5a03b 실패한 다운로드 줄을 쉬운 말로 (136): 진행 막대 대신 실패 · 이유 · [자세히] · [다시 시도]
- 실패하면 진행 막대 ("준비 중…") 를 지우고 사람 말 이유 (화면 언어): 주소에 영상이 없습니다 (404) · 로그인이 필요합니다 (403) ·
  연결할 수 없습니다 · 지원하지 않는 사이트입니다 · 저장 공간이 모자랍니다 · 받지 못했습니다 (그 밖). yt-dlp · aria2 (errorCode) 같은 모양.
- yt-dlp 백엔드는 원문을 저장하고 화면에서 바꾼다. [자세히] 는 원문 - 주소의 서명 · 키 (?… · #… · 아이디:비밀번호@) 는 가림 (redactUrlSecrets).
- [다시 시도] 는 기존 다시 받기. 진행 막대만 쓰는 곳도 실패면 "실패".
- download_error_test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 02:52:57 +09:00
RyuJongJin 26f6389f02 MKV 화면 맨 위 줄: 다른 언어에서 버튼 글이 잘리던 것 (61)
줄이기 순서 (제목 → 버튼 글 → …) 를 정할 때 버튼 너비를 한국어 글로 잰 고정 값으로 셈해, 글이 긴 일본어
("字幕作成 & MKV 作成") 에서는 줄이기 전에 잘렸다 (S10 Ultra 가로). 지금 언어의 글 너비를 재어 차이를 더한다
(한국어는 그대로). 캡처 시험: 실제 서비스로 일본어 1644 · 1480 · 1280 · 1138 · 960, 체크 0 · 50개.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 02:41:14 +09:00
RyuJongJin c986690b3c 환경 설정 > MKV 만들기 > 기본 자막 언어 (MKV 기본 자막 트랙 · 플레이어가 먼저 켜는 자막)
화면 언어 따르기 (처음 값) / 한국어 / English / 日本語 / 中文 / 직접 고르기 (자막 언어 목록).
화면은 일본어인데 한국어 자막을 기본으로 원하는 경우처럼, 고른 언어가 화면 언어보다 먼저.
설정 preferredSubtitleLanguage ('' = 화면 언어 따르기). i18n_small_test 에 시험.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 02:41:14 +09:00
RyuJongJin f429c59502 다국어 작은 것들 (61)
- 영어 복수형: 영어 글에서 수가 1 이면 단수 ("1 videos" → "1 video"). 11 · 21 · 1.5 는 그대로.
- 탐색기 복사 · 이동 확인: 문장 조각 + "복사할까요?" 를 잇던 것을 두 문장으로 (언어마다 어순이 깨졌음).
- MKV 기본 자막 트랙 · 플레이어가 먼저 켜는 자막: 늘 한국어 → 화면 언어 (목록에 없는 언어면 한국어).
- robocopy 진행: 종류 글 ("New File" 등) 이 아니라 탭 모양으로 파일 줄을 알아봐 일본어 · 중국어 Windows 에서도.
- Android 저장소 이름: 내장 · SD 카드는 앱 화면 언어로 (Kotlin 이 isPrimary 를 넘김), USB 등은 기기 이름 그대로.
- 기본 즐겨찾기 ("YouTube 구독" · 표시줄 · 기타): 한국어 열쇠로 저장하고 이름을 바꾸지 않았으면 화면 언어로
  (예전 판이 다른 언어로 저장한 기본 이름 포함). 바꾼 이름 · 사용자 항목은 그대로.
- i18n_small_test · sync_tools_test (일본어 · 중국어 robocopy 줄), 캡처 시험에 일본어 가로 첫 화면.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 02:29:58 +09:00
RyuJongJin f33a4b3547 휴지통 시험: 기본은 가짜 휴지통, 실제 휴지통은 JJ_TEST_REAL_RECYCLE=1 일 때만
전체 시험 도중에 끊기면 사용자 휴지통에 시험 흔적이 남았다 (10/10 01:31).
- 탐색기는 recycle() · restoreRecycled() 를 쓴다 (앱은 늘 실제 휴지통, 시험만 recycleBinForTest 로 바꿈).
- test/support/fake_recycle_bin.dart: 실제와 같은 규칙 (긴 경로 거부 · 되돌릴 때 덮지 않음 · 원래 폴더 없으면 안 만듦),
  다른 드라이브면 복사 후 지움. useTestRecycleBin() 은 JJ_TEST_REAL_RECYCLE=1 이 아니면 가짜로.
- recycle_bin_test 의 실제 휴지통 시험 3개와 긴 경로의 실제 함수 부르기는 실제 모드에서만.
  explorer_conflict · explorer · 캡처 시험 (b3 · check) 은 기본 가짜.
- windows_check_shot_test 의 "65 · 94 · 98" 은 144 뒤로 깨져 있었다 ([그대로 두기] 창을 닫지 않아 다음 크기에서 막힘) - 닫게 고침.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 02:11:38 +09:00
RyuJongJin 5601f9d4fb 화면 글자 검사기가 놓치던 것 잡기 · 찾은 번역 빠짐 고치기 (60)
- tool/l10n: trf( 뒤에서 줄을 바꾼 열쇠도 (앞 200글자까지 봄). tr 로 감싸지 않은 한글 글이 사전에 없으면
  "번역 안 된 글" 로 실패 (tr(변수) 로 쓰이는 글 포함). 일부러 번역하지 않는 자료는 // l10n-skip (줄) · // l10n-skip-file.
- 찾아 고친 것: Rsync 위아래 배치 버튼 "위 → 아래 · 위 ← 아래 · 위 ⇄ 아래", 컴포넌트 "AI 그림" 설명,
  AI 목록의 NVIDIA 라이선스 이름 (받기 화면), 라이선스 화면의 이름 · stable-diffusion.cpp 요약 (화면을 열 때 언어로),
  Android 되돌리기 안내 한 줄, 파일 작업 오류 글 (같은 이름 · 원본 없음 · 자기 안으로 · 쓸 수 없는 이름).
- 자료로 표시: 음성인식 말버릇 목록 (ai_subtitle) · 실제 폴더 이름 · PowerShell 스크립트.
- l10n_check_test: check.py 가 통과하는지, 검사기가 줄 바꾼 trf( · tr(변수) 글을 잡고 skip · raw 는 넘기는지.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 01:39:33 +09:00
RyuJongJin a37ae55e2d 보관 폴더에 README.txt (영어 · 한국어): "설정 보관" · "AI 모델 보관"
Download 아래 한국어 이름 폴더를 한국어를 모르는 사용자도 알아보게 만들 때 안에 README.txt 를 둔다
(이미 있으면 그대로). 보관본 고르기 · 모델 되살리기에는 끼지 않는다. 작은 파일이라 동기로 쓴다
(비동기면 되돌리기 흐름에 기다릴 단계가 늘어 update_ui_test 의 Android 되돌리기가 시간 안에 끝나지 않았다).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 01:39:32 +09:00
RyuJongJin 388d01a0e3 오류 메시지 안쪽을 화면 언어로 (59)
- 컴포넌트 설치 (진행 단계 글 포함) · WebDAV (실패 글 · 상태별 힌트 · 동작 이름) · cron · rsync 설치 · 복사 (rsync · robocopy 종료 코드) ·
  이름 바꾸기 · 설정 복구 · 웹 번역 응답 · 그림 읽기의 오류 글을 tr / trf 로 (번역 3개 언어).
- explainFileError (예전 탐색기 _explain) 는 한국어 글과 화면 언어로 바뀐 글을 둘 다 보고 오류 종류를 가른다.
  WebDAV 실패 글은 상태 번호 앞 빈칸 (" 401") 을 번역에서도 지킨다.
- 컴포넌트 설정의 trStep (한국어 진행 글 앞부분만 번역) 은 할 일이 없어 지움.
- file_error_test: ja 에서 로컬 서버의 401 · 403 · 404 오류 글에 한국어가 없고 종류를 맞게 가르는지
  (시험 바인딩이 HttpClient 를 가짜로 바꾸므로 이 시험에서만 HttpOverrides 를 풂).
- 폴더 이름 "설정 보관" · "AI 모델 보관" 은 예전 보관을 찾아야 하므로 그대로 둔다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 01:11:17 +09:00
RyuJongJin a4afdda5c6 Android 알림 글 · 채널 이름을 화면 언어로 (57)
- 앱이 고른 언어라 Android 의 values-xx 로는 맞출 수 없어, Dart (AndroidKeepAlive) 가 시작할 때와 화면 언어가 바뀔 때
  번역된 글 8개 (진행 알림 제목, "작업 멈춤" 알림, 채널 2개의 이름 · 설명) 를 보낸다.
- Kotlin 은 받은 글을 SharedPreferences 에 저장해 앱이 꺼진 뒤 서비스만 떠도 마지막 언어로 쓰고,
  이미 만든 채널은 같은 ID 로 다시 만들어 이름 · 설명만 바꾼다. 받은 적이 없으면 한국어.
- notification_texts_test: Kotlin 기본값과 Dart 의 한국어 글이 같은지, ja 로 바꾸면 모두 번역되는지.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:48:24 +09:00
RyuJongJin abeb3d8999 옮기기: 시작하면 바로 목록에서 빼고, 옮기지 못하면 원래 자리 · 표시 · 선택으로 되돌리기
- 다른 드라이브 · NAS 로는 복사라 큰 동영상이 1초 넘게 목록에 남아 보였다 (1GB 시험 파일 M: → C: 1,079ms, 같은 드라이브 22ms).
- 데이터 버그 고침 (moveFileInto): 다른 드라이브로 복사한 뒤 원본을 지우지 못하면 (열려 있음 등) 대상 폴더에 사본이
  남아 두 벌이 됐다. 이제 방금 만든 사본을 지우고 오류를 낸다 (원본은 그대로).
  같은 함수를 쓰는 app_controller 안의 다른 두 곳 (옆 자막 · jj_mkv 결과물 함께 옮기기) 도 시험으로 확인했다:
  자막 · 결과물을 열어 둔 채 옮기면 동영상만 옮겨지고, 자막 · 결과물은 원래 자리에 하나만 남는다
  (고치기 전 코드로는 이 시험이 실패하는 것도 확인).
- move_test: 옮기는 중 목록에 없음 → 실패하면 복원 · 대상에 사본 없음, 함께 옮기는 파일의 실패.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:48:24 +09:00
RyuJongJin bd0b75ce7b move_test 흔들림: 세 번 눌러 옮긴 뒤 목록에서 빠질 때까지 기다리기
전체 시험 중 바쁠 때 파일은 옮겨졌는데 목록 (c.videos) 에서 빠지는 것이 늦어 실패했다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:30:01 +09:00
RyuJongJin cd81f6db37 라이선스 원문 (175): 네이티브 구성 요소 · 받는 AI 모델의 저작권 줄과 허가 문구를 그 판 그대로
- assets/licenses 에 원문 9개를 더한다 (tool/licenses/fetch_licenses.py 로 상류에서 그 판 그대로 받아 만든다):
  · STABLE_DIFFUSION_CPP_LICENSES: sd.cpp 228c707 과 sd-cli 에 함께 묶인 ggml · libwebp · libwebm · Oniguruma · Darts-clone ·
    utf8proc · nlohmann/json · miniz · stb · zip
  · WHISPER_CPP_LICENSE (v1.9.1) · ONNXRUNTIME_LICENSES (1.15.1, Third Party Notices 포함)
  · 받는 모델: CreativeML OpenRAIL-M (SD1.5) · OpenRAIL++-M (LCM-LoRA) · TAESD · Real-ESRGAN · Whisper 모델 · NLLB (CC BY-NC 4.0)
- 라이선스 화면에 원문으로 등록. AI 그림 받기 화면의 줄마다 [원문], 동의 창에 [라이선스 원문 보기].
- NLLB 를 받는 곳 (AI 자막 · 번역 · 화면 언어 더하기) 에 "비상업적 이용만 허용" 과 [원문].
- SD1.5 licenseUrl 을 원문이 있는 CompVis 저장소로 (사본 저장소의 LICENSE.md 는 없음).
- THIRD_PARTY_NOTICES (Windows · Android) 에 원문 위치. Windows 는 whisper.cpp · onnxruntime 원문을 실행 파일 옆에도.
- licenses_test: MIT 는 Copyright 줄 + "Permission is hereby granted", BSD 는 "Redistribution and use", OpenRAIL 은 부속서 A,
  NLLB 는 NonCommercial 정의까지. AI 목록의 파일마다 원문 asset 이 있는지.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:29:19 +09:00
RyuJongJin 6010aa908b explorer_test: tearDown 의 임시 폴더 지우기를 될 때까지 기다리기
Windows 에서 방금 닫은 파일 (65 의 잠금 시험) 이나 색인 · 백신이 잠깐 잡고 있으면 지우기가 실패할 수 있다.
100ms 씩 최대 약 5초 다시 지운다. (65 만 20번 연달아 돌려 모두 통과 - 실패는 재현되지 않음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:29:19 +09:00
RyuJongJin 4c0bc0b9ce Flutter 기본 글을 화면 언어로 (140 · 158): 뒤로 · 라이선스 화면 · 복사 · 붙여넣기 메뉴 등
- flutter_localizations 를 더하고 MaterialApp 이 화면 언어 (i18n) 를 바꿀 때만 다시 그려진다.
- Flutter 가 아는 언어면 그 언어 (앱이 더한 기계 번역 언어 포함, 예 de · vi), 모르는 언어일 때만 영어. 한국어가 섞이지 않는다.
- supportedLocales 는 kMaterialSupportedLanguages 전체 + 지금 언어 (zh-Hant · pt-BR 처럼 글자 체계 · 나라까지 그대로).
- Material 만 알고 Cupertino 는 모르는 언어 (ps) 는 Cupertino 글만 영어로 채운다.
- 시험: locale_test (de → Zurück, 없는 언어 → Back, zh-Hant 유지, ps 열림).
- Windows 캡처 시험 (windows_i18n_shot_test): 라이선스 목록을 다 읽은 뒤까지 찍는다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:54:24 +09:00
RyuJongJin f331672e3a 진행 표시: 해상도 올리기는 모델을 읽는 동안 숫자 없이 (167) · 큰 파일 하나도 보낸 바이트로 오름 · 끝난 진행 창의 이름 (171)
- 167: sd-cli 의 모델 읽기 막대를 올리기 진행으로 읽어 처음부터 100% 였다. 읽는 동안은 움직이는 막대,
  올리기는 타일 n/m 으로 % 와 "약 n초 남음" (upscaleProgressOf)
- 앱이 직접 복사할 때 항목마다 바이트 크기와 보낸 바이트로 진행률 (이름 바꾸기로 옮기면 파일 수로),
  보내는 동안 0.2초마다 알림 - 큰 파일 하나가 끝날 때까지 0% 에 머물던 것
- 171: 끝난 진행 창의 " 안의 파일" 에 마지막 항목 이름
- 시험: upscaleProgressOf, 256KB 를 256KB/s 로 복사하는 도중 20~100%

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:14:31 +09:00
RyuJongJin 794f484c19 explorer_test 흔들림: "여러 개 표시 →" 시험이 전체 시험 중에만 실패하던 것 - 될 때까지 기다리기
- 표시 직후 "2개 표시함" 을 될 때까지 기다림, 이 파일의 settle 은 300번 (최대 9초) 까지 (되면 바로 나옴)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:14:31 +09:00
RyuJongJin daac19a514 157 · 162 끝난 작업 알림은 따로 "끝난 작업" 채널로 · [열기] 는 Android 에서 이 앱의 탐색기로
- 157: 끝 알림이 진행 알림 채널 (조용히, 낮은 중요도) 에 올라가 알림 창의 조용한 칸에 묻혔다 -
  새 채널 "끝난 작업" (보통 중요도, 소리 없음, 설정에서 따로 끌 수 있음)
- 162: ExplorerPage.openAt - 이 앱의 탐색기를 그 폴더로 (이미 열려 있으면 그 화면에서, 처음 열면 첫 창으로).
  AI 해상도 끝 알림 · 작업 현황의 [열기] 가 Android 에서 이것을 씀 (Windows 는 Windows 탐색기).
  폴더 메뉴의 "파일 관리자에서 보기" 로 다른 파일 앱도 그대로 열 수 있다
- 시험: openAt 으로 열기 · 열린 채로 다른 폴더

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:03:55 +09:00
RyuJongJin cc05fcad3c 174 MKV 화면이 맨 처음 화면이 아니어도 (홈 화면 = 탐색기 등) MKV 아이콘이 "지금 여기"
- 위쪽 아이콘 줄이 MKV 를 맨 처음 화면일 때만 알아봐, 홈 화면이 다른 화면인 사용자는 MKV 화면에서 아무것도 파랗지 않았다
- 다른 화면처럼 MKV 화면이 스스로 알린다 (AppNavButtons onMkvPage)
- 시험: 다른 화면 위에 연 MKV 화면에서 MKV 아이콘이 지금 여기

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:52:18 +09:00
RyuJongJin 746082db22 147 좁은 화면의 버튼 줄: 들어가지 않는 버튼은 [더 보기] 메뉴로 · 창 배치 버튼은 늘 줄에 · 처음 값은 자주 쓰는 것부터
- 버튼 줄은 밀어 넘기는 줄이라 아래쪽 버튼 (숨은 항목 표시 등) 을 찾지 못했다 - 들어가는 만큼 보이고 나머지는 [더 보기 ⋯]
- 창 배치 (두 창으로 / 좌우 ⇆) 는 사용자 요청대로 늘 줄에 (넘치면 마지막 자리를 내줌)
- 처음 값 (버튼 구성을 바꾸지 않았을 때 · [기본값]) 순서: 복사 · 이동 · 삭제 · 새 폴더 · 이름 변경 · 선택 · 찾기 · 창 배치 → 나머지.
  사용자가 정한 순서는 그대로
- 시험: 낮은 화면에서 [더 보기] · 메뉴로 숨은 항목 표시 · 창 배치 버튼은 줄에 · 처음 값 순서 · 정한 순서 유지

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:52:34 +09:00
RyuJongJin 6a5aea5ff2 135 · 146 · 37-2 같은 폴더로 이동 · 복사는 창으로 알림 · 경로 입력은 칸 아래 오류 · 지워진 폴더는 상위로
- 135: [이동] 을 모두 이미 그 폴더에 있는 것으로 하면 "이미 이 폴더에 있습니다" 창 (짧은 알림은 놓치기 쉬움).
  섞어 고르면 이미 있는 것만 건너뛰고 나머지를 옮김 (확인 창 · 끝난 알림에 "이미 이 폴더에 있어 건너뜀: …").
  [복사] 를 모두 같은 폴더로 하면 "같은 폴더에 사본을 만들까요? (이름 (2))" [취소] / [사본 만들기] (확인 창은 한 번만)
- 146: 경로 입력 창은 없는 경로면 닫지 않고 칸 아래 "폴더가 없거나 열 수 없습니다", 고치면 오류를 지움
- 37-2: 보던 폴더가 밖에서 지워지면 새로 읽을 때 남아 있는 가장 가까운 상위 폴더로, "보던 폴더가 없어져 상위 폴더로 옮겼습니다"
- 시험: explorer_conflict_test (135 · 135 보충 · 146 · 37-2), explorer_parallel_test 의 경로 입력 시험은 창이 열린 채로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:23:29 +09:00
RyuJongJin 6a90ae5706 51 MKV 목록: 동영상 줄을 길게 누르면 오른쪽 클릭과 같은 메뉴 · "목록에서 빼기 (파일은 그대로)"
- 터치 화면에서 길게 누르면 누른 자리에 메뉴 (그 줄 선택, 짧은 진동). 한 번 · 두 번 · 세 번 누르기는 그대로
- 메뉴의 "삭제 (목록에서 제거)" 를 "목록에서 빼기 (파일은 그대로)" 로 - 파일이 지워진다고 오해하지 않게
- AI 자막 만들기 · 인터넷 자막 찾기는 쓸 수 있을 때 (오른쪽 클릭 메뉴와 같은 조건) 나온다
- 시험: 길게 누르기 → 선택 · 메뉴, 한 번 누르기는 메뉴 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:02:24 +09:00
RyuJongJin ba332f9f84 E10 · 50 보충: 로컬 폴더를 읽지 못하면 빈 폴더로 보이지 않게 ("읽을 수 없습니다 · 이유" 와 [다시 시도])
- listEntries 가 Directory.list 의 오류를 버려 권한 없는 폴더 · 빠진 USB · 없는 폴더가 빈 폴더로 보였다.
  하나도 읽지 못했으면 그 오류를 던지고, 일부를 읽은 뒤의 오류는 읽은 것까지 보여 준다
- 부르는 다른 곳: [폴더 재생] 은 이유 알림, [MKV 목록에 추가] 는 읽지 못한 폴더를 건너뛰고 알림, 그림 보기는 고른 그림만
- 시험: 빈 폴더 · 없는 폴더 · C:\System Volume Information (권한 없음) - 탐색기에 "이 폴더를 읽을 권한이 없습니다" 와 [다시 시도]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:52:38 +09:00
RyuJongJin c25a754035 50 Android 가 막은 폴더 (Android/data · obb): 빈 폴더가 아니라 "이 폴더는 Android 가 막아 볼 수 없습니다" · [위 폴더로]
- Android 11 부터 이런 폴더는 오류 없이 빈 목록으로 읽힌다 - 막힌 것이라고 알리고 그 앱의 내보내기 · 공유를 안내
- 다시 해도 같으므로 [다시 시도] 대신 [위 폴더로]
- 시험: file_error_test (경로 판별 · 안내 글)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:52:03 +09:00
RyuJongJin de967d190a 49 탐색기 뒤로 키: 먼저 고른 것 풀기 → 상위 폴더 → 맨 위에서 화면 닫기 (위쪽 ← 는 늘 닫기)
- 탐색기 · Rsync 화면에 PopScope: 시스템 뒤로 (Android 뒤로 키 · 몸짓 · 마우스 뒤로 · Esc) 만 차례를 거친다
- 위쪽 ← 는 브라우저처럼 늘 바로 닫기 (Navigator.pop)
- 시험: 선택 → 뒤로 (풀기) → 뒤로 (상위) → 뒤로 (닫기). 66-2 캡처 시험 (integration_test/windows_662_shot_test) 도 함께

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:29:03 +09:00
RyuJongJin 01600ec69a 66-2 화면 옮기기: 지금 화면 아이콘 파랗게 · 민 쪽으로 미끄러지는 전환 · 처음으로 / 끝 알림 · 브라우저 안내 한 줄 (한 번)
- 지금 화면 아이콘: 넓은 화면도 파란 글자 + 옅은 바탕 (좁은 화면은 바탕 더함)
- 좌우로 밀기 · 이전 · 다음으로 옮길 때만 민 쪽에서 미끄러져 들어옴 (NavSlideTransitionsBuilder, 그 밖은 기본 전환)
- 끝에서 처음으로 넘어가면 "처음으로: …", 처음에서 끝이면 "마지막으로: …", 돌기를 끈 채 끝이면 "마지막 화면입니다" / "첫 화면입니다"
- 웹 브라우저 처음 열 때 위쪽 막대 아래 한 줄 안내 (✕ 로 닫음, browserSwipeHinted) - 알림이면 다른 알림을 뒤로 밀어서 한 줄로
- 캡처 시험: 154 · 155 를 새 화면에서 알림을 지운 뒤 찍음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:16:08 +09:00
RyuJongJin 1450b51316 묶음 3 Windows 확인 캡처 · 휴지통 시험 흔적 스스로 확인 · 꺼내는 드라이브가 비면 "비어 있음"
- integration_test/windows_b3_shot_test: 165 · 48 · 154 · 52 · 148 · 144 를 실제 화면으로 눌러 PNG (JJ_SHOT_DIR)
- 휴지통을 건드리는 시험은 끝에 사용자 휴지통 전체에서 시험 폴더 아래 (하위 포함) 가 원래 위치인 항목이 0 인지 확인
  (test/support/recycle_leftovers.dart)
- 165 보충: 카드 리더 · USB · CD (GetDriveType 2 · 5) 가 열리지 않으면 "연결 안 됨" 이 아니라 "비어 있음" (회색)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 19:55:29 +09:00
RyuJongJin f97f63df79 144 긴 경로 지우기: 휴지통에 넣지 못한 이유는 한 줄 · 이름만 · [그대로 두기] / [영구 삭제 (n개, 되돌릴 수 없음)]
- 휴지통이 받지 않는 긴 경로 (260자 넘음) 는 LongPathException 으로 따로 모아, 실패 창 위에 이유 한 줄과 이름만
- [영구 삭제] 는 그 항목만 지우고 결과를 알림. 긴 경로가 없으면 예전처럼 [확인]
- 시험: 긴 경로 폴더 → 이유 한 줄 · [그대로 두기] 면 남음 · [영구 삭제] 면 지움
- recycle_bin_test: 148 의 "원래 폴더 없음" 시험이 휴지통에 남긴 것을 정확히 그 폴더로 찾아 지우게 (사용자 휴지통에 남던 것)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 19:31:07 +09:00
RyuJongJin e1e3316d8d 탐색기: 이동해도 그 폴더를 고르지 않음 · 148 휴지통으로 보낸 뒤 [되돌리기]
- 드라이브 탭 · 경로로 가기 · 상위 폴더 · .. · [열기] 로 가도 그 폴더를 복사 · 이동 대상으로 잡지 않는다
  (누르거나 표시하지 않은 폴더가 대상이 되지 않게, 위치는 지금 폴더 표시 · 스크롤로)
- 148: 휴지통으로 보낸 알림 (10초) 에 [되돌리기]. 이 사용자 (SID) 의 휴지통에서 보낸 시각 뒤의 $I 를 찾아 $R 을 원래 경로로.
  원래 자리에 같은 이름이 있거나 원래 폴더가 그사이 지워졌으면 되돌리지 않고 알림 (폴더를 다시 만들지 않음)
- 시험: 실제 휴지통으로 되돌리기 · 같은 이름 · 원래 폴더 없음 · 휴지통에 없음, 탐색기 [휴지통으로] → [되돌리기], 상위 폴더 뒤 [복사]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 19:23:58 +09:00
RyuJongJin 8f5d2204f0 흔들리는 시험 2개: 정해 둔 시간 대신 될 때까지 기다리기 (decisions_test 126 · drop_test)
- 전체 시험 중 PC 가 바쁘면 실패하던 것 (따로 돌리면 통과). 흔들리는 시험이 실제 실패를 가리지 않게
- decisions_test 126: 설정 파일을 다시 읽어 migrated 가 빌 때까지 (최대 10초)
- drop_test: 안내 글이 뜰 때까지

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 19:23:57 +09:00
RyuJongJin 1c919ce8c6 52 복사 · 이동 진행 막대: 화면을 떠났다 돌아와도 다시 보이고, 끝나면 받는 폴더를 새로 읽음
- 화면을 열 때 CopyCenter 에서 아직 도는 작업을 찾아 진행 막대를 다시 붙이고, 끝나면 받는 · 원본 폴더를 새로 읽은 뒤 내린다
  (.jjpart 가 남아 보이지 않게)
- 시험: 속도 제한으로 오래 걸리는 복사 도중 화면을 없앴다가 다시 열기 (수정 전 코드에서는 실패)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:57:50 +09:00
RyuJongJin f01fe96151 48 보충: 탐색기를 처음 열면 고른 항목 없음 · 같은 이름 처리 기본값을 환경 설정으로
- 처음 열 때 지금 폴더를 고르지 않는다 (열자마자 폴더 통째가 복사 · 이동 대상이 되지 않게). 바로 [복사] 하면 "복사할 항목을 고르세요"
- 환경 설정 > 파일 탐색기 > 같은 이름이 있을 때: 묻기 (처음 값) · 늘 이름 바꾸기 · 늘 덮어쓰기 · 늘 건너뛰기.
  늘 … 이면 확인 창에 고르는 칸 없이 "환경 설정대로 …" 한 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:57:50 +09:00
RyuJongJin 7d76af382d 165 탐색기 첫 열기 멈춤: 드라이브 목록은 GetLogicalDrives (열어 보지 않음) · 드라이브마다 비동기 확인 · 연결 안 됨 ⟳
- 예전: 드라이브 26개를 isDirectorySync 로 열어 봄 → 끊긴 · 느린 NAS 드라이브에서 화면이 멈춤 (이 PC 11,181ms → 2ms)
- 끊긴 드라이브도 숨기지 않고 탭에 "X: · 연결 안 됨 ⟳" - 누르면 다시 연결 (다시 확인), 안 되면 이유
- 네트워크 드라이브는 GetDriveTypeW 로 알아 네트워크 아이콘
- 지난 폴더는 비동기로 확인 (2초 넘으면 열어 보고, 못 읽으면 그 창에 이유)
- 시험: 응답 없는 드라이브가 있어도 첫 화면이 바로 뜬다 · 연결 안 됨 · 다시 연결

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:41:05 +09:00
RyuJongJin a878e3b1cc 48 같은 이름 충돌: 덮어쓰기 · 건너뛰기 · 이름 바꾸기 고르기 · 154 · 155 복사 · 이동 대상 규칙과 확인 창의 항목 이름
- 48: 앱이 직접 복사 · 이동할 때 받는 폴더에 같은 이름이 있으면 확인 창에서 고른다 (기본 이름 바꾸기 = 원래 파일 그대로).
  덮어쓰기는 새 파일을 .jjpart 에 다 쓴 뒤에만 옛 파일을 지움, 폴더는 합침. 같은 폴더 · 종류가 다르면 새 이름.
  건너뛰기는 그대로 두고 끝난 알림에 알림. 확인 창은 바로 뜨고 같은 이름은 창 안에서 찾음 (다 찾기 전엔 이름 바꾸기)
- 154 · 155: 확인 창 · 담기 알림에 항목 이름 (앞 3개 + 외 n개), 고른 것이 없으면 "옮길 · 복사할 항목을 고르세요",
  길게 누르기의 다른 창으로 복사 · 이동도 표시한 것 모두
- 시험: explorer_conflict_test · file_ops_test, explorer_webdav_test 는 시험 폴더 드라이브로 고정

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:41:05 +09:00
RyuJongJin f34cbe5a69 P0 보충: 되돌리기의 AI 모델 복사 체크는 [저장하고 계속] 때만 설정에 ([취소] 하면 그대로, 건드리지 않으면 자동 유지)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:27:25 +09:00
RyuJongJin e4524cdb9a P0 Android 되돌리기: 남는 것 / 사라지는 것을 사실대로 · 두 번 확인 · 받은 AI 모델 복사해 두었다가 되살리기
- 되돌리기 창: 남는 것 (설정 · 목록 · 즐겨찾기 · 공용 폴더 파일) / 사라지는 것 (AI 모델 실제 크기, 비밀번호 · API 키,
  마스터 · 최상 비밀번호, 브라우저 로그인, 작업 기록). 되돌릴 판이 설정 · 모델을 되살리지 못하면 그렇다고 알림.
  [저장하고 계속] 뒤 "정말 되돌릴까요?" 한 번 더. 버전 고르기 문구에 Android 는 앱을 지운다는 줄
- 받은 AI 모델 (ai · 자막 모델) 을 Download/JJ_MKVMaker/AI 모델 보관 에 SHA-256 목록과 함께 복사 (설정 rollbackKeepModels,
  자동 = 남은 공간이 2배 이상이면 켬). 복사 실패하면 앱을 지우지 않고 멈춤. 다시 설치한 앱 (이 판 이후) 이 켜질 때
  SHA-256 을 확인하며 되살리고 사본을 지움 (맞지 않으면 사본을 남김)
- 공용 폴더가 아직 없어도 남은 공간을 상위 폴더로 잰다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:03:47 +09:00
RyuJongJin 1cc2741b16 릴리스 전: 가짜 AI 결과 막기 · S10 해상도 속도 · 예상 시간 (156-2) · 라이선스 (64) · zip 실행 흔적 검사 (166)
- 156-2: sd-cli 는 업스케일러 · 장치를 만들지 못해도 원본을 저장하고 0 으로 끝난다 (앞 판 Android 의 --backend cuda0).
  엔진 기록의 실패 줄 (new_upscaler_ctx failed 등) 과 결과 크기 (정확히 4배 · 그림은 정한 크기) 로 알아채 실패로 알린다
- Android CPU 해상도 올리기: 모든 코어 · 타일 512 (S10 512x384 만화 모델 88~92초 → 45~50초)
- [이 장] 진행 창 · [모든 장] 확인 창에 지난번 걸린 시간으로 "한 장 약 n초" (처리 장치 · 모델별)
- 64: LGPL 원문을 libmpv 이름으로, Android 고지 문서는 APK 에 맞는 내용 (THIRD_PARTY_NOTICES_ANDROID.txt)
- 166: make_layout 이 Logs · *.log · *.dmp 를 빼고, tool/make_zip.ps1 이 zip 목록을 검사 (실행 흔적이 있으면 실패)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 17:11:31 +09:00
RyuJongJin ebe66a7714 v2026.10.09_001
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:20:31 +09:00
RyuJongJin b098a301d8 라이선스 고지 (62 · 63 · 64) · AI 릴리스 전 수정 (152 · 156 · 157 · 159)
- 62 · 63 · 64: assets/licenses 에 GPL-2.0 · GPL-3.0 · LGPL-2.1 원문, PDFium (chromium/7811) 고지,
  yt-dlp THIRD_PARTY_LICENSES (2026.08.19), rsync COPYING (3.5.1). 앱 안 라이선스 화면에 등록 (Android 포함),
  [고지 문서] 를 Android 에서도 앱 안에서 연다. Windows 는 실행 파일 옆 · tools · rsync 에도 둔다.
  고지문: yt-dlp.exe 는 GPLv3+, FFmpeg 9.0.2 · rsync 3.5.1 · FFmpegKit (FFmpeg n8.1.2) 판 번호와 소스 주소,
  LibreOffice · Temurin · H2Orestart 0.7.14, Real-ESRGAN, Android 판 GPLv3 대응 소스 (같은 판 번호의 태그)
- 156: Android 에서 CUDA 장치를 잘못 세던 것 (사진용 모델을 찾음). 자동이면 받아 둔 모델을 쓰고,
  없으면 그 자리에서 만화 · 그림용 / 사진용을 골라 받은 뒤 이어서 올린다
- 157: 모든 장이 끝나면 "n장 저장: 폴더" 와 [열기] (화면 · 작업 현황 · Android 알림 · 작업 기록)
- 159: 프롬프트 · 만든 그림 목록을 설정에 남겨 되살림, 빈 프롬프트는 바로 보이게 알림
- 152: 모델 읽는 동안 지난 남은 시간이 남아 있던 것

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:20:15 +09:00
RyuJongJin 300ccdde99 AI 해상도: CPU 에서 자동이 만화용을 고르면 알리고 '사진' 모델로 바꿔 다시 할 수 있게
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 15:45:16 +09:00
RyuJongJin 986688eafa AI 해상도 올리기 (121) · AI 마무리 (150~153) · 받기 범위 확인 · 확인됨 표시
- 121: Real-ESRGAN (BSD-3) x4plus · x4plus anime 6B, 공식 주소 · SHA-256 고정. sd-cli -M upscale 로 4배 → 2 · 3배로 줄임
  · 보기 화면 [AI 해상도 올리기 (이 장 · 모든 장)], 진행 · 취소, [원본 / 올린 것] 비교 → 새 파일로 저장 (덮어쓰지 않음, ZIP 은 옆 폴더)
  · 모든 장은 백그라운드 (작업 알림 · 작업 현황, 남은 장 · 예상 시간)
  · 설정: 모델 (자동 = GPU 면 사진용 · CPU 면 만화용) · 배율 · 형식 · 저장 위치 · 저장한 뒤, 자동 장치는 Vulkan → CUDA → CPU
- 150: 취소는 "취소했습니다" 만 (회색), 실패는 쉬운 한 줄 + 엔진 원문은 [자세히]
- 151: 그리는 · 올리는 동안 쓰는 모델은 지우기를 끔
- 152: 모델 읽는 동안 남은 시간 없음 · 돌아와도 자판이 올라오지 않음
- 153: 지워진 결과 그림은 "파일이 없습니다" · [목록에서 빼기]
- 받기: 206 의 범위 시작이 다르거나 맞지 않는 416 이면 조각을 버리고 처음부터, 받은 파일에 "확인됨" (SHA-256)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 15:31:13 +09:00
RyuJongJin 92556f62ed AI 모델 받기: 이어 받기 · 화면이 꺼져도 계속 · 쉬운 실패 문구 (149) · 진행 · 차례 · 공간 표시 (121 받기 ①②③)
- 149: 받다 만 조각을 남겨 Range 로 받은 곳부터 이어 받고 (SHA-256 은 조각까지 합쳐 확인),
  끊기면 저절로 몇 번 다시 잇는다. 실패는 "연결이 끊겼습니다 · [이어 받기]" 한 줄 (서명 주소는 보이지 않음).
  Android 작업 알림에 받는 진행 (화면이 꺼지거나 다른 앱으로 가도 계속). 라이선스 동의는 한 번만.
- ① % · 받은 크기 / 전체 · 남은 시간, ② 한꺼번에 받을 때 "차례 기다림", ③ 필요한 공간 · 남은 공간을 늘 한 줄로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 14:58:22 +09:00
RyuJongJin 7d6c653d5f AI 그림 1차 (123) · WebDAV 안내 (133 · 134 · 142 · 143) · 긴 경로 휴지통 보호 · AI 화면 작은 것 (139 · 140 · 141)
- 123: 기기 안 그림 만들기 (stable-diffusion.cpp 228c707, SD1.5 + LCM-LoRA + TAESD)
  · 공식 배포처 · SHA-256 고정, OpenRAIL-M 용도 제한을 보이고 동의 뒤 받기, 공간 확인
  · Windows 실행 파일은 받음 (Vulkan, NVIDIA 면 CUDA 12), Android 는 APK 에 libsdcli.so (tool/build_sdcpp_android.sh)
  · 처리 장치 자동 (짧게 재서 가장 빠른 것), 진행 · 남은 장 · 예상 시간 · 취소, 작업 알림 · 작업 현황
  · 글 → 그림 · 그림 → 그림, 결과는 새 PNG + json (프롬프트 · 시드 · 모델), 보기 화면의 [AI 그림으로]
  · 사용자가 추가하는 서버 · 서비스 (Automatic1111 · ComfyUI · OpenAI 형식), 기기 밖으로 나간다고 늘 알림, API 키는 안전 저장소
  · 컴포넌트 "AI 그림" · 환경 설정 > AI 그림 · 라이선스 고지
- 133 · 134 · 142: WebDAV 를 읽는 곳에서 마스터로 막히면 [마스터 비밀번호 넣기], 서버에 닿지 않으면 알아볼 수 있는 말
- 143: WebDAV 서버 고치기 창에서도 지우기
- 긴 경로 (260자 넘음) 는 휴지통이 받지 않아 Windows 가 묻지 않고 영구 삭제하던 것 - 보내기 전에 막고 이유를 알림
- 139 · 140 · 141: 받을 크기 한 값으로 · 뒤로 버튼 이름 · 시드 칸 이름, 바탕 그림 작은 그림
- Windows 확인용 화면 캡처 시험 (integration_test/windows_check_shot_test.dart)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 14:35:57 +09:00
RyuJongJin 70056b0db3 보안 · 비밀번호 (124 · 127~132) · 쿠키 (53) · http 경고 (56) · 안내 고침 (116 · 125 · 126) · 시험 안정화
- 124: 최상 · 마스터 비밀번호 (솔트 PBKDF2-HMAC-SHA256, 안전 저장소에 해시만), 묻는 때 (안전 저장소),
  저장된 비밀번호를 쓰는 곳의 문 (WebDAV · OpenSubtitles · 재생 · 환경 설정), 최상으로 초기화 · 잊었을 때 초기화,
  틀리면 기다리기, 서버 지우기 전 쓰는 곳 알림, 실시간 동기화 폴더에 WebDAV 고르기
- 127: 서버 고치기 창은 마스터 뒤에 (저장된 비밀번호가 보이지 않게)
- 128: 마스터 창은 하나로 · 취소하면 배경 작업은 묻지 않고 "기다리는 중" 으로 표시
- 129: 묻는 때를 설정 파일이 아니라 안전 저장소에 (값이 없거나 틀리면 쓸 때 묻기)
- 130: Windows secrets.dat 를 쓰기 · 지우기 전에 다시 읽어 다른 창의 값과 합침
- 131: 앱 안 브라우저 쿠키를 쓰지 않게 바꾸면 내보낸 쿠키 파일을 지움
- 132: 최상 비밀번호 [끄기] 가 넓은 화면에서 잘리지 않게
- 53: 쿠키 파일 안내 · 넘길 사이트 고르기 · 쿠키 · 방문 기록 지우기 · 범위 넓힘 알림
- 56: http:// WebDAV 경고 · 인증서 확인 끄기가 다른 재생에 남지 않게
- 116 · 125 · 126: 권한 안내 다시 · "2-1 / 3장" · 바뀐 기본 설정 창을 닫으면 바로 저장
- 시험판 데이터 폴더 (JJ_MKVMAKER_DATA, Windows) · 휴지통 판정을 $I 정보로 · 모니터링 시험 기다림

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:22:47 +09:00
RyuJongJin 597d853943 비밀번호 안전 저장 · 동기화 안전장치 · 휴지통 · 시작 메뉴 알림 · 보기 화면 (계속 보기 · 두 쪽 · 회전) · 탐색기 배치 등
- 40/54: WebDAV · OpenSubtitles 비밀번호를 안전 저장소 (Android Keystore · Windows DPAPI) 로
- 95~114: rsync 지우기 확인 · 미리 보기 · 좁은 화면 확인 창 · 실시간 동기화 멈춤 알림
- 99: 시작 메뉴 바로 가기 (AUMID) · jjmkvmaker:// · Windows 알림 꺼짐 안내
- 102: 질문을 설정으로 · 시스템 언어 따르기
- 113 · 117~120: 보기 화면 막대 · 계속 보기 · 두 쪽 나눠 보기 · 회전 · 반전 · 좁으면 ⋮ 메뉴
- 115 · 116: 자동 백업 복원 알림 · 권한 안내와 자동 다시 분석
- 118 · 122: 탐색기 좌우 ⇆ / 위아래 ⇅ 버튼 · 경로로 가면 새로 읽어 펼침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:25:08 +09:00
RyuJongJin ec37fa28e4 MKV 화면: 좁은 화면에서도 변환 설명 · '원본보다 커서' 경고 보이기
근거: 감독 세션 지시 31번 (7번은 필요 없는 경고를 없애라는 것, 이 경고는 필요한 것).
- 좁은 화면 (폰 세로 · 접은 폴드) 의 변환 설정 줄에서 빠지던 설명 · ⚠ 경고를 한 줄을 다 써서 보여 줌 (말줄임 없이)
- 화면 · 색 보정을 켜도 ⚠ 경고가 함께 보이게 (보정 요약이 경고를 가리던 것)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:57:57 +09:00
RyuJongJin 3dba07dd62 설정 파일: 안전하게 저장 · 깨지면 백업으로 되살리고 알림
근거: 감독 세션 지시 20번 (설정 파일을 읽다 실패하면 말없이 모든 설정을 처음 상태로 켜고, 무엇이든 바꾸면 원래 설정이 영영 사라짐).

- 저장: settings.json.tmp 에 다 쓴 뒤 바꿔치기 (쓰는 도중 앱이 끝나도 깨지지 않게), 한 번에 하나씩.
  바꿔치기 전의 정상 파일은 settings.json.bak.
- 읽기 실패: 깨진 파일은 settings.broken-(시각).json 으로 보관 (다음 저장에 덮이지 않게), .bak 으로 되살림.
  settings.json 만 없으면 (바꿔치기 도중 끝남) .bak 으로. 백업도 없으면 처음 설정.
- 켤 때 알림 창 (Windows · Android): 백업으로 되살렸는지 / 처음 설정인지, 보관한 파일 위치, 원인. 작업 기록에도.
- 시험: settings_store_test (바꿔치기 · .bak · 깨진 파일 · 백업 없음 · .bak 만 있음).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:54:30 +09:00
RyuJongJin 951a1c927c MKV 화면 · 맨 위 줄 · 브라우저 주소 · 재생 버튼 · 브라우저 소리 설정 · 정리 창 체크
근거:
- 감독 세션 지시 (폰 · S10 Ultra 를 사용자처럼 써 본 결과) 2 · 3 · 4 · 7 · 8 · 9 · 13 · 15번
- 사용자 결정 (10/8): 브라우저 소리 멈춤을 설정으로 (기본 켜짐), 정리 창은 임시 · 받다 만 다운로드만 미리 체크

- 2: 좁은 화면 MKV 화면 둘째 줄은 글자를 숨기지 않고 옆으로 밀어 봄, [MKV 만들기] 맨 앞.
     변환 설정 줄은 줄바꿈 ([화면 · 색 보정] 이 세로에서 안 보이던 것).
- 3: 좁은 화면 맨 위 줄: 이동 아이콘 아래 짧은 이름 (홈 · MKV · 브라우저 · 탐색기 · Rsync · 다운로드 · 뒤로),
     [환경 설정] 옆에는 ⋮ 메뉴 (화면 크게 · 작게 · 기본 크기 · 종료) - 종료가 설정 바로 옆이라 잘못 누르던 것.
- 4 · 15: 브라우저 주소 칸이 좁으면 (폰 세로 · 태블릿 세로 · 폰 가로) 홈 · 번역 · 즐겨찾기 · 작업 현황 · 외부 브라우저를
     ⋮ 메뉴로 (없애지 않음). 주소가 "http" 만 보이거나 사라지던 것.
- 7: Android 에서 "탐색기에서 끌어다 놓아도" 안내 뺌. 켤 때 "⚠ 사용할 수 없는 코덱" 기록 뺌 (코덱 목록에 "(사용 불가)" 로 보임).
- 8: 좁은 화면에 동영상이 없으면 한 화면에 안내 + [동영상 추가].
- 9: Android 재생 화면 버튼 크게, 재생 버튼 줄과 나머지 줄을 나눔.
- 13: 넓은 화면의 동영상 목록 칸을 화면 너비의 40% (320~640) 로 - 태블릿 세로에서 제목이 앞부분만 보이던 것.
- 사용자 결정: 환경 설정 > 웹 브라우저 "다른 화면으로 가면 페이지 소리 멈춤" (기본 켜짐).
- 사용자 결정: 저장 공간 정리 창은 작업 임시 파일 · 받다 만 다운로드만 미리 체크.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:36:21 +09:00
RyuJongJin fead23ce2f 파일 탐색기: 폰 한 창 · 한 번 누르면 실행 · 경로 이름 · 권한 · WebDAV 오류 안내 · 첫 화면 바로
근거: 감독 세션 지시 (폰 Z Fold6 · S10 Ultra 를 사용자처럼 써 본 결과) 1 · 5 · 6 · 10 · 11 · 12 · 14번.
감사 A-13 (지시와 반대였던 기본값) 되돌림 포함.

- 1: "모든 파일 접근" 권한이 없으면 목록 위에 이유 · 할 일 · [권한 허용 화면 열기] · [다시 확인].
     듀얼 앱 (복제한 앱) 이면 그 사실과 원래 앱으로 여는 방법을 안내. [동영상 추가] 권한 화면에도.
     허용하고 돌아오면 다시 확인해 목록을 새로 읽음.
- 5 · 14: 경로를 "SD 카드 › JJ_sdtest › jj_mkv" · "디바이스 저장공간 › Download" · "☁ ds1815 › 폴더" 로 (Android · WebDAV).
- 6: Rsync 창이 위아래면 버튼도 "위 → 아래 · 위 ← 아래 · 위 ⇄ 아래" (화살표도).
- 10 · 5: 기본값을 사용자 입장으로 - 창 배치 기본 '화면 크기 따라' (넓으면 두 창, 폰 세로는 한 창),
     Android 는 한 번 누르면 바로 실행. 예전 기본값 (사용자가 고른 것 아님) 은 한 번 옮김, 고른 값은 그대로.
- 11: 창마다 따로 읽음 - WebDAV 서버를 기다리는 동안에도 이 기기 파일이 바로 보임 (읽는 중 막대).
- 12: 읽을 수 없으면 이유 (서버에 닿지 않음 → 같은 네트워크 · Tailscale, 비밀번호, 인증서, 권한, 없음) ·
     [다시 시도] · [서버 설정 고치기].
- 감사 A-13: ZIP 은 기본 목록 보기 (사용자 지시 10-08 "zip 은 기본 목록 보기, 만화 보기는 설정에서"). 예전 기본 (만화) 은 한 번 끔.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:23:48 +09:00
RyuJongJin b39678f8f6 Android 화면 방향: 기본 자동 (가로 고정 강제 없앰)
근거: 사용자 지적 ("가로 · 세로를 왜 막았나, 지시한 적 없다") + 감독 세션 지시.
예전 결정 (bf6429d · bca0c04, 내 판단 - 사용자 지시 없음): "데스크톱 화면이니까" 시작할 때 가로 고정을 강제하고
기본값 · 모르는 값도 가로 고정으로 해 휴대폰 · 태블릿의 세로 보기를 막았다.

- 시작할 때 가로 고정 강제 (runAndroid) 삭제
- 기본값 auto, 값이 없거나 틀리면 auto
- 예전 설정 파일 (orientationV2 없음) 의 가로 고정은 사용자가 고른 값이 아니므로 한 번 auto 로
- applyScreenOrientation: 모르는 값은 기기 방향 따라 (빈 목록)
- 환경 설정 > 화면 방향: 자동 (기본) · 가로 고정 · 세로 고정
- 시험: player_test 화면 방향

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 22:08:15 +09:00
RyuJongJin 9d7df5f555 v2026.10.08_001
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:16:16 +09:00
RyuJongJin 1be1bf928b Windows 플러그인 등록 갱신 (pdfrx · url_launcher)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:07:24 +09:00
RyuJongJin 70e2b87c11 문서 미리보기 컴포넌트 · 컴포넌트 설치 · 브라우저 위아래 · 화면 순서 버튼
- ComponentStore: GitHub 릴리스 "components" 의 components.json 을 읽어 설치 (받기 · SHA-256 확인 · 풀기) · 제거.
  앱 데이터 폴더 components/(id) 에 둔다 (앱 업데이트에 지워지지 않게).
- 문서 미리보기 (Windows): LibreOffice 26.2.6 (MSI 관리 설치, 관리자 권한 없이) + Temurin JRE 21 + H2Orestart
  (HWP · HWPX). DOC · DOCX · HWP · HWPX · XLS · XLSX · PPT · PPTX · ODT 등을 PDF 로 바꿔 보기.
  Android 는 다른 앱으로 연다.
- 환경 설정 > 컴포넌트: 문서 미리보기 켜면 진행 창과 함께 설치, 끄면 지움.
- 버전 목록: 이 기기용 설치 파일이 있는 버전 릴리스만 (components 릴리스 제외).
- 웹 브라우저: 좌우로 밀기는 위쪽 막대에서만 (웹 페이지의 위 · 아래 스크롤과 다투던 것).
- 화면 순서: 끌어 놓기 대신 ▲ ▼ (화면 배율 때문에 놓는 자리가 어긋나던 것).
- 시험: component_store_test (JJ_COMPONENTS_URL 일 때 실제 설치 · HWP 변환), components_test 순서 버튼.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 16:18:50 +09:00
RyuJongJin ac8fd8ad9a 이미지 · PDF · ZIP 보기 (컴포넌트 "viewer")
- reader_page: 그림 · 만화 · PDF 보기. 오른쪽 1/3 누르면 다음, 왼쪽은 이전, 가운데는 막대 보이기 · 숨기기,
  좌우로 밀어도 넘김. 한 쪽 맞추기 · 좌우 맞추기, 넘기는 방향 (오른쪽→왼쪽 만화), 어둡게 · 밝게, 쪽 막대.
- 그림: 탐색기의 지금 정렬 순서대로 같은 폴더 그림을 넘김. 대상 확장자는 환경 설정 > 파일 탐색기에서 고름.
- PDF (pdfrx): 보기, 페이지 지우기, 그림을 페이지로 넣기, 저장 (빈 새 문서에 보이는 순서대로 조립 -
  원본 문서 페이지 목록을 바꾸면 pdfrx 가 엉뚱한 페이지를 남겼음). 원본은 사본으로 열어 덮어쓰기 가능하게.
- 그림 → PDF: 탐색기 메뉴 "PDF 로 만들기" (표시한 그림들을 한 장씩).
- ZIP · CBZ: 목록 · 골라서 / 모두 풀기 (다른 창 · 새 폴더 · 같은 폴더), 만화 보기 (설정에서 바로 보기 켜기 · 끄기).
- 탐색기 메뉴 "보기", WebDAV 파일도 (받아서).
- 시험: reader_test (ZIP · 넘기기 · 설정), integration_test/pdf_test (실제 pdfium).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:24:47 +09:00
RyuJongJin 6eb0dfc70a 컴포넌트 · 화면 순서 · 좌우로 밀어 화면 이동 · 알림 아이콘
- app/components.dart: 기능 묶음 (MKV · 웹 브라우저 · 파일 탐색기 · Rsync · 다운로드 · 이미지/PDF/ZIP 보기 · 문서 미리보기).
  설정 components (설치한 것) · navOrder (화면 순서) · swipeNav.
- 위쪽 이동 버튼: 설치한 화면만 순서대로 (하나뿐이면 그것만). 홈 화면을 제거했으면 설치된 첫 화면.
- SwipeNav: 화면 가운데를 왼쪽으로 밀면 다음, 오른쪽으로 밀면 이전 화면 (끝 다음은 처음).
- 환경 설정 > 컴포넌트: 켜기 · 끄기, 화면 순서 (끌어서), 좌우로 밀기 켜기 · 끄기.
- Android 작업 알림: 받는 중이면 내려받기 아이콘, 동기화 · 작업 중이면 좌우로 오가는 화살표 (animation-list).
- 시험: components_test (설치 · 순서 · 저장, 버튼 줄, 좌우로 밀기).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:37:24 +09:00
RyuJongJin 2553107aa5 좁은 화면 (접은 폴드 · 휴대폰 세로) 배치 정리
- AppTopBar: 폭 720 미만이면 위쪽 막대를 두 줄로 (위: 화면 이동 · 화면 크기 · 설정 · 종료, 아래: 화면의 도구).
  모든 화면 (MKV · 브라우저 · 탐색기 · Rsync · 모니터링 · 다운로드 · 설정 · 플레이어 · 자막 편집 · 작업 현황 · 즐겨찾기) 에 적용.
  접은 폴드에서 설정 · 종료 버튼이 화면 밖으로 밀려 누를 수 없던 것.
- MKV 화면: 좁으면 목록 위 · 자세히 보기 아래, 변환 설정 줄은 옆으로 밀어 보기, 자막 버튼은 줄바꿈.
- 대화 상자 37개: 내용이 화면보다 길면 스크롤 (가로 폴드에서 버튼이 내용과 겹치던 것).
- SettingTile: 좁은 화면에서 설정 줄의 고르기 상자 · 입력 칸을 설명 아래로.
- 다운로드 도구 줄 옆으로 밀어 보기, 빈 목록 안내 글 여백.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:10:04 +09:00
RyuJongJin 44ae09a31f 플레이어: 건너뛴 뒤 다음 편으로 넘어가던 것 · 지운 WebDAV 서버를 보던 창
- mpv keep-open=always: 파일 끝에서 mpv 가 스스로 다음 파일로 넘어가지 않게 하고 앱이 정한다.
  건너뛴 직후 (3초 안, 끝 5초 전보다 앞) 의 "끝" 은 잘못 본 것 → 그 곳으로 키프레임 기준 다시 건너뜀.
  진짜 끝이면 다음 파일로 넘어가 계속 재생.
- 탐색기: 설정에서 지운 WebDAV 서버를 보던 창은 이 기기의 첫 저장 장치로.
- integration_test/seek_repro_test.dart (JJ_SEEK_FILE 일 때): 여러 곳 건너뛰기 · 진짜 끝에서 다음 파일.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 13:42:47 +09:00
RyuJongJin b3707e2ce5 WebDAV 동영상: 받지 않고 바로 스트리밍 재생
- 플레이어 (mpv) 에 주소 + 인증 헤더로 넘김 (자체 서명 인증서면 tls-verify=no), 건너뛰기는 서버 Range 요청
- 같은 폴더 동영상으로 재생 목록, 같은 폴더 자막은 작으니 임시로 받아서
- 외부 플레이어 · VLC · 기본 프로그램에는 아이디를 넣은 주소로
- 탐색기의 "이 폴더의 동영상 재생" 도 WebDAV 에서

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 12:12:53 +09:00
RyuJongJin 8f779fa7e6 WebDAV: 파일 탐색기 · Rsync 화면 · lsync 에서 연결 · 복사 · 동기화
- 환경 설정 > 파일 탐색기 > WebDAV: 서버 추가 · 고치기 · 지우기 · 연결 확인 (기본 인증, 인증서 확인 안 함)
- 위쪽 "SD 카드" 옆 서버 탭 (없으면 "WebDAV" 탭 → 바로 추가), 경로 dav://서버/경로
- 목록 · 새 폴더 · 이름 변경 · 삭제 · 찾기 · 복사 · 이동 (같은 서버는 MOVE), 열기 · 재생은 임시로 받아서
- Rsync · lsync: 한쪽이 WebDAV 면 앱이 크기 · 시각 비교로 맞춤 (-u · --remove-source-files 따름)
- PROPFIND 에 Content-Length (chunked 면 일부 서버가 남은 본문을 다음 요청으로 오해)
- Android: http:// NAS 연결 허용 (usesCleartextTraffic)
- 화면에는 내부 id 대신 "☁ 서버 이름/경로"
- 시험: webdav_test (시험 서버) · explorer_webdav_test · real_dav_test (JJ_REAL_DAV 일 때 실제 서버)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 11:30:54 +09:00
RyuJongJin 0cf7a9a7b7 업데이트: 올려 둔 모든 버전 중 골라 설치 (예전 버전으로 되돌리기) · Rsync 화면 · 모니터링 개선 · 두 번째 실행 충돌 수정
- 설정 [업데이트 · 버전 고르기]: GitHub 의 모든 릴리스 (지금 · 최신 · 시험판 표시, 바뀐 내용) 중 골라 설치.
  새 버전으로 · 예전 정상 버전으로 되돌리기 · 다시 설치. 자동 확인 (하루 한 번) 은 지금처럼 최신만.
- 버전을 바꾸기 전 지금 버전의 설정 (설정 · 동영상 목록 · 즐겨찾기 · 창 위치) 을 version_snapshots 에 보관
  (자막 API 키 · 비밀번호는 넣지 않음). 그 버전으로 돌아오면 (lastRunVersion 이 다르면) 되살릴지 묻고 다시 시작.
- Windows 되돌리기: 설치 스크립트가 Lib 를 그 버전과 똑같이 맞춤 (robocopy /MIR, 받은 파일 · 모델 폴더 제외).
- Android 되돌리기: 낮은 버전은 위에 설치 불가 → APK · 설정을 Download/JJ_MKVMaker 에 저장 후 앱 지우기 창,
  다시 설치한 앱이 시작할 때 저장해 둔 설정을 되살릴지 묻는다.
- 두 번째 실행 (앱이 켜져 있을 때 다시 실행) 이 끝나며 coremessaging.dll 접근 위반으로 꺼지던 것: 바로 끝내기로.
- Rsync 화면: 새 폴더를 만들면 그 줄로 스크롤하고 그 창의 원본 · 대상으로 바로 고름 (트리 위치 계산도 폴더만 기준으로).
- 모니터링: 끝나지 않은 (실행 중 · 실패 · 취소) 작업도 lsync 로 이동 (실행 중이면 물어보고 멈춘 뒤),
  진행 막대 위 = 지금 파일 (그 파일의 % · 전송 속도), 아래 = 폴더 전체 (rsync to-chk 기준 항목 수),
  전송 속도 표시 (rsync -P · 현재 방식은 최근 3초)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 09:05:45 +09:00
RyuJongJin 1ac48354af rsync 원본 파일 지우기 (--remove-source-files) · 빈 폴더 정리 · lsync 최종 정리
- Rsync 화면의 한 방향 (좌 → 우 · 좌 ← 우): 확인 창에서 [원본 파일 지우기 (--remove-source-files)] 를 고를 수 있고,
  고르면 끝난 뒤 원본의 빈 폴더를 지움 (find 원본/ -type d -empty -delete) - 원본 폴더는 남김 / 지움 중 고르기.
  양쪽 (⇄) 에는 없음
- 모니터링 lsync 의 쌍마다 [최종 정리]: rsync -avHPOg --remove-source-files 원본/ 대상/ 뒤 빈 폴더 지우기,
  원본 폴더 남김 / 지움 고르기. 원본이 비므로 그 실시간 동기화는 끔 (지우기 포함이면 대상까지 비우지 않게),
  진행은 복사 · rsync 탭에 남음
- 기억한 작업에 원본 정리 (prune) 저장, 모니터링 카드에서 바꾸기 · 진행 막대에 "빈 폴더 N개 지움"
- 원본 정리를 고른 이동은 예전의 "비면 원본 폴더째 지우기" 대신 고른 대로
- Windows 실제 rsync 로 시험 (integration_test/rsync_page_test.dart)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 21:47:20 +09:00
RyuJongJin c273297b11 Rsync 화면 (파일 탐색기 옆) · 파일 탐색기에서 모니터링 · rsync · lsync 빼기
- 위쪽 파일 탐색기 옆 [Rsync]: 파일 탐색기와 같은 모양, 늘 좌우 두 창 · 폴더만 · 창마다 폴더 하나만 고르기
- 가운데 좌 → 우 (왼쪽 → 오른쪽) · 좌 ← 우 (오른쪽 → 왼쪽) · 좌 ⇄ 우 (둘 다 함께, -u 로 받는 쪽의 새 파일은 덮어쓰지 않음)
  폴더 안의 것을 맞춤 (원본/ → 대상/), 확인 창에 경로 · 옵션, 진행 막대는 방향마다
- 가운데 [모니터링]: 실행한 rsync 가 남아 옵션 고치기 · 다시 실행 · lsync 로 옮기기 · 일정 (앞의 모니터링 그대로)
  확인 창에서 취소하면 남기지 않음, 고른 폴더는 다음에 열 때 그대로 (rsyncPaths)
- 파일 탐색기: 모니터링 버튼 · rsync 빼고 현재 방식 · robocopy 만 (예전 값 rsync 는 현재 방식으로), 복사는 모니터링에 남기지 않음
- 환경 설정: [Rsync] 묶음 (rsync 옵션 · 가져오기 · 실시간 동기화 · 백그라운드로 실행), 복사 모니터링 스위치 없앰
- Windows 실제 rsync 로 → · ⇄ 시험 (integration_test/rsync_page_test.dart)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 18:28:32 +09:00
RyuJongJin 9ce55bbb74 복사 · 이동 시작 전 확인: 없는 원본 · 없는 대상 · 폴더를 자기 안으로 · 같은 폴더로 이동
- 다른 곳에서 지워진 폴더 (탐색기 목록에는 남아 있던 것) 를 복사 · 이동하면 rsync 오류 대신 알리고 목록을 새로 고침
- 폴더를 자기 자신 (또는 그 안) 으로 복사 · 이동하는 것을 막음 (현재 방식만 막고 있었고 rsync 는 막지 않았음)
- 모니터링의 실행 · 재개도 같은 확인: 시작하지 않고 실패와 이유를 남김

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 14:08:53 +09:00
RyuJongJin ae1592d244 README: rsync 준비 (fetch_rsync.ps1 · build_rsync_android.sh)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:58:08 +09:00
RyuJongJin c5a44f2f3f 폴더 선택 = 자기 자신과 안의 것 전체 · rsync 앱에 내장 (Windows · Android)
- 선택 모드: 폴더를 누르면 자기 자신과 안의 파일 · 폴더 모두 선택 (펼쳐서 ✔), 다시 누르면 모두 취소.
  고른 폴더 안의 항목 하나를 누르면 그것만 빠지고 나머지는 고른 채로
- rsync 내장: Windows 는 실행 파일 옆 rsync\ (tool/fetch_rsync.ps1, MSYS2 패키지 SHA256 고정),
  Android 는 NDK 로 rsync 3.4.1 소스를 빌드한 librsync.so (tool/build_rsync_android.sh, jniLibs)
- rsync 가져오기 기본 = 앱에 들어 있는 것 (Windows 는 없으면 내려받기), 폴더 복사 기본 = rsync (예전 기본값도 rsync 로)
- Android rsync 는 -8 로 한글 이름을 그대로 출력, lsync (실시간 동기화) 도 rsync 로 동작

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:57:59 +09:00
RyuJongJin 967b729e50 Android 브라우저 점검 수정: 뒤로 키 · 알림 · 백그라운드 복귀 화면
- Android 뒤로 키: 앞 웹 페이지가 있으면 웹 페이지 뒤로 (위쪽 ← 화면 이동은 그대로 브라우저 닫기)
- 실행 취소 · 목록 보기 · 허용 알림이 사라지지 않던 것 (Flutter 3.47 부터 action 이 있으면 기본 persist) → persist: false
- 웹뷰를 텍스처로 그림 (useHybridComposition: false): 백그라운드로 실행 중 앱을 밀었다가 다시 열면
  Impeller "EGL Bad Access" 로 앱 화면이 비던 것

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:51:14 +09:00
RyuJongJin fa0b7e56b2 백그라운드로 실행 · 다시 켤 때 동기화 (바로 / 골라서 / 시작 안 함)
- 설정 > 파일 탐색기 > 실시간 동기화 (모니터링 > lsync 위에도): [백그라운드로 실행], [앱을 다시 켤 때 동기화]
- Windows: 백그라운드로 실행 = 창 ✕ 를 누르면 트레이로 (종료 동작 설정과 같은 값), 끄면 닫을 때 종료
- Android: 켜면 ← · 최근 앱에서 밀어도 Dart 엔진을 살려 두고 (엔진 캐시) 동기화 · MKV 만들기 · 다운로드 계속,
  작업 알림 서비스가 동기화를 지켜보는 동안에도 유지 (Android 14+ specialUse), [종료] 는 프로세스까지 끝냄
- lsync: 이번 실행 동안 멈춤 / 시작, 모두 멈춤 / 모두 시작, 켤 때 고르기 창

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 11:53:20 +09:00
RyuJongJin b38253fd67 복사 모니터링 · 복사 기억 · lsync 일정 (cron + 격자 편집)
- 설정 [복사 모니터링] 켜면 파일 탐색기 가운데에 [모니터링] 버튼
- 복사 · rsync 탭: 복사 대상 기억, 진행률, 대상 디스크 용량, 옵션 고치기 → 저장 후 현재 유지 / 설정 반영 후 실행, 실행 · 재개 · 취소 · 지우기, lsync 로 이동
- lsync 탭: 맞출 파일 자동 표시, 지금 맞추기, 복사 · rsync 로 이동
- 동기화 시간: 계속 / cron 시간 창 (요일 × 시 격자로 칠해서 편집)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 11:16:05 +09:00
RyuJongJin 7308f8ce1b 파일 탐색기: 복사 · 이동 방법 (현재 방식 · rsync · robocopy), 속도 제한, 실시간 동기화, 아래 진행 막대
- 환경 설정 > 파일 탐색기: 파일 / 폴더 복사 방법, rsync · robocopy 옵션 (기본 -avPog · /E /COPY:DAT /DCOPY:T /R:2 /W:2,
  바꿀 수 있음), 여러 개를 고르면 항목마다 따로 (기본) / 한 번에, 속도 제한 KB/s (rsync --bwlimit · 현재 방식 앱 조절 · robocopy /IPG)
- rsync (Windows): 처음 쓸 때 MSYS2 공식 저장소에서 rsync 3.5.1 과 필요한 DLL 을 내려받기 (파일마다 SHA256 고정 검증),
  설정 폴더 rsync\ 에 둔다. 경로는 /cygdrive/c/... 로 바꿔 넘김. 또는 직접 지정 (Android 도 지정하면 사용)
- robocopy (Windows): 폴더마다 실행, 파일은 같은 폴더끼리, 출력에서 파일 수를 세어 진행 표시
- 실시간 동기화 (lsyncd 처럼): 원본 · 대상 폴더 쌍, Windows 는 폴더 감시로 바로, Android 는 30초마다, 방법 · 지우기 선택
- 복사 · 이동 진행: 아래에서 올라오는 막대 두 줄 (위: 전체 항목 중 · 아래: 지금 폴더의 파일 중), 끝나면 내려감
- 선택 모드의 ✔: 고르지 않은 것은 흐리게, 고른 것은 진하게
Android 태블릿 (Galaxy Tab S6 Lite) 에서 속도 제한 복사 · 진행 막대 확인. rsync 는 실제로 내려받아 시험 (JJ_NET_TESTS=1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:38:09 +09:00
RyuJongJin 626117995d 파일 탐색기: 폴더 한 번 누르기 = 펼치기, 선택 모드 (X-plore 처럼)
- 폴더는 한 번 누르면 바로 펼치기 · 접기 (오른쪽 파일 목록은 들어가기). 두 번 누르기는 파일만
- 선택 동그라미는 평소 숨기고 선택 모드에서만 (✔ 표시). 가운데 [선택] 버튼 · 길게 누르기 메뉴 [선택] · Ctrl + 클릭으로 시작
- 선택 모드에서 누르기: 파일은 선택 · 취소, 폴더는 처음엔 선택, 고른 폴더를 다시 누르면
  폴더는 풀고 펼쳐서 안의 파일 · 폴더를 모두 선택
- 아래 막대: n개 표시함 · 모두 선택 · 표시 지우기 · 선택 끝. 복사 · 이동 · 삭제가 끝나면 선택 모드도 끝
- 화면을 닫은 뒤 끝난 파일 작업이 닫힌 창을 다시 그리려던 오류 수정
Android 태블릿 (Galaxy Tab S6 Lite) 에서 확인.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 09:43:28 +09:00
RyuJongJin 35ca463287 파일 탐색기: 어느 배치에서나 복사 · 이동 → 붙여넣기
"폴더 + 파일 목록" · 한 창 배치에서는 복사 · 이동이 막혀 있었다 (다른 창이 없음).
길게 누르기 메뉴 · 버튼의 [복사] · [이동] 으로 담아 두고, 넣을 폴더에서 [붙여넣기] (메뉴 · 버튼).
두 창은 지금처럼 "다른 창으로 복사 · 이동" 도 그대로. 붙여넣은 폴더는 펼쳐서 새 항목이 바로 보인다.
Android 태블릿 (Galaxy Tab S6 Lite) 에서 확인.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 08:48:56 +09:00
RyuJongJin a485d0d997 파일 탐색기: 한 번 = 선택 · 두 번 = 실행, 스타일 (X-plore · Windows 탐색기 · Total Commander), 폴더 + 파일 목록 배치
- 환경 설정 > 파일 탐색기: 누르기 (한 번 선택 · 두 번 실행 (기본) / 한 번 바로 실행), 스타일, 창 배치
- 선택 모드: 폴더는 고르기만 (› 화살표로 펼치기), 두 번 누르면 펼치기 · 실행, Ctrl + 클릭 = 여러 개 고르기,
  길게 누르기 · 오른쪽 클릭 = 기능 메뉴. 표시 동그라미는 두 번 누르기를 기다리지 않고 바로
- 스타일: X-plore (썸네일 · 두 줄), Windows 탐색기 (컬러 아이콘 · 이름 / 수정한 날짜 / 유형 / 크기 열),
  Total Commander (촘촘한 줄 · [폴더] · 확장자 / <DIR> / 날짜 열)
- 창 배치 "폴더 + 파일 목록": 왼쪽 폴더 트리, 오른쪽 고른 폴더의 내용 (맨 위 ".." = 상위 폴더, 두 번 누르면 들어가기)
- 모양 · 배치가 바뀌면 보던 폴더로 다시 스크롤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 08:25:46 +09:00
RyuJongJin aa9c431a47 파일 탐색기: 복사 · 이동 확인 문구의 '?:' 정리
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 07:55:33 +09:00
RyuJongJin 058a57e558 파일 탐색기: 창 배치를 바꾼 뒤에도 보던 폴더가 보이게 (Android 태블릿에서 확인)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 05:47:15 +09:00
RyuJongJin 99024d3c6e 파일 탐색기 (X-plore 참고): 두 창 트리 · 가운데 기능 버튼 · 창 배치 · 버튼 구성
- 위쪽 공통 버튼에 [파일 탐색기], 환경 설정 > 홈 화면에 "파일 탐색기" (앱을 켜면 바로)
- 두 창 (트리: 누르면 그 자리에서 펼치기 · 접기), 저장 장치 고르기 (Windows 드라이브 · Android 저장소)
- 동영상: 누르면 기본 동작 (내장 플레이어 또는 확장자별 재생 프로그램), 길게 누르기 · 오른쪽 클릭 메뉴
  (내장 플레이어로 재생 · 다른 앱으로 열기 · MKV 목록에 추가 · 이름 변경 · 복사 / 이동 · 삭제 · 정보)
- 썸네일 · 해상도 · 길이 (작업 중에는 썸네일을 만들지 않음), 그림 파일 미리 보기
- 가운데 버튼: 상위 폴더 · 새로 고침 · 재생 · MKV 목록에 추가 · 새 폴더 · 이름 변경 · 복사 · 이동 · 삭제 ·
  찾기 · 정렬 기준 · 숨은 항목 · 내역 · 창 배치 · 버튼 구성
- 창 배치: 두 창 / 한 창, 좌우 / 위아래 / 화면 모양 따라, 버튼 줄 두 창 사이 / 끝 / 숨김
- 버튼 구성: 보일 버튼 · 순서
- 다른 앱으로 열기: Windows "연결 프로그램" 창 · Android 앱 고르기 (FileProvider, SD 카드 포함)
- 여러 개 지울 때 확인 창에 이름 · 폴더를 보여 준다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 04:14:05 +09:00
RyuJongJin bba955f6bf v2026.10.05_003
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 03:14:06 +09:00
RyuJongJin ed9f00118c Android 업데이트: 설치 허용이 꺼져 있으면 [설치 계속] 으로 받은 파일 그대로 설치
"이 출처의 앱 설치 허용" 설정 화면을 연 뒤 "다시 [업데이트]" 로 끝나
허용하고 돌아오면 같은 APK (약 90MB) 를 처음부터 다시 받아야 했다.
설치 허용 필요 (PERMISSION) 를 따로 알려, 돌아와 [설치 계속] 을 누르면 받은 파일로 바로 설치한다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 03:12:45 +09:00
RyuJongJin 6dfb4776a2 v2026.10.05_002
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:49:32 +09:00
RyuJongJin 81d6e6afa3 업데이트 확인: 다른 버전이 남긴 확인 기록이면 다시 확인 · 결과를 작업 기록에
같은 PC 에서 설정 파일을 같이 쓰는 새 버전 (개발 빌드 등) 이 "확인함" 을 남기면
옛 버전 (Windows 배포 폴더) 이 20시간 동안 확인을 건너뛰어 10월 1일 판에 머물렀다.
확인한 버전 (lastUpdateCheckVersion) 을 함께 저장해 지금 버전과 다르면 바로 확인한다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:49:32 +09:00
RyuJongJin 7ddc32d5af README: 웹 브라우저 페이지 자동 번역 · JavaScript 끄기
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:05:31 +09:00
RyuJongJin 9ec7428cce v2026.10.05_001
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:04:57 +09:00
RyuJongJin be0c08834d 웹 브라우저: 페이지 자동 번역 · JavaScript 끄기 (환경 설정 > 웹 브라우저)
- 웹 페이지 자동 번역 (기본 끔): 다른 언어 페이지를 화면 언어로 (Google 번역, 원문 언어 자동 감지).
  주소창 옆 번역 버튼으로 원문 · 번역 바꾸기, 새로 생긴 글도 2초마다 번역 (lib/core/web_translate.dart)
- JavaScript 사용 (기본 켬): Edge · Android 는 바로 다시 읽어 적용, Chrome (CEF) 은 웹뷰를 새로 만든다.
  Edge 는 JavaScript 를 꺼도 번역됨 (TreeWalker 거르기 함수를 쓰지 않음)
- webview_cef: create 에 {url, javascript} 를 받아 CefBrowserSettings.javascript 를 끔 (JJ_PATCH.md 5)
- 통합 테스트 (Windows Edge · Android WebView): integration_test/web_translate_test.dart
  debug 빌드만 평문 http 허용 (기기 안 시험 페이지)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:58:53 +09:00
RyuJongJin 0f5c1e8391 앱 클래스 이름: JjCapCutApp → JjMkvMakerApp
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:58:51 +09:00
RyuJongJin d73281adfd 테스트 · 도구의 작업 폴더 경로: M:\jj_CapCut → M:\jj_MKVMaker
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:00:34 +09:00
RyuJongJin a973bcfda7 v2026.10.04_012
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:48:11 +09:00
RyuJongJin 758eb41d72 노란 앱 아이콘 (노랑·검정 두 색) 추가 및 환경 설정에서 앱 아이콘 고르기
- 아이콘 5종: 노랑(기본) · 검정 · 필름+JJ · 필름 · 처음 아이콘(파랑)
- Android: 아이콘마다 activity-alias, 중계 LauncherActivity 로 바꿔도 앱 화면 유지
- Windows: 창 · 작업 표시줄 · 트레이 아이콘, 앱 안 홈 버튼도 고른 아이콘으로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:47:00 +09:00
RyuJongJin a9f3cba991 v2026.10.04_011: 검은 아이콘 (노란 JJ)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:01:05 +09:00
RyuJongJin 397fc6408b 아이콘: 검은 바탕 (노란 J · J + 흰 재생 버튼 + 필름 띠), 앱 안 버튼도 검은 바탕에 노란 JJ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:00:08 +09:00
RyuJongJin 8fec4ae091 앱 안 홈 버튼: 작아도 또렷한 노란 JJ 아이콘 (런처 아이콘과 같은 색감)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 22:53:38 +09:00
RyuJongJin 6adb217857 아이콘: 왼쪽에 노란 J · J (위 · 아래), 재생 버튼은 오른쪽
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 22:39:58 +09:00
RyuJongJin bc14f66443 v2026.10.04_010: 새 아이콘
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 22:20:16 +09:00
RyuJongJin 38ae9fe9de 아이콘: 눈에 띄게 (빨강 → 주황 바탕 + 큰 흰 재생 버튼 + 필름 띠), Android adaptive icon
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 22:16:16 +09:00
RyuJongJin 3a79510702 v2026.10.04_009: 폴더 만들기 · 닫기
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 21:26:28 +09:00
RyuJongJin e3282c2c38 Android 파일 · 폴더 고르기: [닫기] 는 어느 폴더에서든 바로 닫기 (뒤로 가기 키는 위 폴더로)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 21:23:04 +09:00
RyuJongJin 4257b97305 Android 폴더 고르기: [이 폴더 선택] 왼쪽에 [폴더 만들기] (지금 폴더 아래에 만들고 그 폴더로)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 21:17:37 +09:00
RyuJongJin 7c487c41cd v2026.10.04_008: 이동 버튼 여러 개
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:54:28 +09:00
RyuJongJin d7efce87a6 이동 버튼 여러 개 (표시 이름 · 폴더, 환경 설정 > MKV 만들기), 세부 정보 오른쪽 아래에 표시 이름으로 위로 쌓기. 예전 이동 폴더는 '이동' 버튼으로
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:52:11 +09:00
RyuJongJin 22364ec16a v2026.10.04_007: 작업 알림 → 작업 현황, AI 취소
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:20:27 +09:00
RyuJongJin ad01fd5671 Android 작업 알림을 누르면 작업 현황 화면, 위쪽 작업 표시에 진행률 · 누르면 작업 현황, 앱 화면은 하나만 (singleTask). AI 음성인식 중 취소가 바로 먹히게 (whisper abort, Android 태그 포인터)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:18:27 +09:00
721 changed files with 112162 additions and 1188 deletions

View File

@ -24,7 +24,7 @@
| **탐색기 연결** | 오른쪽 클릭 "JJ_MKVMaker 로 재생" / "JJ_MKVMaker 로 자막 만들기", 끌어다 놓기. 더블클릭(연결 프로그램)으로 열면 바로 재생 또는 목록에 추가, 켜져 있는 창 / 새 창 (환경 설정) |
| **창 · 종료** | 창 위치 · 동영상 목록 기억, 여러 창의 목록 자동 맞춤. 종료(✕)를 누르면 백그라운드로 계속 / 모두 종료 / 매번 고르기 (환경 설정). 완전 종료는 트레이 아이콘 > 종료 |
| **다운로드** | Ctrl+C 로 복사한 YouTube 주소 → yt-dlp, 마그넷 · .torrent → aria2 (백그라운드, 트레이). 다 받은 영상은 편집 목록에 자동 추가 |
| **웹 브라우저** | 앱 안 브라우저(Edge WebView2)로 보다가 [다운로드], 즐겨찾기(Chrome · Edge · Whale 에서 가져오기), 로그인 쿠키를 yt-dlp 와 공유 |
| **웹 브라우저** | 앱 안 브라우저(Edge WebView2)로 보다가 [다운로드], 즐겨찾기(Chrome · Edge · Whale 에서 가져오기), 로그인 쿠키를 yt-dlp 와 공유, 다른 언어 페이지를 화면 언어로 자동 번역 (Google 번역), JavaScript 끄기 |
| **작업 대기열 · 화면 분할** | AI 자막 · 번역 · MKV 만들기를 차례로 백그라운드 실행, 브라우저 옆 [작업 현황] 에서 진행 확인 |
| **일괄 작업** | 동영상 목록에서 여러 개를 체크(전체 선택 · 이름/날짜 정렬) → [자막 만들기] · [자막 만들기 & MKV 만들기] · [선택한 파일 재생] · [MKV 만들기] |
| **상태 표시 · 화면 크기** | 모든 화면 오른쪽 위에 CPU · MEM · 다운로드 디스크 남은 용량, 화면 크기 [−] 100% [+], 환경 설정 · 종료 버튼 |
@ -76,10 +76,13 @@ git clone <이 저장소>
cd app
powershell -File tool\fetch_ffmpeg.ps1 # third_party\ffmpeg\windows 에 FFmpeg 준비
powershell -File tool\fetch_tools.ps1 # third_party\tools\windows 에 yt-dlp · aria2 · Deno 준비
powershell -File tool\fetch_rsync.ps1 # third_party\rsync\windows 에 rsync (MSYS2, SHA256 고정) 준비
flutter pub get
flutter build windows --release # build\windows\x64\runner\Release
```
Android 의 rsync: Git Bash 에서 `sh tool/build_rsync_android.sh` (Android NDK 로 rsync 3.4.1 소스를 빌드해 third_party/rsync/android 에 둠, APK 에 들어감)
테스트: `flutter test` (AI 모델 · 인터넷이 필요한 테스트는 자동으로 건너뜀)
### 구조
@ -101,9 +104,10 @@ Android 이식 시 `platform/android/` 구현과 `services/platform_services.dar
## 라이선스
- JJ_MKVMaker 소스 코드: [MIT](LICENSE)
- 포함 · 사용하는 구성 요소: [THIRD_PARTY_NOTICES.txt](THIRD_PARTY_NOTICES.txt)
- FFmpeg (GPLv3) · aria2 (GPLv2) 는 수정하지 않은 별도 실행 파일로 함께 제공
- libmpv (LGPL), whisper.cpp · ONNX Runtime (MIT), yt-dlp (Unlicense), Deno (MIT)
- 포함 · 사용하는 구성 요소: [THIRD_PARTY_NOTICES.txt](assets/licenses/THIRD_PARTY_NOTICES.txt) (라이선스 원문: [assets/licenses](assets/licenses))
- FFmpeg · yt-dlp 실행 파일 · rsync (GPLv3+) · aria2 (GPLv2+) 는 수정하지 않은 별도 실행 파일로 함께 제공
- libmpv (LGPL), PDFium (BSD), whisper.cpp · ONNX Runtime · stable-diffusion.cpp (MIT), Deno (MIT)
- **Android 판은 GPLv3 구성 요소 (FFmpegKit · youtubedl-android · rsync) 를 포함하며, 대응 소스는 같은 판 번호의 태그 (예: v1.2.3) 입니다.**
- **NLLB-200 번역 모델은 CC-BY-NC 4.0 (비상업적 이용만)** — 이 프로그램은 무료 배포이며, 모델은 사용자가 직접 내려받습니다
## 주의

View File

@ -1,82 +0,0 @@
JJ_MKVMaker - 포함된 오픈 소스 구성 요소 및 라이선스 고지
==========================================================
이 프로그램은 아래 오픈 소스 구성 요소를 포함하거나 내려받아 사용합니다.
각 구성 요소는 해당 라이선스를 따릅니다. 라이선스 원문은 같은 폴더(또는 ffmpeg\, tools\)의
*_LICENSE*, *_COPYING* 파일과 프로그램 안의 "환경 설정 > 라이선스" 화면에서 볼 수 있습니다.
[프로그램에 연결된 라이브러리]
- Flutter / Dart BSD 3-Clause https://github.com/flutter/flutter
- media_kit, media_kit_video MIT https://github.com/media-kit/media-kit
- libmpv (libmpv-2.dll) LGPL-2.1+ (-Dgpl=false 빌드)
https://github.com/mpv-player/mpv
빌드: https://github.com/media-kit/libmpv-win32-video-build
· libmpv 안의 FFmpeg LGPL-3.0 (--enable-version3, GPL 옵션 없음)
· libass, libplacebo 등 ISC / LGPL-2.1+
libmpv-2.dll 은 별도 파일이며, 같은 인터페이스의 다른 빌드로 바꿔 쓸 수 있습니다.
- ANGLE (libEGL.dll, libGLESv2.dll) BSD 3-Clause https://chromium.googlesource.com/angle/angle
- whisper.cpp / ggml MIT https://github.com/ggml-org/whisper.cpp
- whisper_ggml (JJ_MKVMaker 수정본) MIT https://pub.dev/packages/whisper_ggml
수정 내용: app\packages\whisper_ggml\JJ_PATCH.md
- ONNX Runtime (onnxruntime.dll) MIT https://github.com/microsoft/onnxruntime
- onnxruntime (Dart) MIT https://pub.dev/packages/onnxruntime
- window_manager, tray_manager, hotkey_manager, screen_retriever, nativeapi
MIT https://github.com/leanflutter
- flutter_inappwebview Apache-2.0 https://pub.dev/packages/flutter_inappwebview
- Microsoft Edge WebView2 SDK (WebView2Loader.dll)
BSD 3-Clause (Microsoft) https://www.nuget.org/packages/Microsoft.Web.WebView2
앱 안 브라우저는 Windows 에 설치된 Edge WebView2 런타임을 사용합니다 (Windows 11 기본 포함).
- desktop_drop Apache-2.0 https://pub.dev/packages/desktop_drop
- file_picker MIT https://pub.dev/packages/file_picker
- charset Apache-2.0 https://pub.dev/packages/charset
- cp949_codec BSD https://pub.dev/packages/cp949_codec
- Microsoft Visual C++ 런타임 (msvcp140.dll, vcruntime140*.dll)
Microsoft 재배포 가능 파일 (Visual Studio 라이선스의 Distributable Code)
- 그 밖의 Dart 패키지 라이선스: 프로그램의 "환경 설정 > 라이선스" 화면
[별도 실행 파일로 함께 제공 - 프로그램과 결합되지 않고 명령줄로 실행]
- FFmpeg (ffmpeg\ffmpeg.exe) GPL-3.0 https://ffmpeg.org
빌드: gyan.dev "release essentials" https://www.gyan.dev/ffmpeg/builds/
소스: https://ffmpeg.org/download.html 및 위 빌드 페이지의 소스 링크
원문: ffmpeg\FFMPEG_LICENSE.txt
- aria2 (tools\aria2c.exe) GPL-2.0-or-later https://github.com/aria2/aria2
소스: https://github.com/aria2/aria2/releases/tag/release-1.37.0
원문: tools\ARIA2_COPYING.txt
- yt-dlp (tools\yt-dlp.exe) Unlicense (퍼블릭 도메인) https://github.com/yt-dlp/yt-dlp
원문: tools\YT-DLP_LICENSE.txt
- Deno (tools\deno.exe) MIT https://github.com/denoland/deno
원문: tools\DENO_LICENSE.txt
위 GPL 프로그램은 수정하지 않은 공식 배포 파일이며, 해당 소스는 위 주소에서 받을 수 있습니다.
[Android 판 (APK) 에만 들어 있는 구성 요소]
- FFmpegKit (ffmpeg_kit_flutter_new_min_gpl, JJ_MKVMaker 수정본)
GPL-3.0 (FFmpeg GPL 빌드 포함) https://pub.dev/packages/ffmpeg_kit_flutter_new_min_gpl
수정 내용: app\packages\ffmpeg_kit_flutter_new_min_gpl\JJ_PATCH.md
- youtubedl-android (Python · yt-dlp · FFmpeg · aria2c 실행 파일 포함)
GPL-3.0 https://github.com/JunkFood02/youtubedl-android
· Python PSF License https://www.python.org
· QuickJS MIT https://bellard.org/quickjs/
· yt-dlp Unlicense
· FFmpeg GPL https://ffmpeg.org
· aria2 GPL-2.0-or-later https://github.com/aria2/aria2
수정하지 않은 공식 배포본 (Maven Central io.github.junkfood02.youtubedl-android 0.18.1) 이며,
소스는 위 주소에서 받을 수 있습니다.
- libmpv (media_kit_libs_android_video) LGPL-2.1+ https://github.com/media-kit/libmpv-android-video-build
[처음 사용할 때 내려받는 AI 모델]
- Whisper 모델 (ggml-base.bin 등) MIT (OpenAI Whisper) https://huggingface.co/ggerganov/whisper.cpp
- NLLB-200 distilled 600M (ONNX) CC-BY-NC 4.0 (비상업적 이용만 허용)
https://huggingface.co/facebook/nllb-200-distilled-600M
변환본: https://huggingface.co/Xenova/nllb-200-distilled-600M
※ NLLB 번역 모델은 상업적 용도로 쓸 수 없습니다. 판매·유료 서비스에 쓰려면
상업 이용이 가능한 번역 모델로 바꿔야 합니다.
[인터넷 서비스]
- OpenSubtitles.com REST API: 사용자가 발급받은 API 키로 이용하며 OpenSubtitles 이용 약관을 따릅니다.
- YouTube 등에서 받은 영상, 토렌트로 받은 파일의 저작권 책임은 이용자에게 있습니다.

View File

@ -17,6 +17,8 @@ analyzer:
- packages/webview_cef/**
- packages/flutter_inappwebview_android/**
- packages/ffmpeg_kit_flutter_new_min_gpl/** # 고친 외부 부품 (원본 코드 그대로)
- packages/flutter_inappwebview_windows/** # 고친 외부 부품 (Tor 화면 다운로드 막기 · 프록시 인증, JJ_PATCH.md)
- packages/whisper_ggml/** # 고친 외부 부품 (자기 analysis_options 가 flutter_lints 를 찾지 못함)
linter:
# The lint rules applied to this project can be customized in the

View File

@ -58,6 +58,23 @@ android {
}
}
// rsync (librsync.so 라는 이름의 실행 파일): tool/build_rsync_android.sh 로 third_party/rsync/android 에 준비.
// 네이티브 라이브러리 폴더에 풀려 실행할 수 있다 (앱 데이터 폴더의 파일은 Android 10+ 에서 실행 불가)
sourceSets["main"].jniLibs.srcDir("../../third_party/rsync/android")
// 123 · 121: stable-diffusion.cpp 의 sd-cli (libsdcli.so 라는 이름의 실행 파일): tool/build_sdcpp_android.sh
sourceSets["main"].jniLibs.srcDir("../../third_party/sdcpp/android")
// Tor (libtor.so 라는 이름의 실행 파일): 공식 Tor Expert Bundle (Android aarch64) 의 libTor.so, tool/fetch_tor_android.ps1 (판 · SHA-256 고정)
sourceSets["main"].jniLibs.srcDir("../../third_party/tor/android")
// 86: 7-Zip 25.01 (lib7zz.so 라는 이름의 실행 파일 - 7z · RAR 풀기): tool/build_7zz_android.sh (공식 소스, SHA-256 고정)
sourceSets["main"].jniLibs.srcDir("../../third_party/7zip/android")
// 195: third_party 의 실행 파일은 third_party_libs.txt 에 적은 것만 (공유 폴더에 다른 판이 있어도 섞이지 않게)
val thirdPartyLibs = file("third_party_libs.txt").readLines().map { it.trim() }.filter { it.isNotEmpty() && !it.startsWith("#") }.toSet()
listOf("../../third_party/rsync/android", "../../third_party/sdcpp/android", "../../third_party/tor/android", "../../third_party/7zip/android").forEach { dir ->
fileTree(dir).matching { include("**/*.so") }.forEach { so ->
if (so.name !in thirdPartyLibs) packaging.jniLibs.excludes += "**/${so.name}"
}
}
// ffmpeg · whisper 등 네이티브 라이브러리를 압축하지 않고 그대로 (설치 후 바로 로드)
packaging {
jniLibs {
@ -86,4 +103,8 @@ dependencies {
implementation("io.github.junkfood02.youtubedl-android:aria2c:0.18.1")
// 업데이트 APK 를 설치 화면에 넘기는 FileProvider
implementation("androidx.core:core:1.16.0")
// Tor 화면: 웹 뷰 기능 확인 · 전용 프로필 지우기 (flutter_inappwebview_android 와 같은 판)
implementation("androidx.webkit:webkit:1.12.0")
// 88: Android SMB (SMB2 · SMB3, SMB1 없음) - Apache-2.0. 고지: THIRD_PARTY_NOTICES_ANDROID · 라이선스 화면
implementation("com.hierynomus:smbj:0.15.0")
}

View File

@ -5,3 +5,11 @@
-keep class org.apache.commons.compress.archivers.zip.** { *; }
-dontwarn com.fasterxml.jackson.**
-dontwarn org.apache.commons.**
# 88: smbj (SMB) - 메시지 · 보안 제공자를 이름으로 찾는다 (축소가 지우면 연결 · 로그인이 실패)
-keep class com.hierynomus.** { *; }
-keep class net.engio.mbassy.** { *; }
-keep class org.bouncycastle.** { *; }
-dontwarn com.hierynomus.**
-dontwarn net.engio.mbassy.**
-dontwarn org.bouncycastle.**
-dontwarn org.slf4j.**

View File

@ -4,4 +4,6 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 통합 테스트가 기기 안에 띄운 http://127.0.0.1 시험 페이지를 열 수 있게 (debug 빌드만) -->
<application android:usesCleartextTraffic="true"/>
</manifest>

View File

@ -1,5 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 28 APK 점검 · 관리자 판단 (10/19): Wi-Fi 공유 · SMB 찾기가 망 종류 (Wi-Fi · 유선 / 모바일 데이터 · VPN) 를 알기 위해. 일반 권한 (묻지 않음) -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<!-- 동영상 옆 jj_mkv 폴더에 MKV 를 만들기 위한 저장소 접근 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
@ -9,14 +11,20 @@
<!-- 앱이 화면에서 내려가도 다운로드 · MKV 만들기 · AI 자막을 계속하고 진행 알림을 보여 주기 위해 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- 환경 설정 > 최신 버전 확인: 받은 새 APK 로 설치 화면 열기 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<!-- 예전 버전으로 되돌릴 때 이 앱을 지우는 확인 창 (Android 는 낮은 버전을 위에 설치하지 못함) -->
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
<application
android:label="JJ_MKVMaker"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true">
android:requestLegacyExternalStorage="true"
android:usesCleartextTraffic="true"
android:fullBackupContent="@xml/backup_rules"
android:dataExtractionRules="@xml/data_extraction_rules">
<!-- Android 16: 큰 화면 (태블릿) 에서도 환경 설정 > 화면 방향 (가로 고정 · 세로 고정) 을 따르게 -->
<property
android:name="android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY"
@ -24,7 +32,7 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:launchMode="singleTask"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
@ -38,10 +46,7 @@
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- 앱 목록 · 홈 화면 아이콘은 아래 activity-alias (환경 설정 > 일반 > 앱 아이콘 에서 고른 것 하나만 켬) -->
<!-- 연결 프로그램: 파일 앱 등에서 동영상 "다음으로 열기" -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
@ -59,6 +64,69 @@
<data android:mimeType="video/*"/>
</intent-filter>
</activity>
<!-- 아이콘이 여는 중계 화면 (MainActivity 를 열고 바로 닫힘: 아이콘을 바꿔도 앱 화면이 닫히지 않게) -->
<activity
android:name=".LauncherActivity"
android:exported="false"
android:excludeFromRecents="true"
android:noHistory="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<!-- 앱 아이콘 (고를 수 있는 것마다 하나, 처음에는 노랑만 켜짐) -->
<activity-alias
android:name=".IconYellow"
android:targetActivity=".LauncherActivity"
android:icon="@mipmap/ic_launcher_yellow"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity-alias>
<activity-alias
android:name=".IconBlack"
android:targetActivity=".LauncherActivity"
android:icon="@mipmap/ic_launcher_black"
android:enabled="false"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity-alias>
<activity-alias
android:name=".IconFilmJj"
android:targetActivity=".LauncherActivity"
android:icon="@mipmap/ic_launcher_film_jj"
android:enabled="false"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity-alias>
<activity-alias
android:name=".IconFilm"
android:targetActivity=".LauncherActivity"
android:icon="@mipmap/ic_launcher_film"
android:enabled="false"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity-alias>
<activity-alias
android:name=".IconBlue"
android:targetActivity=".LauncherActivity"
android:icon="@mipmap/ic_launcher_blue"
android:enabled="false"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity-alias>
<!-- 업데이트 APK 를 설치 화면에 넘기기 위한 파일 공유 (cache/updates 만) -->
<provider
android:name="androidx.core.content.FileProvider"
@ -69,10 +137,16 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/jj_file_paths"/>
</provider>
<!-- 작업 알림 서비스 (28 APK 점검 · 관리자 지시 10/19): specialUse 설명은 실제로 하는 일 모두를 적는다
(실시간 동기화 · 동영상 변환 · AI 자막 · 다운로드 · 복사/이동 · 원격 서버 전송 · Wi-Fi 공유) -->
<service
android:name=".KeepAliveService"
android:exported="false"
android:foregroundServiceType="dataSync"/>
android:foregroundServiceType="dataSync|specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Keeps user-started work running while the app is in the background (user setting: run in background): real-time folder sync, video to MKV conversion and AI subtitles, downloads, file copy/move, transfers to the user's own SFTP/SMB/FTP/WebDAV servers, and the read-only Wi-Fi file sharing server. A notification shows what is running."/>
</service>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data

View File

@ -12,7 +12,7 @@ import android.os.Build
import android.os.IBinder
import android.os.PowerManager
/// 다운로드 · MKV 만들기 · AI 자막이 진행 중일 때 앱이 화면에서 내려가도 Android 가 멈추지 않도록 하는
/// 다운로드 · MKV 만들기 · AI 자막 · 실시간 동기화 (백그라운드로 실행) 가 진행 중일 때 앱이 화면에서 내려가도 Android 가 멈추지 않도록 하는
/// 포그라운드 서비스. 알림에 진행 상황을 보여 주고, 화면이 꺼져도 CPU 가 잠들지 않게 한다.
/// 실제 작업은 앱 (Dart) 이 같은 프로세스에서 계속한다. 작업이 모두 끝나면 앱이 멈춘다.
class KeepAliveService : Service() {
@ -23,9 +23,13 @@ class KeepAliveService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val text = intent?.getStringExtra(EXTRA_TEXT) ?: ""
val progress = intent?.getIntExtra(EXTRA_PROGRESS, -1) ?: -1
val n = notification(this, text, progress)
val icon = intent?.getStringExtra(EXTRA_ICON) ?: ""
val n = notification(this, text, progress, icon)
running = true
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// 실시간 동기화는 앱이 켜져 있는 내내 지켜봐야 하므로 하루 6시간 제한이 있는 dataSync 대신 specialUse (Android 14+)
startForeground(NOTIFICATION_ID, n, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, n, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
} else {
startForeground(NOTIFICATION_ID, n)
@ -33,7 +37,12 @@ class KeepAliveService : Service() {
if (wakeLock == null) {
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager)
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "jj_mkvmaker:jobs")
.apply { acquire(6 * 60 * 60 * 1000L) }
.apply {
setReferenceCounted(false)
acquire(RENEW_MS * 2)
}
// 22: 6시간이 지나면 잠금이 풀려 화면을 끈 채 걸어 둔 동기화 · 긴 작업이 조용히 멈출 수 있었다 → 작업 중에는 늘 다시 건다
renewer.postDelayed(renew, RENEW_MS)
}
return START_NOT_STICKY
}
@ -43,7 +52,25 @@ class KeepAliveService : Service() {
stopSelf()
}
private val renewer = android.os.Handler(android.os.Looper.getMainLooper())
private val renew = object : Runnable {
override fun run() {
wakeLock?.let { if (running) it.acquire(RENEW_MS * 2) }
if (running) renewer.postDelayed(this, RENEW_MS)
}
}
/// 최근 앱에서 밀어 닫음: "백그라운드로 실행" 이 꺼져 있으면 작업이 멈추므로 알림을 남기고 서비스도 끝낸다 (21)
override fun onTaskRemoved(rootIntent: Intent?) {
if (!MainActivity.background) {
notifyStopped(this)
stopSelf()
}
super.onTaskRemoved(rootIntent)
}
override fun onDestroy() {
renewer.removeCallbacks(renew)
running = false
wakeLock?.let { if (it.isHeld) it.release() }
wakeLock = null
@ -53,31 +80,100 @@ class KeepAliveService : Service() {
companion object {
const val CHANNEL_ID = "jobs"
const val NOTIFICATION_ID = 1
const val EXTRA_TEXT = "text"
const val EXTRA_PROGRESS = "progress"
const val STOPPED_ID = 2
const val DONE_ID = 3
/// 서비스가 떠 있는지 (떠 있으면 알림만 바꾼다 - 백그라운드에서는 서비스를 새로 시작할 수 없음)
@Volatile
var running = false
const val DONE_CHANNEL_ID = "done"
fun update(context: Context, text: String, progress: Int) {
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nm.notify(NOTIFICATION_ID, notification(context, text, progress))
/// 57: 알림 글 · 채널 이름을 앱 화면 언어로 (앱이 고른 언어라 Android 의 values-xx 로는 맞출 수 없다).
/// 앱 (Dart) 이 언어를 바꿀 때마다 보내 오고, 다음에 켤 때도 쓰게 저장해 둔다. 없으면 한국어.
private const val PREFS = "jj_notify_texts"
private val defaults = mapOf(
"working" to "JJ_MKVMaker 작업 중",
"stoppedTitle" to "JJ_MKVMaker 작업이 멈췄습니다",
"stoppedText" to "앱을 닫아 진행 중이던 작업이 멈췄습니다. 닫아도 계속하려면 환경 설정에서 '백그라운드로 실행' 을 켜세요.",
"stoppedLong" to "앱을 닫아 진행 중이던 작업 (MKV · 다운로드 · 동기화) 이 멈췄습니다. 앱을 닫아도 계속하려면 환경 설정 > Rsync > '백그라운드로 실행' 을 켜세요.",
"jobsChannel" to "작업 진행",
"jobsChannelDesc" to "다운로드 · MKV 만들기 · AI 자막 · 동기화 진행 상황",
"doneChannel" to "끝난 작업",
"doneChannelDesc" to "AI 해상도 올리기 등 오래 걸린 작업이 끝났을 때",
)
private fun t(context: Context, key: String): String =
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getString(key, null)?.takeIf { it.isNotEmpty() }
?: defaults.getValue(key)
/// 앱이 보낸 글을 저장하고, 이미 만든 채널은 이름 · 설명을 바꾼다 (같은 ID 로 다시 만들면 이름만 바뀐다)
fun setTexts(context: Context, texts: Map<String, String>) {
val e = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit()
for ((k, v) in texts) if (k in defaults) e.putString(k, v)
e.apply()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (nm.getNotificationChannel(CHANNEL_ID) != null) createJobsChannel(context, nm, texts)
if (nm.getNotificationChannel(DONE_CHANNEL_ID) != null) createDoneChannel(context, nm, texts)
}
}
private fun notification(context: Context, text: String, progress: Int): Notification {
private fun pick(context: Context, texts: Map<String, String>?, key: String) =
texts?.get(key)?.takeIf { it.isNotEmpty() } ?: t(context, key)
private fun createJobsChannel(context: Context, nm: NotificationManager, texts: Map<String, String>? = null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
nm.createNotificationChannel(
NotificationChannel(CHANNEL_ID, pick(context, texts, "jobsChannel"), NotificationManager.IMPORTANCE_LOW).apply {
description = pick(context, texts, "jobsChannelDesc")
setShowBadge(false)
}
)
}
private fun createDoneChannel(context: Context, nm: NotificationManager, texts: Map<String, String>? = null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
nm.createNotificationChannel(
NotificationChannel(DONE_CHANNEL_ID, pick(context, texts, "doneChannel"), NotificationManager.IMPORTANCE_DEFAULT).apply {
description = pick(context, texts, "doneChannelDesc")
setSound(null, null)
}
)
}
/// 157: 끝난 작업 알림 (예: "4장 저장: 폴더"). 누르면 작업 현황으로.
/// 진행 알림 채널 (조용히, 낮은 중요도) 과 따로 두어 알림 창의 "조용한 알림" 칸에 묻히지 않게 (보통 중요도, 소리 없음)
fun notifyDone(context: Context, title: String, text: String) {
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && nm.getNotificationChannel(CHANNEL_ID) == null) {
nm.createNotificationChannel(
NotificationChannel(CHANNEL_ID, "작업 진행", NotificationManager.IMPORTANCE_LOW).apply {
description = "다운로드 · MKV 만들기 · AI 자막 진행 상황"
setShowBadge(false)
}
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && nm.getNotificationChannel(DONE_CHANNEL_ID) == null) {
createDoneChannel(context, nm)
}
val open = PendingIntent.getActivity(
context, 0,
Intent(context, MainActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
context, 3,
Intent(context, MainActivity::class.java)
.setAction(MainActivity.ACTION_SHOW_JOBS)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
val b = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(context, DONE_CHANNEL_ID)
} else {
@Suppress("DEPRECATION") Notification.Builder(context)
}
b.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setContentTitle(title)
.setContentText(text)
.setStyle(Notification.BigTextStyle().bigText(text))
.setContentIntent(open)
.setAutoCancel(true)
nm.notify(DONE_ID, b.build())
}
/// 잠금을 다시 거는 간격 (1시간, 잠금은 그 두 배 동안)
const val RENEW_MS = 60 * 60 * 1000L
/// 앱을 닫아 작업이 멈췄다는 알림 (누르면 앱을 다시 연다)
fun notifyStopped(context: Context) {
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val open = PendingIntent.getActivity(
context, 1, Intent(context, MainActivity::class.java),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
val b = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -85,8 +181,48 @@ class KeepAliveService : Service() {
} else {
@Suppress("DEPRECATION") Notification.Builder(context)
}
b.setSmallIcon(android.R.drawable.stat_sys_download)
.setContentTitle("JJ_MKVMaker 작업 중")
b.setSmallIcon(android.R.drawable.stat_notify_error)
.setContentTitle(t(context, "stoppedTitle"))
.setContentText(t(context, "stoppedText"))
.setStyle(Notification.BigTextStyle().bigText(t(context, "stoppedLong")))
.setContentIntent(open)
.setAutoCancel(true)
nm.notify(STOPPED_ID, b.build())
}
const val EXTRA_TEXT = "text"
const val EXTRA_PROGRESS = "progress"
const val EXTRA_ICON = "icon"
/// 서비스가 떠 있는지 (떠 있으면 알림만 바꾼다 - 백그라운드에서는 서비스를 새로 시작할 수 없음)
@Volatile
var running = false
fun update(context: Context, text: String, progress: Int, icon: String) {
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nm.notify(NOTIFICATION_ID, notification(context, text, progress, icon))
}
private fun notification(context: Context, text: String, progress: Int, icon: String): Notification {
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && nm.getNotificationChannel(CHANNEL_ID) == null) {
createJobsChannel(context, nm)
}
val open = PendingIntent.getActivity(
context, 0,
// 누르면 앱의 "작업 현황" 화면으로 (지금 하는 작업 · 진행률 · 대기열 · 다운로드)
Intent(context, MainActivity::class.java)
.setAction(MainActivity.ACTION_SHOW_JOBS)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
val b = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(context, CHANNEL_ID)
} else {
@Suppress("DEPRECATION") Notification.Builder(context)
}
// 다운로드 중이면 내려받기 아이콘, 그 밖 (실시간 동기화 · MKV · AI 자막) 은 좌우로 오가는 화살표
b.setSmallIcon(if (icon == "download") android.R.drawable.stat_sys_download else R.drawable.ic_stat_sync_anim)
.setContentTitle(t(context, "working"))
.setContentText(text)
.setContentIntent(open)
.setOngoing(true)

View File

@ -0,0 +1,19 @@
package com.jj.jj_mkvmaker
import android.app.Activity
import android.content.Intent
import android.os.Bundle
/// 앱 목록 · 홈 화면 아이콘 (activity-alias) 이 여는 중계 화면: MainActivity 를 열고 바로 닫는다.
/// MainActivity 작업이 alias 로 시작되면 환경 설정에서 아이콘을 바꿀 때 (그 alias 를 끄면) 앱 화면이 닫히므로.
class LauncherActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivity(Intent(this, MainActivity::class.java).apply {
action = Intent.ACTION_MAIN
addCategory(Intent.CATEGORY_LAUNCHER)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
})
finish()
}
}

View File

@ -1,59 +1,209 @@
package com.jj.jj_mkvmaker
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.Settings
import androidx.webkit.ProfileStore
import androidx.webkit.WebViewFeature
import com.yausername.aria2c.Aria2c
import com.yausername.ffmpeg.FFmpeg
import com.yausername.youtubedl_android.YoutubeDL
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.plugin.common.MethodChannel
import java.io.File
/// 저장소 전체 접근 (동영상 옆 jj_mkv 폴더에 MKV 를 만들기 위해) 확인 · 요청, 다운로드 프로그램 준비
class MainActivity : FlutterActivity() {
companion object {
/// 작업 알림을 눌렀을 때: 작업 현황 화면을 연다
const val ACTION_SHOW_JOBS = "com.jj.jj_mkvmaker.SHOW_JOBS"
private const val ENGINE_ID = "main"
/// 백그라운드로 실행 (환경 설정, Dart 가 알려 준다): 켜져 있으면 화면을 닫아도 (← · 최근 앱에서 밀기)
/// Dart 엔진을 없애지 않아 동기화 · MKV 만들기 · 다운로드가 계속된다 (작업 알림 서비스가 프로세스를 살려 둠).
@Volatile
var background = false
}
/// 앞 화면이 닫힌 뒤 살아 있던 엔진을 다시 붙였는지 (Dart 는 이미 돌고 있다)
private var reusedEngine = false
/// 엔진은 앱 (프로세스) 이 갖는다: 화면이 닫혀도 [background] 면 살려 두고, 다시 열면 그 엔진을 붙인다
override fun provideFlutterEngine(context: Context): FlutterEngine {
val cache = FlutterEngineCache.getInstance()
cache.get(ENGINE_ID)?.let {
reusedEngine = true
return it
}
return FlutterEngine(context.applicationContext).also { cache.put(ENGINE_ID, it) }
}
/// 백그라운드로 실행이 꺼져 있으면 지금처럼 화면과 함께 엔진도 끝낸다
override fun shouldDestroyEngineWithHost(): Boolean {
if (background) return false
FlutterEngineCache.getInstance().remove(ENGINE_ID)
return true
}
/// 첫 화면에서 ←: 백그라운드로 실행이면 앱을 끝내지 않고 뒤로 보낸다 (홈 버튼과 같음)
override fun popSystemNavigator(): Boolean {
if (!background) return false
moveTaskToBack(true)
return true
}
private var channel: MethodChannel? = null
/// 앱이 켜질 때 작업 알림으로 열렸는지 (Dart 가 준비되면 가져간다)
private var pendingShowJobs = false
/// 다른 앱에서 연 동영상 (앱이 켜질 때 받은 것: Dart 가 준비되면 가져간다)
private var pendingOpen: List<Map<String, String?>> = emptyList()
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
pendingOpen = openedFiles(intent)
pendingShowJobs = intent?.action == ACTION_SHOW_JOBS
val ch = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "jj_mkvmaker/android")
channel = ch
// 88: SMB (smbj) - 탐색기의 SMB 서버 탭
SmbBridge.attach(flutterEngine.dartExecutor.binaryMessenger)
if (reusedEngine) {
// Dart 는 이미 돌고 있어 take… 로 가져가지 않는다: 바로 보낸다
if (pendingShowJobs) ch.invokeMethod("showJobs", null)
if (pendingOpen.isNotEmpty()) ch.invokeMethod("openFiles", pendingOpen)
pendingShowJobs = false
pendingOpen = emptyList()
}
ch.setMethodCallHandler { call, result ->
when (call.method) {
"secretReadAll" -> {
try {
result.success(SecretBox.readAll(applicationContext))
} catch (e: Exception) {
result.error("secret", e.toString(), null)
}
}
"secretWrite" -> {
try {
SecretBox.write(applicationContext, call.argument<String>("key")!!, call.argument<String>("value")!!)
result.success(null)
} catch (e: Exception) {
result.error("secret", e.toString(), null)
}
}
"secretDelete" -> {
SecretBox.delete(applicationContext, call.argument<String>("key")!!)
result.success(null)
}
"moveToBack" -> {
// 작업 중에 ← : 앱을 끝내지 않고 뒤로 보낸다 (작업 계속)
moveTaskToBack(true)
result.success(null)
}
"setBackground" -> {
background = call.argument<Boolean>("on") ?: false
result.success(null)
}
"exitApp" -> {
// [종료]: 작업 알림 · 살려 둔 엔진까지 모두 끝낸다
result.success(null)
background = false
stopService(Intent(applicationContext, KeepAliveService::class.java))
FlutterEngineCache.getInstance().remove(ENGINE_ID)
finishAndRemoveTask()
android.os.Handler(mainLooper).postDelayed({
android.os.Process.killProcess(android.os.Process.myPid())
}, 300)
}
"takeOpenedFiles" -> {
result.success(pendingOpen)
pendingOpen = emptyList()
}
"takeShowJobs" -> {
result.success(pendingShowJobs)
pendingShowJobs = false
}
"hasAllFilesAccess" -> result.success(hasAllFilesAccess())
"requestAllFilesAccess" -> {
requestAllFilesAccess()
result.success(null)
}
"storageRoot" -> result.success(Environment.getExternalStorageDirectory().path)
// 앱에 들어 있는 rsync (librsync.so) 가 풀리는 곳
"nativeLibDir" -> result.success(applicationInfo.nativeLibraryDir)
// Tor 화면: 이 기기 웹 뷰가 프록시 걸기 · 따로 프로필 · 페이지보다 먼저 스크립트를 지원하는지 (없으면 화면을 열지 않음)
"torWebSupport" -> result.success(mapOf(
"proxyOverride" to WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE),
"multiProfile" to WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROFILE),
"documentStartScript" to WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT),
))
// Tor 화면 프로필 (쿠키 · 저장소 · 캐시) 지우기. 웹 뷰가 아직 쓰고 있으면 false (나중에 다시)
"torProfileClear" -> result.success(clearWebProfile(call.argument<String>("name") ?: "jj_tor"))
"storageVolumes" -> result.success(storageVolumes())
"openUrl" -> result.success(openUrl(call.argument<String>("url") ?: ""))
"installApk" -> try {
installApk(call.argument<String>("path") ?: "")
result.success(null)
} catch (e: InstallPermissionNeededException) {
// 설치 허용 설정 화면을 열었다: 앱이 받은 파일로 다시 설치하게 따로 알린다
result.error("PERMISSION", e.message ?: e.toString(), null)
} catch (e: Exception) {
result.error("INSTALL", e.message ?: e.toString(), null)
// 170: 다른 오류를 "설치 허용 필요" 로 오해하지 않게 (예전에는 IllegalStateException 이면 모두 허용 필요로 보았다)
result.error("INSTALL", "${e.javaClass.simpleName}: ${e.message ?: ""}", null)
}
// 170: 지금 이 앱이 다른 앱 (APK) 을 설치할 수 있는지
"canInstallPackages" -> result.success(
Build.VERSION.SDK_INT < Build.VERSION_CODES.O || packageManager.canRequestPackageInstalls()
)
"openFolder" -> result.success(openFolder(call.argument<String>("path") ?: ""))
// 예전 버전으로 되돌리기: Android 는 낮은 버전을 위에 설치하지 못해 앱을 지우는 확인 창을 연다
"uninstallSelf" -> {
startActivity(Intent(Intent.ACTION_DELETE, Uri.parse("package:$packageName")).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
result.success(null)
}
// 28 APK 점검 · 관리자 판단 (10/19): 주소마다 망 종류 - Wi-Fi 공유 · SMB 찾기가 Wi-Fi · 유선만 쓰게 (모바일 데이터 · VPN 은 뺀다)
"netKinds" -> result.success(netKinds())
"diskSpace" -> result.success(
try {
val st = android.os.StatFs(call.argument<String>("path") ?: "")
listOf(st.availableBytes, st.totalBytes)
} catch (e: Exception) {
null
}
)
"openWith" -> result.success(
openWith(call.argument<String>("path") ?: "", call.argument<Boolean>("choose") ?: false)
)
"shareFiles" -> result.success(shareFiles(call.argument<List<String>>("paths") ?: emptyList()))
"setAppIcon" -> result.success(setAppIcon(call.argument<String>("id") ?: ""))
"keepAlive" -> {
keepAlive(call.argument<String>("text") ?: "", call.argument<Int>("progress") ?: -1)
keepAlive(call.argument<String>("text") ?: "", call.argument<Int>("progress") ?: -1,
call.argument<String>("icon") ?: "")
result.success(null)
}
"notificationTexts" -> {
// 57: 알림 글 · 채널 이름 (앱 화면 언어)
val m = call.arguments as? Map<*, *> ?: emptyMap<Any, Any>()
KeepAliveService.setTexts(applicationContext,
m.entries.mapNotNull { (k, v) -> if (k is String && v is String) k to v else null }.toMap())
result.success(null)
}
"notifyDone" -> {
KeepAliveService.notifyDone(applicationContext, call.argument<String>("title") ?: "",
call.argument<String>("text") ?: "")
result.success(null)
}
"stopKeepAlive" -> {
stopService(Intent(this, KeepAliveService::class.java))
stopService(Intent(applicationContext, KeepAliveService::class.java))
result.success(null)
}
"downloadToolsInit" -> inBackground(result) { downloadToolsInit() }
@ -66,7 +216,7 @@ class MainActivity : FlutterActivity() {
}
/// 받은 업데이트 APK 로 Android 설치 화면을 연다. 앱 전용 폴더의 파일이라 FileProvider 로 넘긴다.
/// 처음이면 "이 출처의 앱 설치 허용" 설정 화면을 먼저 연다 (허용한 뒤 다시 [업데이트])
/// 처음이면 "이 출처의 앱 설치 허용" 설정 화면을 먼저 연다 (허용하고 돌아와 [설치 계속] - 받은 파일을 그대로 쓴다)
private fun installApk(path: String) {
val src = File(path)
if (!src.isFile) throw IllegalArgumentException("APK 파일이 없습니다: $path")
@ -75,7 +225,7 @@ class MainActivity : FlutterActivity() {
Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:$packageName"))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
throw IllegalStateException("\"이 출처의 앱 설치 허용\" 을 켠 뒤 다시 [업데이트] 를 누르세요")
throw InstallPermissionNeededException("\"이 출처의 앱 설치 허용\" 을 켠 뒤 다시 [업데이트] 를 누르세요")
}
// FileProvider 가 내보내는 폴더 (cache/updates) 로 옮긴다
val dir = File(cacheDir, "updates").apply { mkdirs() }
@ -95,9 +245,16 @@ class MainActivity : FlutterActivity() {
)
}
/// 170: 설치 허용이 꺼져 있어 설정 화면을 연 경우만 (다른 오류와 구별)
class InstallPermissionNeededException(message: String) : Exception(message)
/// 앱이 켜져 있을 때 다른 앱에서 동영상을 열면 (launchMode singleTop)
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.action == ACTION_SHOW_JOBS) {
channel?.invokeMethod("showJobs", null)
return
}
val files = openedFiles(intent)
if (files.isNotEmpty()) channel?.invokeMethod("openFiles", files)
}
@ -206,6 +363,7 @@ class MainActivity : FlutterActivity() {
"path" to dir.absolutePath,
"label" to (v.getDescription(this) ?: dir.name),
"removable" to v.isRemovable,
"primary" to v.isPrimary,
)
)
}
@ -221,6 +379,96 @@ class MainActivity : FlutterActivity() {
}
/// 폴더를 파일 앱으로 연다 (내장 저장소 안의 폴더만). 열 앱이 없으면 false.
/// 앱 아이콘 바꾸기: 고른 아이콘의 activity-alias 만 켜고 나머지는 끈다 (앱은 계속 켜 둔 채로)
private fun setAppIcon(id: String): Boolean {
val ids = listOf("yellow", "black", "film_jj", "film", "blue")
if (id !in ids) return false
val pm = packageManager
for (i in ids) {
val name = "$packageName.Icon" + i.split('_').joinToString("") { it.replaceFirstChar(Char::uppercaseChar) }
val state = if (i == id) PackageManager.COMPONENT_ENABLED_STATE_ENABLED
else PackageManager.COMPONENT_ENABLED_STATE_DISABLED
val cn = android.content.ComponentName(this, name)
if (pm.getComponentEnabledSetting(cn) != state) {
pm.setComponentEnabledSetting(cn, state, PackageManager.DONT_KILL_APP)
}
}
return true
}
/// 파일 탐색기: 파일을 다른 앱으로 연다. [choose] 면 늘 앱 고르기 창 (이 앱은 뺌), 아니면 기본 앱 (없으면 고르기 창).
/// SD 카드 · USB 파일도 넘길 수 있게 FileProvider 의 root 경로를 쓴다 (res/xml/jj_file_paths.xml).
private fun openWith(path: String, choose: Boolean): Boolean {
val f = File(path)
if (!f.isFile) return false
val uri = try {
androidx.core.content.FileProvider.getUriForFile(this, "$packageName.files", f)
} catch (e: Exception) {
return false
}
val mime = android.webkit.MimeTypeMap.getSingleton().getMimeTypeFromExtension(f.extension.lowercase()) ?: "*/*"
val view = Intent(Intent.ACTION_VIEW)
.setDataAndType(uri, mime)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK)
fun chooser(): Intent = Intent.createChooser(view, null)
.putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, arrayOf(android.content.ComponentName(this, MainActivity::class.java)))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
return try {
startActivity(if (choose) chooser() else view)
true
} catch (e: Exception) {
try {
startActivity(chooser())
true
} catch (e2: Exception) {
false
}
}
}
/// 77: 파일 탐색기의 [공유] - 파일 하나는 ACTION_SEND, 여럿은 ACTION_SEND_MULTIPLE (공유 창, 이 앱은 뺌)
private fun shareFiles(paths: List<String>): Boolean {
val uris = ArrayList<android.net.Uri>()
val mimes = mutableSetOf<String>()
for (p in paths) {
val f = File(p)
if (!f.isFile) continue
uris.add(
try {
androidx.core.content.FileProvider.getUriForFile(this, "$packageName.files", f)
} catch (e: Exception) {
return false
}
)
mimes.add(android.webkit.MimeTypeMap.getSingleton().getMimeTypeFromExtension(f.extension.lowercase()) ?: "*/*")
}
if (uris.isEmpty()) return false
// 종류가 하나면 그것, 앞부분 (video/ 등) 만 같으면 video/*, 아니면 */*
val tops = mimes.map { it.substringBefore('/') }.toSet()
val mime = if (mimes.size == 1) mimes.first() else if (tops.size == 1 && tops.first() != "*") "${tops.first()}/*" else "*/*"
val send = if (uris.size == 1) {
Intent(Intent.ACTION_SEND).putExtra(Intent.EXTRA_STREAM, uris[0])
} else {
Intent(Intent.ACTION_SEND_MULTIPLE).putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
}
send.type = mime
// 받는 앱이 모든 파일을 읽을 수 있게 (EXTRA_STREAM 만으로는 첫 것만 허락되는 앱이 있다)
val clip = android.content.ClipData.newRawUri(null, uris[0])
for (i in 1 until uris.size) clip.addItem(android.content.ClipData.Item(uris[i]))
send.clipData = clip
send.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
return try {
startActivity(
Intent.createChooser(send, null)
.putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, arrayOf(android.content.ComponentName(this, MainActivity::class.java)))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
true
} catch (e: Exception) {
false
}
}
private fun openFolder(path: String): Boolean {
// 내장 저장소 (primary) 또는 SD 카드 · USB (/storage/XXXX-XXXX → 문서 ID "XXXX-XXXX:...")
val root = Environment.getExternalStorageDirectory().path
@ -265,22 +513,24 @@ class MainActivity : FlutterActivity() {
/// 작업 진행 알림 (포그라운드 서비스) 시작 · 갱신. 처음 한 번 알림 권한을 묻는다 (Android 13+, 거절해도 작업은 계속)
private var askedNotifications = false
private fun keepAlive(text: String, progress: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !askedNotifications &&
private fun keepAlive(text: String, progress: Int, icon: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !askedNotifications && !isDestroyed &&
checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED
) {
askedNotifications = true
requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), 2)
}
if (KeepAliveService.running) {
KeepAliveService.update(this, text, progress)
KeepAliveService.update(applicationContext, text, progress, icon)
return
}
val i = Intent(this, KeepAliveService::class.java)
val i = Intent(applicationContext, KeepAliveService::class.java)
.putExtra(KeepAliveService.EXTRA_TEXT, text)
.putExtra(KeepAliveService.EXTRA_PROGRESS, progress)
.putExtra(KeepAliveService.EXTRA_ICON, icon)
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) startForegroundService(i) else startService(i)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) applicationContext.startForegroundService(i)
else applicationContext.startService(i)
} catch (e: Exception) {
// 백그라운드에서는 새로 시작할 수 없다 (Android 12+). 이미 떠 있으면 다음 갱신 때 바뀐다
android.util.Log.w("jj_mkvmaker", "keepAlive", e)
@ -333,6 +583,24 @@ class MainActivity : FlutterActivity() {
)
}
/// Tor 화면 전용 웹 뷰 프로필을 지운다 (없으면 true). 쓰는 웹 뷰가 남아 있으면 쿠키 · 저장소만 지우고 false
private fun clearWebProfile(name: String): Boolean {
if (!WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROFILE)) return true
val store = ProfileStore.getInstance()
if (store.getProfile(name) == null) return true
return try {
store.deleteProfile(name)
} catch (e: IllegalStateException) {
try {
val profile = store.getProfile(name)
profile?.cookieManager?.removeAllCookies(null)
profile?.webStorage?.deleteAllData()
} catch (_: Exception) {
}
false
}
}
private fun hasAllFilesAccess(): Boolean =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Environment.isExternalStorageManager()
@ -353,4 +621,29 @@ class MainActivity : FlutterActivity() {
)
}
}
/// IPv4 주소 → 망 종류 ("wifi" · "ethernet" · "cellular" · "vpn" · "other"). 읽지 못하면 null (Dart 가 이름 방식으로)
@Suppress("DEPRECATION")
private fun netKinds(): Map<String, String>? = try {
val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as android.net.ConnectivityManager
val out = HashMap<String, String>()
for (n in cm.allNetworks) {
val caps = cm.getNetworkCapabilities(n) ?: continue
val lp = cm.getLinkProperties(n) ?: continue
val kind = when {
caps.hasTransport(android.net.NetworkCapabilities.TRANSPORT_VPN) -> "vpn"
caps.hasTransport(android.net.NetworkCapabilities.TRANSPORT_WIFI) -> "wifi"
caps.hasTransport(android.net.NetworkCapabilities.TRANSPORT_ETHERNET) -> "ethernet"
caps.hasTransport(android.net.NetworkCapabilities.TRANSPORT_CELLULAR) -> "cellular"
else -> "other"
}
for (la in lp.linkAddresses) {
val a = la.address
if (a is java.net.Inet4Address) a.hostAddress?.let { out[it] = kind }
}
}
out
} catch (e: Exception) {
null
}
}

View File

@ -0,0 +1,66 @@
package com.jj.jj_mkvmaker
import android.content.Context
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec
/// 비밀 값 (WebDAV · OpenSubtitles 비밀번호 등) 을 AndroidKeyStore 의 AES-GCM 키로 암호화해 앱 전용 SharedPreferences ("jj_secrets") 에 둔다.
/// 키는 이 기기 밖으로 나가지 않는다 (백업에서도 제외 - res/xml/backup_rules.xml). 풀지 못한 값은 지우지 않고 건너뛴다.
object SecretBox {
private const val ALIAS = "jj_secrets"
private const val PREFS = "jj_secrets"
private fun key(): SecretKey {
val ks = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
(ks.getKey(ALIAS, null) as? SecretKey)?.let { return it }
val gen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
gen.init(
KeyGenParameterSpec.Builder(ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.build()
)
return gen.generateKey()
}
private fun prefs(c: Context) = c.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
fun readAll(c: Context): Map<String, String> {
val all = prefs(c).all
if (all.isEmpty()) return emptyMap()
val k = key()
val out = HashMap<String, String>()
for ((name, v) in all) {
val raw = try { Base64.decode(v as String, Base64.NO_WRAP) } catch (_: Exception) { continue }
if (raw.size < 13) continue
try {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.DECRYPT_MODE, k, GCMParameterSpec(128, raw, 0, 12))
out[name] = String(cipher.doFinal(raw, 12, raw.size - 12), Charsets.UTF_8)
} catch (_: Exception) {
}
}
return out
}
fun write(c: Context, name: String, value: String) {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, key())
val body = cipher.doFinal(value.toByteArray(Charsets.UTF_8))
val raw = cipher.iv + body
if (!prefs(c).edit().putString(name, Base64.encodeToString(raw, Base64.NO_WRAP)).commit()) {
throw IllegalStateException("secret write failed")
}
}
fun delete(c: Context, name: String) {
prefs(c).edit().remove(name).commit()
}
}

View File

@ -0,0 +1,253 @@
package com.jj.jj_mkvmaker
import android.os.Handler
import android.os.Looper
import com.hierynomus.msdtyp.AccessMask
import com.hierynomus.mserref.NtStatus
import com.hierynomus.msfscc.FileAttributes
import com.hierynomus.mssmb2.SMB2CreateDisposition
import com.hierynomus.mssmb2.SMB2ShareAccess
import com.hierynomus.mssmb2.SMBApiException
import com.hierynomus.security.bc.BCSecurityProvider
import com.hierynomus.smbj.SMBClient
import com.hierynomus.smbj.SmbConfig
import com.hierynomus.smbj.auth.AuthenticationContext
import com.hierynomus.smbj.connection.Connection
import com.hierynomus.smbj.session.Session
import com.hierynomus.smbj.share.DiskShare
import com.hierynomus.smbj.share.File as SmbFile
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.net.ConnectException
import java.net.NoRouteToHostException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import java.util.EnumSet
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
/// 88: Android SMB (smbj, Apache-2.0). Windows 는 운영체제가 연다 (사용자 결정 88-1) - 이것은 Android 에만.
/// Dart (lib/core/smb.dart) 가 부른다: 서버 설정은 부를 때마다 함께 받고 (비밀번호는 저장하지 않음), 연결은 서버마다 하나를 다시 쓴다.
/// SMB1 은 쓰지 않는다 (smbj 는 SMB2 부터). 암호화: 서버가 되면 SMB3 암호화 (사용자 결정 88-4 - 안 되면 Dart 가 경고).
object SmbBridge {
private val pool = Executors.newCachedThreadPool()
private val main = Handler(Looper.getMainLooper())
private class Conn(val key: String, val client: SMBClient, val connection: Connection, val session: Session, val share: DiskShare)
/// 서버 id → 연결 (설정이 바뀌면 key 가 달라 새로 연다)
private val conns = ConcurrentHashMap<String, Conn>()
/// 열어 둔 파일 (조각으로 읽기 · 쓰기)
private val files = ConcurrentHashMap<Int, SmbFile>()
private val nextHandle = AtomicInteger(1)
fun attach(messenger: BinaryMessenger) {
MethodChannel(messenger, "jj_mkvmaker/smb").setMethodCallHandler { call, result ->
pool.execute {
try {
val value = handle(call)
main.post { result.success(value) }
} catch (e: Throwable) {
val (code, message) = explain(e)
main.post { result.error(code, message, null) }
}
}
}
}
private fun handle(call: MethodCall): Any? {
when (call.method) {
"close" -> {
conns.remove(call.argument<String>("id")!!)?.let { closeQuietly(it) }
return null
}
"readChunk" -> {
val f = files[call.argument<Int>("handle")!!] ?: throw IllegalStateException("handle")
val len = call.argument<Int>("length")!!
val buf = ByteArray(len)
val n = f.read(buf, (call.argument<Number>("offset")!!).toLong())
return if (n <= 0) ByteArray(0) else buf.copyOf(n)
}
"writeChunk" -> {
val f = files[call.argument<Int>("handle")!!] ?: throw IllegalStateException("handle")
val data = call.argument<ByteArray>("data")!!
f.write(data, (call.argument<Number>("offset")!!).toLong())
return null
}
"closeHandle" -> {
files.remove(call.argument<Int>("handle")!!)?.let { runCatching { it.close() } }
return null
}
}
val c = conn(call)
val path = smbPath(call.argument<String>("path") ?: "")
return when (call.method) {
// 보안 검토 6: 이 연결 (세션) 이 실제로 암호화 · 서명되는지
"info" -> mapOf(
"encrypted" to runCatching { c.session.shouldEncryptData() }.getOrDefault(false),
"signed" to c.session.isSigningRequired,
"dialect" to c.connection.connectionContext.negotiatedProtocol.dialect.name,
)
"list" -> c.share.list(path).filter { it.fileName != "." && it.fileName != ".." }.map {
mapOf(
"name" to it.fileName,
"dir" to ((it.fileAttributes and FileAttributes.FILE_ATTRIBUTE_DIRECTORY.value) != 0L),
"size" to it.endOfFile,
"mtime" to it.lastWriteTime.toEpochMillis(),
)
}
"stat" -> {
if (path.isEmpty()) return mapOf("dir" to true, "size" to 0L, "mtime" to 0L)
if (!c.share.fileExists(path) && !c.share.folderExists(path)) return null
val info = c.share.getFileInformation(path)
mapOf(
"dir" to info.standardInformation.isDirectory,
"size" to info.standardInformation.endOfFile,
"mtime" to info.basicInformation.lastWriteTime.toEpochMillis(),
)
}
"mkdir" -> {
c.share.mkdir(path)
null
}
"delete" -> {
if (c.share.folderExists(path)) c.share.rmdir(path, true) else if (c.share.fileExists(path)) c.share.rm(path)
null
}
"move" -> {
val to = smbPath(call.argument<String>("to")!!)
val overwrite = call.argument<Boolean>("overwrite") ?: false
val entry = c.share.open(
path,
EnumSet.of(AccessMask.DELETE, AccessMask.FILE_READ_ATTRIBUTES),
null,
SMB2ShareAccess.ALL,
SMB2CreateDisposition.FILE_OPEN,
null,
)
entry.use { it.rename(to, overwrite) }
null
}
"openRead" -> {
val f = c.share.openFile(
path,
EnumSet.of(AccessMask.GENERIC_READ),
null,
SMB2ShareAccess.ALL,
SMB2CreateDisposition.FILE_OPEN,
null,
)
val h = nextHandle.getAndIncrement()
files[h] = f
mapOf("handle" to h, "size" to f.fileInformation.standardInformation.endOfFile)
}
"openWrite" -> {
val f = c.share.openFile(
path,
EnumSet.of(AccessMask.GENERIC_WRITE),
null,
SMB2ShareAccess.ALL,
SMB2CreateDisposition.FILE_OVERWRITE_IF,
null,
)
val h = nextHandle.getAndIncrement()
files[h] = f
h
}
"quota" -> {
val s = c.share.shareInformation
listOf(s.freeSpace, s.totalSpace - s.freeSpace)
}
else -> throw UnsupportedOperationException(call.method)
}
}
/// 서버 설정 → 연결 (같은 설정이면 다시 씀, 끊겼으면 새로)
private fun conn(call: MethodCall): Conn {
val id = call.argument<String>("id")!!
val host = call.argument<String>("host")!!
val port = call.argument<Int>("port") ?: 445
val shareName = call.argument<String>("share")!!
val user = call.argument<String>("user") ?: ""
val domain = call.argument<String>("domain") ?: ""
val password = call.argument<String>("password") ?: ""
val requireSecure = call.argument<Boolean>("requireSecure") ?: false
val key = listOf(host, port, shareName, user, domain, password.hashCode(), requireSecure).joinToString("|")
conns[id]?.let {
if (it.key == key && it.connection.isConnected) return it
conns.remove(id)
closeQuietly(it)
}
val config = SmbConfig.builder()
// Android 의 기본 보안 제공자에는 NTLM 에 필요한 MD4 가 없다 - Bouncy Castle 로
.withSecurityProvider(BCSecurityProvider())
.withEncryptData(true)
// 보안 검토 6: 설정 "SMB 서명 · 암호화 필수" 면 서명을 요구한다 (서버가 서명을 켜 두었으면 서명)
.withSigningRequired(requireSecure)
.withTimeout(30, TimeUnit.SECONDS)
.withSoTimeout(60, TimeUnit.SECONDS)
.build()
val client = SMBClient(config)
try {
val connection = client.connect(host, port)
val auth = if (user.isEmpty()) AuthenticationContext.guest()
else AuthenticationContext(user, password.toCharArray(), domain.ifEmpty { null })
val session = connection.authenticate(auth)
if (requireSecure && !session.isSigningRequired && !runCatching { session.shouldEncryptData() }.getOrDefault(false)) {
runCatching { session.close() }
throw SmbBridgeException("insecure", "signing and encryption are off")
}
val share = session.connectShare(shareName) as? DiskShare
?: throw SmbBridgeException("noShare", "공유 폴더가 아닙니다: $shareName")
val c = Conn(key, client, connection, session, share)
conns[id] = c
return c
} catch (e: Throwable) {
runCatching { client.close() }
throw e
}
}
private fun closeQuietly(c: Conn) {
runCatching { c.share.close() }
runCatching { c.session.close() }
runCatching { c.connection.close() }
runCatching { c.client.close() }
}
/// "/a/b" → "a\\b" (공유 안의 경로, 맨 앞 구분자 없음)
private fun smbPath(rel: String): String = rel.trim('/').replace('/', '\\')
class SmbBridgeException(val code: String, message: String) : Exception(message)
/// 오류 → (종류, 원문). 종류는 Dart 가 사람 말로 바꾼다
private fun explain(e: Throwable): Pair<String, String> {
val root = generateSequence(e) { it.cause }.last()
if (e is SmbBridgeException) return e.code to (e.message ?: "")
if (e is SMBApiException) {
val code = when (e.status) {
NtStatus.STATUS_LOGON_FAILURE, NtStatus.STATUS_ACCOUNT_DISABLED, NtStatus.STATUS_PASSWORD_EXPIRED,
NtStatus.STATUS_LOGON_TYPE_NOT_GRANTED -> "auth"
NtStatus.STATUS_ACCESS_DENIED -> "permission"
NtStatus.STATUS_OBJECT_NAME_NOT_FOUND, NtStatus.STATUS_OBJECT_PATH_NOT_FOUND, NtStatus.STATUS_NO_SUCH_FILE -> "notFound"
NtStatus.STATUS_BAD_NETWORK_NAME -> "noShare"
NtStatus.STATUS_OBJECT_NAME_COLLISION -> "exists"
else -> "other"
}
return code to "${e.status}: ${e.message}"
}
if (root is UnknownHostException || root is ConnectException || root is NoRouteToHostException || root is SocketTimeoutException) {
return "unreachable" to "${root.javaClass.simpleName}: ${root.message}"
}
val text = "${e.javaClass.simpleName}: ${e.message} / ${root.javaClass.simpleName}: ${root.message}"
// SMB1 만 되는 서버 (smbj 는 SMB2 부터 - 협상 실패)
if (text.contains("SMB1", ignoreCase = true) || text.contains("dialect", ignoreCase = true) || text.contains("negotiat", ignoreCase = true)) {
return "smb1" to text
}
return "other" to text
}
}

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FFFFFFFF" android:pathData="M2,7 L14,7 L14,4 L19,8 L14,12 L14,9 L2,9 Z"/>
<path android:fillColor="#FFFFFFFF" android:pathData="M22,15 L10,15 L10,12 L5,16 L10,20 L10,17 L22,17 Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FFFFFFFF" android:pathData="M3.5,7 L15.5,7 L15.5,4 L20.5,8 L15.5,12 L15.5,9 L3.5,9 Z"/>
<path android:fillColor="#FFFFFFFF" android:pathData="M20.5,15 L8.5,15 L8.5,12 L3.5,16 L8.5,20 L8.5,17 L20.5,17 Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FFFFFFFF" android:pathData="M5,7 L17,7 L17,4 L22,8 L17,12 L17,9 L5,9 Z"/>
<path android:fillColor="#FFFFFFFF" android:pathData="M19,15 L7,15 L7,12 L2,16 L7,20 L7,17 L19,17 Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FFFFFFFF" android:pathData="M6.5,7 L18.5,7 L18.5,4 L23.5,8 L18.5,12 L18.5,9 L6.5,9 Z"/>
<path android:fillColor="#FFFFFFFF" android:pathData="M17.5,15 L5.5,15 L5.5,12 L0.5,16 L5.5,20 L5.5,17 L17.5,17 Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FFFFFFFF" android:pathData="M5,7 L17,7 L17,4 L22,8 L17,12 L17,9 L5,9 Z"/>
<path android:fillColor="#FFFFFFFF" android:pathData="M19,15 L7,15 L7,12 L2,16 L7,20 L7,17 L19,17 Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FFFFFFFF" android:pathData="M3.5,7 L15.5,7 L15.5,4 L20.5,8 L15.5,12 L15.5,9 L3.5,9 Z"/>
<path android:fillColor="#FFFFFFFF" android:pathData="M20.5,15 L8.5,15 L8.5,12 L3.5,16 L8.5,20 L8.5,17 L20.5,17 Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 실시간 동기화 · 작업 중 알림 아이콘: 위 화살표는 오른쪽, 아래 화살표는 왼쪽으로 오간다 -->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
<item android:drawable="@drawable/ic_stat_sync_0" android:duration="150"/>
<item android:drawable="@drawable/ic_stat_sync_1" android:duration="150"/>
<item android:drawable="@drawable/ic_stat_sync_2" android:duration="150"/>
<item android:drawable="@drawable/ic_stat_sync_3" android:duration="150"/>
<item android:drawable="@drawable/ic_stat_sync_4" android:duration="150"/>
<item android:drawable="@drawable/ic_stat_sync_5" android:duration="150"/>
</animation-list>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_bg"/>
<foreground android:drawable="@mipmap/ic_launcher_fg"/>
<monochrome android:drawable="@mipmap/ic_launcher_fg"/>
</adaptive-icon>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_black_bg"/>
<foreground android:drawable="@mipmap/ic_launcher_black_fg"/>
<monochrome android:drawable="@mipmap/ic_launcher_black_fg"/>
</adaptive-icon>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_blue_bg"/>
<foreground android:drawable="@mipmap/ic_launcher_blue_fg"/>
<monochrome android:drawable="@mipmap/ic_launcher_blue_fg"/>
</adaptive-icon>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_film_bg"/>
<foreground android:drawable="@mipmap/ic_launcher_film_fg"/>
<monochrome android:drawable="@mipmap/ic_launcher_film_fg"/>
</adaptive-icon>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_film_jj_bg"/>
<foreground android:drawable="@mipmap/ic_launcher_film_jj_fg"/>
<monochrome android:drawable="@mipmap/ic_launcher_film_jj_fg"/>
</adaptive-icon>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_yellow_bg"/>
<foreground android:drawable="@mipmap/ic_launcher_yellow_fg"/>
<monochrome android:drawable="@mipmap/ic_launcher_yellow_fg"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Some files were not shown because too many files have changed in this diff Show More