ซิงโครไนซ์แพ็คเกจระหว่างเครื่องต่าง ๆ


57

ฉันใช้ emacs ในที่ต่าง ๆ และฉันต้องการติดตั้งและติดตั้งแพคเกจที่คล้ายคลึงกันทุกที่ ฉันเดาว่าฉันสามารถใช้ที่เก็บข้อมูลการควบคุมเวอร์ชันสำหรับไฟล์ติดตั้ง ตั้งแต่ฉันใช้โหมโรงนั่นก็จะเป็น~/.emacs.d/personal/เช่นนั้น

ฉันไม่รู้ว่าจะทำอย่างไรกับแพ็คเกจ มีไฟล์อยู่ใน.emacs.d/รายการแพคเกจที่ติดตั้งซึ่งฉันสามารถใช้ทำ emacs ในเครื่องอื่น ๆ เพื่อติดตั้งไฟล์เหล่านั้นด้วยหรือไม่


2
สำหรับฉันแพ็คเกจที่ติดตั้งเป็นเพียงส่วนหนึ่งของที่เก็บรุ่นควบคุม Emacs ของฉัน ฉันคอมไพล์แพ็กเกจด้วย Emacs เวอร์ชั่นเก่าที่สุดที่ฉันต้องการ / จำเป็นต้องใช้และใส่ไฟล์. elc ลงในที่เก็บ สิ่งนี้ให้การควบคุมและความมั่นคงสูงสุด การแลกเปลี่ยนเป็นพื้นที่เก็บข้อมูลขนาดใหญ่ (ค่อนข้าง) ที่เก็บคอมไพล์อายุ 6 ปีของฉันมีขนาด 120MB ในขณะที่ฉันสามารถหลีกเลี่ยงได้ด้วย 1/10 ของมันหากฉันไม่ได้รวมแพ็คเกจ แต่เมกะไบต์เหล่านั้น "สูญเปล่า" เมกะไบต์เหล่านั้นไม่ต้องกังวลกับฉันจริงๆ
paprika

1
ฉันควรจะเพิ่มในขณะที่ ELPA / MELPA / ... กำลังได้รับความนิยมในทุกวันนี้ แต่ก็ยังมีบางแพ็คเกจที่ไม่สามารถใช้ได้ ดังนั้นหากคุณใช้แพ็คเกจที่ต้องมีการติดตั้งด้วยตนเองคุณอาจไม่ต้องการทำซ้ำความพยายามในแต่ละเครื่องใหม่ที่คุณใช้ (บวกสำหรับการอัพเกรดแพ็คเกจแต่ละครั้ง) อีกวิธีง่ายๆคือเพิ่มแพ็คเกจลงในที่เก็บ Emacs version control ของคุณ
paprika

คุณไม่สามารถคอมไพล์บนไซต์และไม่ต้องสนใจไฟล์. elc ในคอมไพล์หรือไม่?
Vamsi

@Vamsi: ฉันใส่ไฟล์ที่คอมไพล์ด้วยไบต์ลงในที่เก็บเพราะแพ็คเกจ (ไม่ใช่ ELPA) บางตัวค่อนข้างพิถีพิถันในการรวบรวมเนื่องจากการอ้างอิงของพวกเขา สำหรับสิ่งเหล่านี้ฉันไม่ชอบที่จะทำซ้ำขั้นตอนนี้หากไม่จำเป็น
paprika

@paprika คุณช่วยให้รายละเอียดบางอย่างเกี่ยวกับวิธีการรับการตั้งค่าของคุณ? คุณเพิ่มทุกอย่างใน. emacs.d plus .emacs ไปยังที่เก็บแล้วหรือไม่
เริ่มตั้งแต่

คำตอบ:


50

ไม่มีไฟล์รายการที่สร้างขึ้นโดยอัตโนมัติที่คุณสามารถประสานเพื่อให้ได้ผลตามที่ต้องการ

ที่กล่าวถึงสิ่งที่คุณสามารถทำได้คือเพิ่มการโทรไปยังpackage-installการกำหนดค่า emacs ของคุณเอง

(package-install 'auctex)

แนวคิดคือpackage-installidempotent ดังนั้นหากมีแพ็คเกจอยู่แล้วจะไม่มีอะไรเกิดขึ้นจริง สมมติว่าคุณมีการโทรดังกล่าวสำหรับทุกแพ็คเกจที่คุณใช้ (หรืออย่างน้อยก็ทิ้งไว้ในกราฟการพึ่งพา) ซึ่งจะซิงโครไนซ์แพคเกจของคุณบนเครื่องได้อย่างมีประสิทธิภาพ


สำหรับหลายแพ็คเกจคุณสามารถใช้สิ่งต่อไปนี้:

(setq my-package-list '(package1 package2 packageN))
(mapc #'package-install my-package-list)

2
ผมประหลาดใจวิธีที่ง่ายมากวิธีนี้เมื่อเทียบกับวิธีการแก้ปัญหาที่นี่ มีความแตกต่างหรือไม่? package-installข้อมูลยังใช้
Stenskjaer

1
มีการเปลี่ยนแปลงpackage.elตั้งแต่คำตอบที่เชื่อมโยง เป็นไปได้ว่าในเวลานั้นpackage-installการดำเนินการกับแพคเกจที่มีอยู่ไม่เพียง แต่ถอนการติดตั้ง
Jonathan Leech-Pepin

3
เทคนิคนี้เป็นปัญหาที่น่าเสียดาย - แม้ว่าที่เก็บแพ็กเกจการเก็บถาวรจะเหมือนกันระหว่างเครื่องและระบุไว้ใน SCM ไม่รับประกันว่ารุ่นแพ็คเกจจะเหมือนกันระหว่างเครื่อง ปัญหาคือไม่ได้ระบุรุ่นแพ็คเกจ แพ็คเกจเหล่านี้อาจแตกต่างกันเมื่อเวลาผ่านไปและการอ้างอิงของพวกเขาอาจเข้ากันไม่ได้ สิ่งนี้สามารถเกิดขึ้นได้ค่อนข้างง่ายในคลังข้อมูลแพคเกจที่ใช้งานอยู่เช่น melpa
ctpenrose

@ctpenrose: คุณมีข้อเสนอแนะเพื่อหลีกเลี่ยงปัญหานี้หรือไม่?
นักเรียน

@ นักเรียนฉันได้ลดปัญหาโดยใช้ Melpa-stable และอัปเดตแพ็คเกจบ่อยครั้ง
ctpenrose

34

ฉันเก็บไดเรกทอรี. emacs.d ของฉันไว้ในการควบคุมเวอร์ชัน จากนั้นในไฟล์ init.el และไฟล์ที่ตามมาฉันใช้use-packageเพื่อกำหนดการตั้งค่าแพ็คเกจ แพคเกจใช้งานไม่เพียง แต่โหลดแพคเกจของคุณอย่างเฉื่อยเท่านั้น แต่จะดาวน์โหลดตามความต้องการหากไม่มีแพ็กเกจ repos ใด ๆ ที่คุณตั้งค่าไว้

ตัวอย่างเช่นฉันใช้โหมดโกโก้ แต่ไม่ได้ใช้กับทุกเครื่อง ใน init.el ของฉันฉันมีดังต่อไปนี้:

(use-package go-mode
  :ensure t
  :config
  (progn
    (defun my-go-mode-hook ()
      (linum-mode t)
      (setq tab-width 4)
      (add-hook 'before-save-hook 'gofmt-before-save))
    (add-hook 'go-mode-hook 'my-go-mode-hook)))

สิ่งนี้จะเพิ่มโหมดเบ็ด แต่ที่สำคัญกว่าโดยระบุ:ensure tว่าจะดาวน์โหลดแพ็คเกจตามต้องการ

ในการซิงค์เครื่องคุณสามารถเช็คเอาต์หรือดึงจาก repo และเริ่ม Emacs แพ็คเกจใหม่ใด ๆ จะถูกดาวน์โหลดและติดตั้ง


นี่เป็นวิธีแก้ปัญหาที่ฉันใช้ในตอนนี้มากกว่าที่จะใช้ Cask เป็นหลักเพราะ (ตามที่ T. Verron ระบุไว้) Cask ใช้งานไม่ได้ (ดี) บน Windows และยังเป็นการพึ่งพาอื่น
Andy

1
นี่คือวิธีที่ฉันใช้ด้วย แต่แทนที่จะทำเช่น:ensure go-modeนั้นซ้ำชื่อแพคเกจคุณสามารถระบุได้:ensure t
Pedro Luz

จุดดี! นี่คือคำตอบเก่า ฉันจะอัปเดต
elarson

คุณสามารถใช้:hookคำหลักเพื่อทำให้รหัสของคุณง่ายขึ้น
Guilherme Salomé

17

ใน Emacs-25 มีตัวแปรpackage-selected-packagesดังนั้นคุณสามารถปรับแต่งตัวแปรนี้และใช้package-install-selected-packagesเพื่อให้แน่ใจว่าติดตั้งแล้ว


โปรดสังเกตว่าฉันเห็นแล้วชื่อของคำสั่งนั้นค่อนข้างสับสนเล็กน้อย ฉันสามารถเปลี่ยนเป็นแพ็คเกจติดตั้งที่เลือกไว้ได้หรือไม่?
Malabarba

สมมติว่าคุณหมายถึง "ตอนนี้" แทนที่จะเป็น "หมายเหตุ" ใช่
สเตฟาน

9

สิ่งที่คุณต้องการที่จะใช้เป็นถังซึ่งจะช่วยให้คุณสร้างไฟล์ถังcask installระบุแพคเกจที่จะติดตั้งบน สามารถใช้ในการจัดการการพึ่งพาของแพ็คเกจและ "การพึ่งพา" ของการกำหนดค่า Emacs ของคุณได้อย่างง่ายดาย วางไฟล์ Cask ของคุณไว้ในการควบคุมเวอร์ชันและติดตั้ง / อัปเดตแพ็คเกจตามเครื่องต่อเครื่อง


4
เป็นที่น่าสังเกตว่า (ณ วันนี้) โซลูชันนี้ใช้ไม่ได้กับเครื่อง windows
T. Verron

1
ฉันไม่ได้ใช้โซลูชันนี้อีกต่อไปด้วยเหตุผลอย่างมากของคุณ เหมาะสำหรับทำแพ็คเกจ น่ากลัวสำหรับการจัดการการกำหนดค่า
Andy

6

วิธีทางเลือกที่จะเป็นต่อไปนี้: เพราะผมไม่ต้องการที่จะประสานแพคเกจ Emacs ของฉัน แต่ยังไฟล์อื่น ๆ (เช่น.emacs, .bashrcแต่ยังไดเรกทอรีอื่น ๆ ) ระหว่างเซิร์ฟเวอร์และแล็ปท็อปของฉันของฉันฉันเริ่มที่จะใช้unisonในการซิงค์ไฟล์และ ไดเรกทอรี ดังนั้นเมื่อทำงานกับแล็ปท็อปของฉันฉันก็วิ่งได้ง่ายunison laptopก่อนสิ่งอื่นใด ~/.unison/laptop.prfไฟล์ของฉันมีส่วนต่อไปนี้สำหรับไฟล์ที่เกี่ยวข้องกับ Emacs:

path = .emacs
path = .emacs.d
ignore = Path {.emacs.d/semanticdb}

ตั้งแต่แพ็คเกจ Emacs ของฉัน (และสำรองข้อมูลและบุ๊คมาร์ค Emacs ของฉัน) จะถูกเก็บไว้ใน~/.emacs.dนี้ทำให้แน่ใจว่าฉันมีทุกอย่างในเครื่องของฉัน

อีกวิธีหนึ่งที่จะทำให้.emacs.dไดเรกทอรีในไดเรกทอรีที่ซิงค์กับ OwnCloud, DropBox หรือบริการซิงค์ไฟล์อื่น ๆ แล้วสร้าง symlink จาก~/.emacs.dไปยังไดเรกทอรีที่ใช้ร่วมกัน


5

ในขณะที่package.elเป็นวิธีมาตรฐานในการติดตั้งแพคเกจคุณอาจต้องการลองใช้el-getซึ่งมีประโยชน์มากสำหรับการติดตั้งแพ็กเกจที่ไม่ได้ (หรือไม่สามารถ) บน elpa คำตอบนี้เกี่ยวข้องกับการซิงโครไนซ์แพ็คเกจดังกล่าว

วิธีที่คุณมั่นใจว่าติดตั้งแพ็กเกจที่ให้ไว้เมื่อใช้ el-get คือการเพิ่มสิ่งต่อไปนี้ในไฟล์ init ของคุณ

(el-get 'sync '(packages))

โดยที่ packages เป็นรายการของแพ็กเกจที่คุณต้องการติดตั้ง ฟังก์ชั่นนี้คล้ายกับpackage-installติดตั้งแพ็กเกจเฉพาะเมื่อยังไม่ได้ติดตั้งมิฉะนั้นจะเริ่มต้นแพ็คเกจได้ทันที


5

ฉันใช้เคล็ดลับเล็ก ๆ น้อย ๆ "ขโมย" จาก emacs-starter-kit (ฉันคิดว่า):

(defun maybe-install-and-require (p)
  (when (not (package-installed-p p))
   (package-install p))
  (require p))

ดังนั้นเมื่อฉันต้องการแพคเกจฉันก็ใช้:

(maybe-install-and-require 'magit)

ในการเริ่มต้น emacs ประเมินการตั้งค่าของฉันpackage.elจะให้การติดตั้ง magit หากไม่ได้ติดตั้ง

คุณสามารถค้นหาการกำหนดค่าของฉันได้ที่นี่:

https://github.com/mdallastella/emacs-config/


1
ตามปรัชญาเดียวกันคุณสามารถใช้ 'บุคคลที่ผิดธรรมดา' จากบุคคลที่ผิดธรรมดา
csantosb

3

ฉันมีไดเรกทอรี ~ / emacs ซึ่งควบคุมเวอร์ชัน Mercurial และมีทุกอย่างที่การตั้งค่า emacs ของฉันประกอบด้วย (~ / emacs / site-lisp สำหรับห้องสมุดที่ดาวน์โหลดด้วยตนเอง ~ / emacs / elpa สำหรับห้องสมุดที่ติดตั้ง Elpa ~ ~ emacs / etc / สำหรับ splash. emacs ~ / emacs / dot-emacs.el ซึ่งฉันเห็นว่าเป็น ~ / .emacs) มันต้องมีการปรับแต่งบางแพ็กเกจเพื่อให้ไฟล์สำคัญทั้งหมดอยู่ในทรีนี้ แต่มันทำงานได้ดี บิตเหล่านั้นซึ่งเป็นเครื่องเฉพาะฉันได้นำไปใช้โดยเงื่อนไขบนชื่อระบบ

ดังนั้นหลังจากที่ฉันติดตั้ง / ตั้งค่าใหม่ / เปลี่ยนแปลงอะไรฉันก็แค่ดึง / ดันการเปลี่ยนแปลงทั้งหมดระหว่างเครื่องทั้งหมดที่ฉันใช้

ผลประโยชน์พิเศษคือฉันมีประวัติสมบูรณ์ของการกำหนดค่าของฉันและสามารถย้อนกลับ / แยก / เปลี่ยนกลับในกรณีที่มีอะไรผิดพลาด

ดูเหมือนว่า Mercurial PS จะเหมาะสมเป็นพิเศษเนื่องจากมีการดึง / ผลักสองด้านอย่างเป็นธรรมชาติ


3

ฉันมีนี้setup-packages.elในการตั้งค่าของฉัน emacs ซึ่งเป็นไฮบริดของรหัสจากโหมโรงและบล็อก Tomorokoshi ในการบริหารจัดการแพคเกจ

setup-packages.el ทำต่อไปนี้:

  • สร้างไดเรกทอรีสำหรับแพคเกจหากไม่อยู่และเพิ่มและไดเรกทอรีย่อยไปยังelpaload-path
  • อัปเดตpackage-archivesรายการด้วย Melpa
  • ตรวจสอบว่าคุณมีแพ็คเกจmy-packagesทั้งหมดในรายการติดตั้งหรือไม่ หากไม่ได้ติดตั้งแพ็คเกจให้ติดตั้ง

วิธีการใช้งาน

  • บันทึกsetup-packages.elด้านล่างไปยัง~/.emacs.d/ไดเรกทอรีของคุณ
  • ตั้งค่าuser-emacs-directory, setup-packages-fileและmy-packagesตัวแปรในของคุณและทำinit.el(load setup-packages-file)

เมื่อคุณเริ่ม emacs เป็นครั้งแรกบนเครื่องที่ไม่ได้ติดตั้งแพ็กเกจเหล่านี้ pacakges ทั้งหมดที่อยู่ในรายการmy-packagesจะได้รับการติดตั้งอัตโนมัติ

การติดตั้ง-packages.el

;; setup-packages.el - Package management

(require 'cl)
(require 'package)

;; Set the directory where you want to install the packages
(setq package-user-dir (concat user-emacs-directory "elpa/"))

;; Add melpa package source when using package list
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)

;; Load emacs packages and activate them
;; This must come before configurations of installed packages.
;; Don't delete this line.
(package-initialize)
;; `package-initialize' call is required before any of the below
;; can happen

;; Auto install the required packages
;; Method to check if all packages are installed
(defun packages-installed-p ()
  (loop for p in my-packages
        when (not (package-installed-p p)) do (return nil)
        finally (return t)))

;; if not all packages are installed, check one by one and install the missing ones.
(unless (packages-installed-p)
  ;; check for new packages (package versions)
  (message "%s" "Emacs is now refreshing its package database...")
  (package-refresh-contents)
  (message "%s" " done.")
  ;; install the missing packages
  (dolist (p my-packages)
    (when (not (package-installed-p p))
      (package-install p))))

(provide 'setup-packages)

init.el

คุณจะต้องมีสิ่งต่อไปนี้ในinit.el:

(setq user-home-directory  (getenv "HOME"))
(setq user-emacs-directory (concat user-home-directory ".emacs.d/"))
(setq setup-packages-file  (expand-file-name "setup-packages.el" user-emacs-directory))

;; A list of packages to ensure are installed at launch
(setq my-packages
      '(
        ;; package1
        ;; package2
       ))

(load setup-packages-file nil :nomessage) ; Load the packages

2

เพื่อที่จะสะท้อนการตั้งค่าของฉันฉันตัดสินใจที่จะใช้วิธีการอื่นโดยใช้Syncthing ; การเปลี่ยนแปลงในไฟล์ปรับแต่งใด ๆ ของฉันจะแพร่กระจายไปยังชิ้นส่วนอื่น ๆ ของฉันโดยไม่ต้องสนใจดังนั้นเมื่อฉันอัปเกรดแพ็คเกจฉันต้องทำในชิ้นใดชิ้นหนึ่ง


2

RSYNC : ซิงโครไนซ์โฟลเดอร์ / ไฟล์ที่เลือกโดยใช้rsyncไม่ว่าจะผ่านเครือข่ายในบ้านหรือผ่านทางsshเซิร์ฟเวอร์ระยะไกล

rsyncเป็นยูทิลิตี้การซิงโครไนซ์ทางเดียวที่สามารถลบไฟล์ในเป้าหมายได้ดังนั้นโปรดสำรองข้อมูลของคุณทั้งที่ต้นทางและที่ตั้งเป้าหมายและทดสอบอย่างละเอียดโดยใช้--dry-runตัวเลือกก่อนดำเนินการจริง

หากต้องการอ่านเกี่ยวกับวิธีกำหนดค่า.authinfoไฟล์อย่างถูกต้องโปรดดู https://www.gnu.org/software/emacs/manual/auth.html.authinfoเนื้อหาไฟล์ ตัวอย่าง(ซึ่งอาจมีหลายรายการที่แตกต่างกัน) มีดังนี้:

machine mymachine login myloginname password mypassword port myport

กำหนดค่าและใช้ฟังก์ชันrsync-remoteเพื่อซิงโครไนซ์sshกับเซิร์ฟเวอร์ระยะไกล หรือใช้ฟังก์ชั่นrsync-localเพื่อซิงโครไนซ์บนคอมพิวเตอร์เครื่องเดียวกันหรือผ่านเครือข่ายในบ้านที่เชื่อถือได้

(require 'auth-source)

;;; EXAMPLE:
;;;   (get-auth-info "12.34.567.89" "username")
;;;   (get-auth-info "localhost" "root")
(defun get-auth-info (host user &optional port)
  (let ((info (nth 0 (auth-source-search
                      :host host
                      :user user
                      :port port
                      :require '(:user :secret)
                      :create t))))
    (if info
      (let* ((port (plist-get info :port))
             (secret-maybe (plist-get info :secret))
             (secret
               (if (functionp secret-maybe)
                 (funcall secret-maybe)
                 secret-maybe)))
          (list port secret))
    nil)))

(defun rsync-filter (proc string)
  (cond
    ((string-match
       "^\\([a-zA-Z0-9_\\-\\.]+\\)@\\([a-zA-Z0-9_\\-\\.]+\\)'s password: "
       string)
      (let* ((user (substring string (match-beginning 1) (match-end 1)))
             (host (substring string (match-beginning 2) (match-end 2)))
             (password (car (cdr (get-auth-info host user)))))
        (process-send-string proc (concat password "\n"))))
    ((not (or (string-match "files\\.\\.\\.\r" string)
              (string-match "files to consider\n" string)))
      (with-current-buffer (messages-buffer)
        (let ((inhibit-read-only t))
          (goto-char (point-max))
          (when (not (bolp))
            (insert "\n"))
          (insert string)
          (when (not (bolp))
            (insert "\n")))))))

(defun rsync-remote ()
"Use rsync to a remote server via ssh.  Back-up your data first!!!"
(interactive)
  (let* (
      (host "localhost")
      (username "root")
      (port (or (car (get-auth-info host username))
                (number-to-string (read-number "Port:  "))))
      (source
        (let ((dir (expand-file-name (locate-user-emacs-file "elpa/"))))
          (if (file-directory-p dir)
            dir
            (let ((debug-on-quit nil)
                  (msg (format "`%s` is not a valid directory." dir)))
              (signal 'quit `(,msg))))))
      (target "/private/var/mobile/elpa/")
      (ssh "/usr/bin/ssh")
      (rsync "/usr/bin/rsync")
      (rsync-include-file "/path/to/include-file.txt")
      (rsync-exclude-file "/path/to/exclude-file.txt")
      (rsh (concat "--rsh=ssh -p " port " -l " username))
      (host+target (concat host ":" target)))
    (start-process
        "rsync-process"
        nil
        rsync
        "-avr" ;; must specify the `-r` argument when using `--files-from`
        "--delete"
        ;; The paths inside the exclusion file must be relative, NOT absolute.
        ;;; (concat "--files-from=" rsync-include-file)
        ;;; (concat "--exclude-from=" rsync-exclude-file)
        rsh
        source
        host+target)
    (set-process-filter (get-process "rsync-process") 'rsync-filter)
    (set-process-sentinel
      (get-process "rsync-process")
      (lambda (p e) (when (= 0 (process-exit-status p))
        (message "rsync-remote:  synchronizing ... done."))))))

(defun rsync-local ()
"Use rsync locally -- e.g., over a trusted home network.
 Back-up your data first!!!"
  (interactive)
  (let (
      (rsync-program "/usr/bin/rsync")
      (source
        (let ((dir (expand-file-name
                     (file-name-as-directory
                       (read-directory-name "Source Directory: " nil nil nil nil)))))
          (if (file-directory-p dir)
            dir
            (let ((debug-on-quit nil)
                  (msg (format "`%s` is not a valid directory." dir)))
              (signal 'quit `(,msg))))))
      (target (expand-file-name
                (file-name-as-directory
                  (read-directory-name "Target Directory: " nil nil nil nil)))))
    (unless (y-or-n-p (format "SOURCE:  %s | TARGET:  %s" source target))
      (let ((debug-on-quit nil))
        (signal 'quit `("You have exited the function."))))
    (start-process "rsync-process"
      nil
      rsync-program
      "--delete"
      "-arzhv"
      source
      target)
    (set-process-filter (get-process "rsync-process") #'rsync-process-filter)
    (set-process-sentinel
      (get-process "rsync-process")
      (lambda (p e)
        (when (= 0 (process-exit-status p))
        (message "Done!"))))))

0

https://github.com/redguardtoo/elpa-mirrorสร้างที่เก็บโลคัลของแพ็กเกจที่ติดตั้งทั้งหมด

M-x elpamr-create-mirror-for-installedการใช้งานง่ายเพียงแค่เรียกใช้

บนเครื่องอื่น ๆ ใส่(setq package-archives '(("myelpa" . "~/myelpa/")))เข้าไปในของคุณ.emacsและเริ่มต้น Emacs

ในทุกเครื่องคุณจะได้รับแพ็คเกจรุ่นเดียวกันทุกประการ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.