Word Finder 개발기 2 - 업데이트
내가 모델로 했던 프로그램의 소스코드에서 얻은 것이 있다면 업데이트 로직이라고 하겠다. 다음은 대략적인 순서다.
- 서버에서 업데이트된 프로그램의 압축파일을 임시 폴더에 받아온다.
- 임시폴더 안의 내용을 모두 압축 해제 한다.
- 현재 실행중인 프로그램을 임시폴더로 넣고 이름을 바꾼다.
- 임시폴더에 있는 새로운 프로그램을 밖으로 꺼내고 실행시킨다.
3, 4번 과정에서 원래 켜져 있던 프로그램이 닫히고 새로운 프로그램이 열리게 된다. 많은 경우 웹 서버에 업데이트 파일을 올리고 거기서 내려받는 것을 볼 수 있었다. 하지만 나 같은 경우 라즈베리파이로 만든 NAS에 저장하고 외부 포트를 열어 익명 접속을 통해 파일을 내려받도록 하였다.
조금 더 구체적인 순서를 보이자면 다음과 같다.
-
사용자가 업데이트 메뉴를 클릭한다.
-
먼저 FTP 서버에 접속해 현재 프로그램과 서버의 올라온 버전을 비교한다.
2-1. 만약 현재 버전이 더 높거나 같다면 업데이트할 필요가 없다는 메세지를 띄우고 돌아간다.
-
서버의 버전이 더 높다면 updater 스레드를 생성한다.
-
updater 스레드는 서버에서 파일을 내려받는다.
4-1. 먼저 서버에 로그인하고 타겟 파일의 용량을 스레드 신호로 내보낸다.
4-2. 타겟 파일을 내려받는다.
4-3. 내려받은 파일의 압축을 푼다.
4-4. 현재 돌아가는 exe파일을 임시 폴더에 이름을 바꾸어 넣고, 임시 폴더에 받은 타겟 exe파일을 현재 경로로 꺼낸다.
4-5. 꺼낸 새 파일을 실행시킨다.
-
updater 스레드가 타겟 파일의 용량을 신호로 보내면 update_checker 라는 새 스레드를 만든다.
5-1. 이 스레드는 현재 임시 폴더에 존재하는 파일의 용량과 타겟 파일의 용량(목표 용량)을 계속 비교한다.
5-2. 비교한 값을 백분율로 환산하여 일정 간격마다 신호로 보낸다.
-
메인 스레드는 받은 신호, 즉 progress 값을 창의 진행 바로 표시한다.
-
모든 절차가 끝나면 현재 창이 닫히고 다운로드한 새 프로그램이 실행된다.
먼저 다음은 업데이트 메뉴를 클릭했을 때 나타나는 창 클래스이다.
class Updater_GUI(QDialog, updater_gui):
def __init__(self, parent, filename):
super(Updater_GUI, self).__init__(parent)
self.setupUi(self)
self.setWindowTitle('Update')
self.show()
self.filename = filename
self.total_size = 0
# 업데이트하는 worker를 생성한다.
self.threadpool = QThreadPool()
self.update_worker = updater.Updater(self.filename)
self.update_worker.set_sever(WORDFINDER_FTP_SERVER)
self.update_worker.set_dir(TEMP_UPDATE_DIR)
self.threadpool.start(self.update_worker)
# 업데이트 worker가 파일 전체 용량을 가르쳐주면 그때부터 진행상황을 체크한다.
self.update_worker.signal.total_size.connect(self.check_progress)
self.update_worker.signal.finished.connect(self.close)
def check_progress(self, size):
self.total_size = size
if self.total_size == 0:
return
# 업데이트 진행상황을 체크하는 worker 스레드 클래스.
class _update_checker(QRunnable):
def __init__(self, filename, total_size):
super(_update_checker, self).__init__()
self.signals = check_signal()
self.filename = filename
self.total_size = total_size
@pyqtSlot()
def run(self):
current_size = 0
total = self.total_size
sleep(3)
# 업데이트 진행상황을 일정한 간격으로 체크한다.
while (current_size <= total):
if os.path.isfile(os.path.join(TEMP_UPDATE_DIR, self.filename + '.zip')):
current_size = os.path.getsize(os.path.join(TEMP_UPDATE_DIR, self.filename + '.zip'))
else:
return
self.signals.progress.emit((int(current_size) / total) * 100)
sleep(0.01)
self.progress_checker = _update_checker(self.filename, self.total_size)
self.threadpool.start(self.progress_checker)
self.progress_checker.signals.progress.connect(self.progress_bar.setValue)
전체 코드는 깃허브에서 볼 수 있다.