DONE‎ > ‎

brag encoding hack

brag을 쓰면서 파일명 처리에 문제가 생기는 경우가 있어서 확인차 코드를 보았는데 Tcl을 전혀 모르는 채로 문법을 찾아보면서 따라가는 게 쉽지 않다.


convmv를 따로 호출하기

기본적으로 생성되는 파일명은 서버측 환경대로 EUC-KR이라서 전송이 끝나고 나면 convmv로 후처리를 했다. (이때 brag이 구동 중이라면, 처음엔 파일이 너무 오랫동안 안 쌓인다고 생각했는데, 서버에서 받은 조각파일을 결과파일에 합치다가 brag이 결과파일이 사라진 것으로 판단해 파일이 없다고 나온다. 따라서 동시 실행을 방지하기 위해 convmv를 실행하기 전에 brag 프로세스가 떠 있는지 검사하도록 해야 했다.)


하지만 convmv 자체도 cron으로 돌리고 있기 때문에 아무 필요없을 때도 매번 시행되는 게 좀 그랬는데, incron이라고 inotify를 통해 이벤트가 잡히면 명령을 실행하는 걸 발견했다. 하지만 이런저런 조건을 생각하면 어떻게 써야 딱 맞을지 좀 그렇다.


그러다 2010년 1월 경 어느 때부터 mv 역할을 하는 rename 단계에서 에러가 나면서 제대로 처리가 안 되었다. 나중에 짐작하기로는 아마 cron 내부의 로케일 기본값이 바뀌면서 Tcl이 인식하는 로케일이 바뀌어 파일명에 접근을 못하게 된 게 아닐까 싶다. 그래서 convmv를 쓰지 않고 바로 처리할 수 있지 않을까 해서 살펴보게 되었다.


내부 동작 분석

구조를 보니, unfinished 디렉토리 안에 ID로 디렉토리를 만들어 파트는 part.111 식의 일괄적인 형식으로 받고 그 제목도 subject 파일로 따로 저장을 하고, 파트가 완료되면 그 목록을 uudeview에 넘겨서 디코딩을 마친 뒤 결과 파일을 finished 디렉토리로 옮기는 식이다. 결국 디코딩된 파일이 중요한 것으로, 기본적으로 서버측 설정대로 EUC-KR로 되어 있는 걸 UTF-8로 적절하게 바꿔주면 되는 것이다.

이때 brag에서 디코딩 결과에 대해 정확한 파일명을 보유하는 것이 아니라 후처리 과정에서 스스로 생성한 파일들은 삭제하고 그 나머지가 결과물일 것으로 간주하는 식이다. uudeview의 출력을 활용하는 것도 생각해 보았지만 깔끔하게 결과 파일명만 나오는 게 아니라서 그 결과를 파싱하는 건 지나치게 복잡한 방법인 것으로 보인다.

rename 단계에서 처리하기

Tcl에서는 가령

set subjectConv [encoding convertfrom cp949 $subject]
라고 하면 $subject가 cp949라고 가정하고 유니코드로 바꾼다. 이는 파이썬에서 내부적으로 유니코드를 쓰는 것과 같은 식인가보다. 이걸 출력하면 UTF-8 터미널에서 잘 나오는 걸로 봐서 출력하는 과정에서 내부적으로 시스템 인코딩에 맞게 변환을 해주는 것으로 보인다.

fconfigure stdout -encoding utf-8
식으로 특정 채널에 대해서 인코딩을 정할 수 있는데, brag에서는 소켓을 binary로 정하여, 인코딩 관련한 동작이 이루어지지 않도록 하고 있다. (그래서 서버측의 EUC-KR 스트링이 그대로 온다)

이때 후처리 과정에서 glob으로 추출한 디코딩된 파일은 uudeview에서 원본 인코딩인 EUC-KR로 파일명을 만들었으므로 rename 과정에서 대상 파일을 UTF-8 인코딩으로 만들어 전달하면 바로 처리될 것이라 생각했다. 하지만 실제로 해본 결과 Tcl이 인코딩 중립적이지 않은 것인지, rename에서 원본 파일의 EUC-KR 파일명과 대상 파일의 UTF-8 파일명을 동시에 인식하지 못해 파일명을 인식하지 못하고 실패한다. 아예 두 번으로 나누어 원본-tmp-대상 식으로 중간 단계를 두고 각 경우의 직전에 (LANG 환경변수를 설정하는 것과 같은 효과를 내는) encoding system cp949encoding system utf-8를 호출해 파일명에 맞게 시스템 인코딩으로 인식하는 값을 설정해보았지만 의외로 이것도 실패.

convmv를 내부에서 호출하기

EUC-KR 파일명만 전달하면 외부에서 처리가 되도록 convmv 호출해보았다. uudeview를 호출하는 부분을 참조하여 eval exec convmv -f cp949 -t utf-8 ... 식으로 호출하고 바깥을 catch {...} results 처럼 감싸 출력이 있을 경우 잡아내도록 했다.

이 경우 문제는 exec에 파일명을 어떻게 잘 전달하는가가 되는데 파일명에 온갖 문자가 들어가 있을 수 있어서 Tcl 문법과의 충돌을 피하기 위해 escape 처리가 필요하다. Tcl에서 escape를 어떻게 처리하는가 찾아봤지만 딱 이거다 싶은 게 없었다. { }로 대상을 감싸라는 얘기가 있긴 했지만 (awk를 언급하는 부분이어서 그쪽에 특화된 건가 싶기도 하고) 기대했던 결과가 나오지 않았던 것 같다. (정확한 기억이 아니다.)

그러나 이 방식 또한 Tcl이 인식하는 시스템 인코딩과 밀접하게 연관되어 있어, 파일명을 인자로 전달하는 방식을 택하는 이상 문제가 있을 수 밖에 없었다. 그렇다면 관건은 파일명을 직접 호출하지 않는 것. 이를 위해 convmv에 -r 옵션을 주고 대상을 현재 디렉토리인 .으로 하였다.
catch {eval exec convmv -f cp949 -t utf-8 --notest -r -- .}

그리고 이 변환을 할 최적의 시점은 uudeview로 파일 생성이 끝나고 그게 glob에 수집되기 직전 단계일 것이다. 이렇게 해서 glob에서는 UTF-8로 변환이 끝난 파일명을 수집하게 되고 인코딩 문제 없이 위치를 바꿀 수 있게 됐다. (2010년 7월 29일)

출력 처리

출력되는 내용 중 아티클의 제목 부분은 깨져서 나오는데 이는 전송 소켓이 fconfigure $sock -encoding binary의 적용을 받는데다 기본적으로 EUC-KR 인코딩이기 때문이다. 사용에는 지장이 없으나, 로그를 깔끔하게 하려면 print에서 해당 부분을 [encoding convertfrom cp949 $var] 식으로 감싸주면 $var를 cp949에서 내부 유니코드로 변환하고 이는 출력 때 UTF-8로 변환 처리된다.

제대로 하자면 소켓에서 꺼내와 내부에서 유통하는 모든 내용을 제대로 인코딩 변환해서 처리하면 좋겠지만 어차피 다들 임시로 소진되는 파일이기 때문에 굳이 안쪽으로 더 파고 들어갈 생각은 안 든다.

accept와 reject

제목 기준으로 받을 대상과 받지 않을 대상을 정할 수 있는데, 기본적으로 서버는 EUC-KR인 데다 소켓이 binary로 처리되다 보니 어떤 식으로 처리를 할지 여의치 않다.


TODO

Comments