1;;; sj3v2-socket.scm: SJ3 protocol version 2 for uim.
2;;;
3;;; Copyright (c) 2009-2013 uim Project https://github.com/uim/uim
4;;;
5;;; All rights reserved.
6;;;
7;;; Redistribution and use in source and binary forms, with or without
8;;; modification, are permitted provided that the following conditions
9;;; are met:
10;;; 1. Redistributions of source code must retain the above copyright
11;;;    notice, this list of conditions and the following disclaimer.
12;;; 2. Redistributions in binary form must reproduce the above copyright
13;;;    notice, this list of conditions and the following disclaimer in the
14;;;    documentation and/or other materials provided with the distribution.
15;;; 3. Neither the name of authors nor the names of its contributors
16;;;    may be used to endorse or promote products derived from this software
17;;;    without specific prior written permission.
18;;;
19;;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
20;;; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21;;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22;;; ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
23;;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25;;; OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26;;; HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27;;; LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28;;; OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29;;; SUCH DAMAGE.
30;;;;
31
32(use srfi-1)
33(require "util.scm")
34(require "i18n.scm")
35(require "socket.scm")
36(require "lolevel.scm")
37(require "process.scm") ;; getpid
38
39;; sj3v2 protocol operators
40(define $SJ3_CONNECT        1)
41(define $SJ3_DISCONNECT     2)
42(define $SJ3_OPENDICT       11)
43(define $SJ3_CLOSEDICT      12)
44(define $SJ3_OPENSTDY       21)
45(define $SJ3_CLOSESTDY      22)
46(define $SJ3_STDYSIZE       23)
47(define $SJ3_STUDY          61)
48(define $SJ3_MAKEDICT       81)
49(define $SJ3_MAKESTDY       82)
50(define $SJ3_MAKEDIR        83)
51(define $SJ3_ACCESS         84)
52(define $SJ3_PH2KNJ_EUC     111)
53(define $SJ3_CL2KNJ_ALL_EUC 115)
54(define $SJ3_CL2KNJ_CNT_EUC 116)
55(define $SJ3_CLSTUDY_EUC    117)
56
57(define sj3-lib-error-str-alist
58  `((-1  . ,(N_ "Internal server error."))    ;; SJ3_InternalError
59    (0   . ,(N_ "No error."))                 ;; SJ3_NormalEnd
60    (1   . ,(N_ "Serverdown."))               ;; SJ3_ServerDown
61    (2   . ,(N_ "Cannot open socket."))       ;; SJ3_OpenSocket
62    (3   . ,(N_ "Cannot connect socket."))    ;; SJ3_ConnectSocket
63    (4   . ,(N_ "Unknown hostname."))         ;; SJ3_GetHostByName
64    (5   . ,(N_ "Not opened."))               ;; SJ3_NotOpened
65    (6   . ,(N_ "Not enough memory."))        ;; SJ3_NotEnoughMemory
66    (7   . ,(N_ "Illegal command."))          ;; SJ3_IllegalCommand
67    (11  . ,(N_ "Different version."))        ;; SJ3_DifferentVersion
68    (12  . ,(N_ "No host name."))             ;; SJ3_NoHostName
69    (13  . ,(N_ "No user name."))             ;; SJ3_NoUserName
70    (14  . ,(N_ "User not allowd."))          ;; SJ3_NotAllowedUser
71    (15  . ,(N_ "Already connected."))        ;; SJ3_AlreadyConnected
72    (16  . ,(N_ "Not connected."))            ;; SJ3_NotConnected
73    (21  . ,(N_ "Too long parameter."))       ;; SJ3_TooLongParameter
74    (22  . ,(N_ "Illegal parameter."))        ;; SJ3_IllegalParameter
75    (31  . ,(N_ "Bad dictionary ID."))        ;; SJ3_BadDictID
76    (32  . ,(N_ "Illegal dictionary file."))  ;; SJ3_IllegalDictFile
77    (33  . ,(N_ "Illegal study file."))       ;; SJ3_IllegalStdyFile
78    (34  . ,(N_ "Incorrect password."))       ;; SJ3_IncorrectPasswd
79    (35  . ,(N_ "File not exist."))           ;; SJ3_FileNotExist
80    (36  . ,(N_ "Cannot access file."))       ;; SJ3_CannotAccessFile
81    (37  . ,(N_ "Cannot open file."))         ;; SJ3_CannotOpenFile
82    (38  . ,(N_ "Cannot create file."))       ;; SJ3_CannotCreateFile
83    (39  . ,(N_ "File read error."))          ;; SJ3_FileReadError
84    (40  . ,(N_ "File write error."))         ;; SJ3_FileWriteError
85    (41  . ,(N_ "File seek error."))          ;; SJ3_FileSeekError
86    (51  . ,(N_ "Study already opened."))     ;; SJ3_StdyAlreadyOpened
87    (52  . ,(N_ "Study file not opened."))    ;; SJ3_StdyFileNotOpened
88    (53  . ,(N_ "Too small study area."))     ;; SJ3_TooSmallStdyArea
89    (61  . ,(N_ "Locked by other."))          ;; SJ3_LockedByOther
90    (62  . ,(N_ "Not locked."))               ;; SJ3_NotLocked
91    (71  . ,(N_ "No such dictionary."))       ;; SJ3_NoSuchDict
92    (72  . ,(N_ "Dictionary is read only."))  ;; SJ3_ReadOnlyDict
93    (73  . ,(N_ "Dictionary is locked."))     ;; SJ3_DictLocked
94    (74  . ,(N_ "Yomi string is bad."))       ;; SJ3_BadYomiString
95    (75  . ,(N_ "Kanji string is bad."))      ;; SJ3_BadKanjiString
96    (76  . ,(N_ "Hinshi code is bad."))       ;; SJ3_BadHinsiCode
97    (81  . ,(N_ "Add dictionary failed."))    ;; SJ3_AddDictFailed
98    (82  . ,(N_ "Word is already exist."))    ;; SJ3_AlreadyExistWord
99    (83  . ,(N_ "No more douon word."))       ;; SJ3_NoMoreDouonWord
100    (84  . ,(N_ "No more user dictionary."))  ;; SJ3_NoMoreUserDict
101    (85  . ,(N_ "No more index block"))       ;; SJ3_NoMoreIndexBlock
102    (91  . ,(N_ "Delete dictionary failed.")) ;; SJ3_DelDictFailed
103    (92  . ,(N_ "No such word."))             ;; SJ3_NoSuchWord
104    (101 . ,(N_ "Directory already exist."))  ;; SJ3_DirAlreadyExist
105    (102 . ,(N_ "Cannot create directory."))  ;; SJ3_CannotCreateDir
106    (111 . ,(N_ "No more dictionary data."))  ;; SJ3_NoMoreDictData
107    (121 . ,(N_ "User connected."))           ;; SJ3_UserConnected
108    (131 . ,(N_ "Too long password."))        ;; SJ3_TooLongPasswd
109    (132 . ,(N_ "Too long comment."))         ;; SJ3_TooLongComment
110    (133 . ,(N_ "Cannot code convert."))))    ;; SJ3_CannotCodeConvert
111
112
113(define sj3-protocol-version 2)
114
115;;
116;; sj3 protocol api
117;;
118(define (sj3-lib-connect socket user)
119  (file-write socket
120              (u8list->string-buf
121               (u8list-pack '(u32 u32 s8 s8 s8)
122                            $SJ3_CONNECT sj3-protocol-version
123                            "unix" user (format "~a.uim-sj3" (current-process-id)))))
124  (call-with-u8list-unpack
125   '(u32) (string-buf->u8list (file-read socket 4))
126   (lambda (result)
127     (= -2 (u32->s32 result)))))
128
129(define (sj3-lib-disconnect socket)
130  (file-write socket
131              (u8list->string-buf
132               (u8list-pack '(u32) $SJ3_DISCONNECT)))
133  (call-with-u8list-unpack
134   '(u32) (string-buf->u8list (file-read socket 4))
135   (lambda (result)
136     (= 0 result))))
137
138(define (sj3-lib-opendict socket dictionary-name passwd)
139  (file-write socket
140              (u8list->string-buf
141               (u8list-pack '(u32 s8 s8) $SJ3_OPENDICT
142                            dictionary-name passwd)))
143  (call-with-u8list-unpack
144   '(u32) (string-buf->u8list (file-read socket 4))
145   (lambda (result)
146     (and (= result 0)
147          (call-with-u8list-unpack
148           '(u32) (string-buf->u8list (file-read socket 4))
149           (lambda (result)
150             result))))))
151
152(define (sj3-lib-closedict socket dict-id)
153  (file-write socket
154              (u8list->string-buf
155               (u8list-pack '(u32 u32) $SJ3_CLOSEDICT dict-id)))
156  (call-with-u8list-unpack
157   '(u32) (string-buf->u8list (file-read socket 4))
158   (lambda (result)
159     (= 0 result))))
160
161(define (sj3-lib-openstdy socket stdy-name)
162  (file-write socket
163              (u8list->string-buf
164               (u8list-pack '(u32 s8 s8) $SJ3_OPENSTDY stdy-name "")))
165  (call-with-u8list-unpack
166   '(u32) (string-buf->u8list (file-read socket 4))
167   (lambda (result)
168     result)))
169
170(define (sj3-lib-closestdy socket)
171  (file-write socket
172              (u8list->string-buf
173               (u8list-pack '(u32) $SJ3_CLOSESTDY)))
174  (call-with-u8list-unpack
175   '(u32) (string-buf->u8list (file-read socket 4))
176   (lambda (result)
177     result)))
178
179(define (sj3-lib-stdy-size socket)
180  (file-write socket
181              (u8list->string-buf
182               (u8list-pack '(u32) $SJ3_STDYSIZE)))
183  (call-with-u8list-unpack
184   '(u32) (string-buf->u8list (file-read socket 4))
185   (lambda (result)
186     (and (= result 0)
187          (call-with-u8list-unpack
188           '(u32) (string-buf->u8list (file-read socket 4))
189           (lambda (result)
190             result))))))
191
192(define (sj3-lib-study socket stdy)
193  (file-write socket
194              (u8list->string-buf
195               (u8list-pack '(u32 u8list) $SJ3_STUDY stdy)))
196  (call-with-u8list-unpack
197   '(u32) (string-buf->u8list (file-read socket 4))
198   (lambda (result)
199     result)))
200
201(define (sj3-lib-makedict socket dictionary-name)
202  (file-write socket
203              (u8list->string-buf
204               (u8list-pack '(u32 s8 u32 u32 u32) $SJ3_MAKEDICT
205                            dictionary-name
206                            2048  ; Index length
207                            2048  ; Length
208                            256   ; Number
209                            )))
210  (call-with-u8list-unpack
211   '(u32) (string-buf->u8list (file-read socket 4))
212   (lambda (result)
213     (= 0 result))))
214
215(define (sj3-lib-makestdy socket stdy-name)
216  (file-write socket
217              (u8list->string-buf
218               (u8list-pack '(u32 s8 u32 u32 u32) $SJ3_MAKESTDY
219                            stdy-name
220                            2048  ; Number
221                            1     ; Step
222                            2048  ; Length
223                            )))
224  (call-with-u8list-unpack
225   '(u32) (string-buf->u8list (file-read socket 4))
226   (lambda (result)
227     (= 0 result))))
228
229(define (sj3-lib-makedir socket directory-name)
230  (file-write socket
231              (u8list->string-buf
232               (u8list-pack '(u32 s8) $SJ3_MAKEDIR directory-name)))
233  (call-with-u8list-unpack
234   '(u32) (string-buf->u8list (file-read socket 4))
235   (lambda (result)
236     result)))
237
238(define (sj3-lib-access? socket directory-name mode)
239  (file-write socket
240              (u8list->string-buf
241               (u8list-pack '(u32 s8 u32) $SJ3_ACCESS
242                            directory-name
243                            mode)))
244  (call-with-u8list-unpack
245   '(u32) (string-buf->u8list (file-read socket 4))
246   (lambda (result)
247     (= 0 result))))
248
249(define (sj3-lib-ph2knj-euc socket stdy-size yomi)
250  (file-write socket
251              (u8list->string-buf
252               (u8list-pack '(u32 s8) $SJ3_PH2KNJ_EUC yomi)))
253  (call-with-u8list-unpack
254   '(u32 u32) (string-buf->u8list (file-read socket 8))
255   (lambda (result yomi-length)
256     (and (= result 0)
257          (let loop ((yomi-len (cons (car (string-buf->u8list (file-read socket 1)))
258                                     '()))
259                     (rest-stdy '())
260                     (rest-kouho '()))
261            (if (<= (car yomi-len) 0)
262                (values (reverse yomi-len) (reverse rest-stdy) (reverse rest-kouho))
263                (let* ((new-stdy (string-buf->u8list (file-read socket stdy-size)))
264                       (new-kouho (file-read-string-with-terminate socket #\nul)))
265                  (loop (cons (car (string-buf->u8list (file-read socket 1)))
266                              yomi-len)
267                        (cons new-stdy rest-stdy)
268                        (cons new-kouho rest-kouho)))))))))
269
270(define (sj3-lib-cl2knj-all-euc socket stdy-size len yomi)
271  (file-write socket
272              (u8list->string-buf
273               (u8list-pack '(u32 u32 s8) $SJ3_CL2KNJ_ALL_EUC len yomi)))
274  (call-with-u8list-unpack
275   '(u32) (string-buf->u8list (file-read socket 4))
276   (lambda (result)
277     (and (= result 0)
278          (let loop ((yomi-len
279                      (cons (u8list->u32 (string-buf->u8list (file-read socket 4)))
280                            '()))
281                     (rest-stdy '())
282                     (rest-kouho '()))
283            (if (<= (car yomi-len) 0)
284                (values (reverse yomi-len) (reverse rest-stdy) (reverse rest-kouho))
285                (let* ((new-stdy (string-buf->u8list (file-read socket stdy-size)))
286                       (new-kouho (file-read-string-with-terminate socket #\nul)))
287                  (loop (cons (u8list->u32 (string-buf->u8list (file-read socket 4)))
288                              yomi-len)
289                        (cons new-stdy rest-stdy)
290                        (cons new-kouho rest-kouho)))))))))
291
292(define (sj3-lib-cl2knj-cnt-euc socket stdy-size len yomi)
293  (file-write socket
294              (u8list->string-buf
295               (u8list-pack '(u32 u32 s8) $SJ3_CL2KNJ_CNT_EUC len yomi)))
296  (call-with-u8list-unpack
297   '(u32) (string-buf->u8list (file-read socket 4))
298   (lambda (result)
299     (and (= result 0)
300          (call-with-u8list-unpack
301           '(u32) (string-buf->u8list (file-read socket 4))
302           (lambda (result)
303             result))))))
304
305(define (sj3-lib-clstudy-euc socket yomi1 yomi2 stdy)
306  (file-write socket
307              (u8list->string-buf
308               (u8list-pack '(u32 s8 s8 u8list) $SJ3_CLSTUDY_EUC
309                            yomi1 yomi2 stdy)))
310  (call-with-u8list-unpack
311   '(u32) (string-buf->u8list (file-read socket 4))
312   (lambda (result)
313     result)))
314
315
316;;
317;; helper functions
318;;
319(define (sj3-lib-mkdir-p socket path)
320  (let ((entries (string-split path "/")))
321    (fold (lambda (acc rest)
322            (let ((new-path (if (string=? rest "")
323                                acc
324                                (string-append rest "/" acc))))
325              (if (not (sj3-lib-access? socket acc 0))
326                  (sj3-lib-makedir socket new-path))
327              new-path))
328          ""
329          entries)))
330
331(define (sj3-lib-split-yomi yomi yomi-length-list)
332  (let loop ((yomi yomi)
333             (yomi-length-list yomi-length-list)
334             (rest '()))
335    (if (= (car yomi-length-list) 0)
336        (reverse rest)
337        (loop (substring yomi (car yomi-length-list) (string-length yomi))
338              (cdr yomi-length-list)
339              (cons (substring yomi 0 (car yomi-length-list)) rest)))))
340
341
342;;
343;; sj3lib compatible functions
344;;
345
346(define *sj3-lib-socket* #f)
347(define *sj3-lib-stdy-size* 20)
348(define *sj3-lib-main-dict* #f)
349(define *sj3-lib-user-dict* #f)
350
351(define (sj3-lib-get-private-path user-name)
352  (format "user/~a" user-name))
353(define (sj3-lib-get-private-dicionary-name user-name)
354  (format "~a/private.dic" (sj3-lib-get-private-path user-name)))
355(define (sj3-lib-get-private-study-name user-name)
356  (format "~a/study.dat" (sj3-lib-get-private-path user-name)))
357
358(define (sj3-lib-open-with-server server)
359  (let ((server-name (if (equal? server "")
360                         "localhost")))
361    (if sj3-use-remote-server?
362        (tcp-connect server-name 3086)
363        (unix-domain-socket-connect sj3-unix-domain-socket-path))))
364
365(define (sj3-lib-open server user-name)
366  (set! *sj3-lib-socket* (sj3-lib-open-with-server server))
367  (if *sj3-lib-socket*
368      (begin
369        (if (not (sj3-lib-connect *sj3-lib-socket* user-name))
370            (raise (N_ "Cannot connect SJ3 server")))
371        (set! *sj3-lib-main-dict* (sj3-lib-opendict *sj3-lib-socket* "sj3main.dic" ""))
372        (if (not (sj3-lib-access? *sj3-lib-socket* (sj3-lib-get-private-path user-name) 0))
373            (begin
374              (sj3-lib-mkdir-p *sj3-lib-socket* (sj3-lib-get-private-path user-name))
375              (sj3-lib-makedict *sj3-lib-socket* (sj3-lib-get-private-dicionary-name user-name))
376              (uim-notify-info (N_ "SJ3: create new dictionary"))))
377        (if (not (sj3-lib-access? *sj3-lib-socket* (sj3-lib-get-private-study-name user-name) 0))
378            (sj3-lib-makestdy *sj3-lib-socket* (sj3-lib-get-private-study-name user-name)))
379        (set! *sj3-lib-user-dict*
380              (sj3-lib-opendict *sj3-lib-socket* (sj3-lib-get-private-dicionary-name user-name) ""))
381        (sj3-lib-openstdy *sj3-lib-socket* (sj3-lib-get-private-study-name user-name))
382        (set! *sj3-lib-stdy-size* (sj3-lib-stdy-size *sj3-lib-socket*)))
383      (uim-notify-info (N_ "Cannot connect SJ3 server")))
384  *sj3-lib-socket*)
385
386(define (sj3-lib-opened?)
387  *sj3-lib-socket*)
388
389(define (sj3-lib-close)
390  (if *sj3-lib-socket*
391      (begin
392        (sj3-lib-closestdy *sj3-lib-socket*)
393        (sj3-lib-closedict *sj3-lib-socket* *sj3-lib-user-dict*)
394        (sj3-lib-closedict *sj3-lib-socket* *sj3-lib-main-dict*)
395        (sj3-lib-disconnect *sj3-lib-socket*)
396        (file-close *sj3-lib-socket*))))
397
398(define (sj3-lib-getkan yomi)
399  (if *sj3-lib-socket*
400      (receive (yomi-len stdy cands)
401        (sj3-lib-ph2knj-euc *sj3-lib-socket* *sj3-lib-stdy-size* yomi)
402        (cons (apply string-append cands)
403             (zip (sj3-lib-split-yomi yomi yomi-len)
404                   cands
405                   stdy)))
406      #f))
407
408(define (sj3-lib-douoncnt yomi)
409  (if *sj3-lib-socket*
410      (sj3-lib-cl2knj-cnt-euc *sj3-lib-socket* *sj3-lib-stdy-size*
411			      (length (string->list yomi)) ;; byte length
412			      yomi)
413      0))
414
415(define (sj3-lib-getdouon yomi)
416  (receive (yomi-len stdy cand)
417      (sj3-lib-cl2knj-all-euc *sj3-lib-socket* *sj3-lib-stdy-size*
418                              (length (string->list yomi)) ;; byte length
419                              yomi)
420    (zip cand stdy)))
421
422(define (sj3-lib-get-nth-douon yomi nth)
423  (receive (yomi-len stdy cand)
424      (sj3-lib-cl2knj-all-euc *sj3-lib-socket* *sj3-lib-stdy-size*
425                              (length (string->list yomi)) ;; byte length
426                              yomi)
427    (list (list-ref cand nth)
428          (list-ref stdy nth))))
429
430(define (sj3-lib-gakusyuu stdy)
431  (sj3-lib-study *sj3-lib-socket* stdy))
432
433(define (sj3-lib-gakusyuu2 yomi1 yomi2 stdy)
434  (let ((new-yomi1 (or yomi1 ""))
435        (new-yomi2 (or yomi2 "")))
436    (sj3-lib-clstudy-euc *sj3-lib-socket*
437                         new-yomi1 new-yomi2
438                         stdy)))
439
440