未分類

import os
import sys
import time
import requests
import threading
import tkinter as tk
from tkinter import filedialog, messagebox
from datetime import datetime, timezone, timedelta
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import pystray
from PIL import Image, ImageDraw

class GPVManagerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("GPVデータ 自動取得&管理ツール (Rjp対応版)")
        self.root.geometry("600x600") 
        
        self.root.protocol('WM_DELETE_WINDOW', self.hide_window)
        
        self.target_date_var = tk.StringVar()
        self.save_dir_var = tk.StringVar()
        self.start_date_var = tk.StringVar() 
        self.end_date_var = tk.StringVar()   
        
        self.download_event = threading.Event()
        self.period_download_event = threading.Event() 
        self.delete_event = threading.Event()
        
        self.create_widgets()
        self.set_utc_today() 
        
    def create_widgets(self):
        setting_frame = tk.LabelFrame(self.root, text="基本設定", padx=10, pady=10)
        setting_frame.pack(pady=5, padx=10, fill="x")
        
        tk.Label(setting_frame, text="対象日 (UTC自動更新):").grid(row=0, column=0, sticky="w", pady=5)
        tk.Entry(setting_frame, textvariable=self.target_date_var, width=15, state="readonly").grid(row=0, column=1, sticky="w", padx=5)
        tk.Label(setting_frame, text="※起動中は毎日自動で最新日(世界標準時)に切り替わります", fg="gray", font=("", 9)).grid(row=0, column=2, sticky="w")
        
        tk.Label(setting_frame, text="保存先:").grid(row=1, column=0, sticky="w", pady=5)
        tk.Entry(setting_frame, textvariable=self.save_dir_var, width=35).grid(row=1, column=1, sticky="w", padx=5)
        tk.Button(setting_frame, text="参照...", command=self.browse_folder).grid(row=1, column=2, sticky="w")
        
        dl_frame = tk.LabelFrame(self.root, text="当日自動取得 (10分間隔でチェック)", padx=10, pady=10)
        dl_frame.pack(pady=5, padx=10, fill="x")
        
        self.btn_dl_start = tk.Button(dl_frame, text="▶ 取得スタート", command=self.start_download, bg="#e0f7fa", width=15)
        self.btn_dl_start.pack(side="left", padx=10)
        
        self.btn_dl_stop = tk.Button(dl_frame, text="■ 取得ストップ", command=self.stop_download, bg="#ffebee", width=15, state="disabled")
        self.btn_dl_stop.pack(side="left", padx=10)

        period_frame = tk.LabelFrame(self.root, text="期間指定取得 (ファイル名に「Rjp」を含むもの限定)", padx=10, pady=10)
        period_frame.pack(pady=5, padx=10, fill="x")
        
        tk.Label(period_frame, text="開始日 (YYYY/MM/DD):").grid(row=0, column=0, sticky="w", pady=5)
        tk.Entry(period_frame, textvariable=self.start_date_var, width=12).grid(row=0, column=1, sticky="w", padx=5)
        
        tk.Label(period_frame, text="終了日 (YYYY/MM/DD):").grid(row=0, column=2, sticky="w", pady=5)
        tk.Entry(period_frame, textvariable=self.end_date_var, width=12).grid(row=0, column=3, sticky="w", padx=5)
        
        self.btn_period_start = tk.Button(period_frame, text="▶ 期間取得スタート", command=self.start_period_download, bg="#e8f5e9", width=18)
        self.btn_period_start.grid(row=1, column=0, columnspan=2, pady=5, padx=10, sticky="w")
        
        self.btn_period_stop = tk.Button(period_frame, text="■ 期間取得ストップ", command=self.stop_period_download, bg="#ffebee", width=18, state="disabled")
        self.btn_period_stop.grid(row=1, column=2, columnspan=2, pady=5, padx=10, sticky="w")

        del_frame = tk.LabelFrame(self.root, text="24時間経過データの自動削除", padx=10, pady=10)
        del_frame.pack(pady=5, padx=10, fill="x")
        
        self.btn_del_start = tk.Button(del_frame, text="▶ 削除監視スタート", command=self.start_delete, bg="#fff3e0", width=15)
        self.btn_del_start.pack(side="left", padx=10)
        
        self.btn_del_stop = tk.Button(del_frame, text="■ 削除監視ストップ", command=self.stop_delete, bg="#ffebee", width=15, state="disabled")
        self.btn_del_stop.pack(side="left", padx=10)
        
        tk.Label(self.root, text="進行状況ログ (時刻はローカルタイム):").pack(anchor="w", padx=10)
        self.log_text = tk.Text(self.root, height=10, width=75, state="disabled", bg="#f5f5f5")
        self.log_text.pack(pady=5, padx=10, fill="both", expand=True)
        
    def set_utc_today(self):
        utc_today_str = datetime.now(timezone.utc).strftime("%Y/%m/%d")
        self.target_date_var.set(utc_today_str)
        self.start_date_var.set(utc_today_str) 
        self.end_date_var.set(utc_today_str)   
        
    def browse_folder(self):
        folder = filedialog.askdirectory(title="保存先フォルダを選択")
        if folder:
            self.save_dir_var.set(folder)
            
    def log(self, message):
        timestamp = datetime.now().strftime("%H:%M:%S")
        self.log_text.config(state="normal")
        self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
        self.log_text.see(tk.END)
        self.log_text.config(state="disabled")
        self.root.update_idletasks()

    def create_icon_image(self):
        image = Image.new('RGBA', (64, 64), (255, 255, 255, 0))
        dc = ImageDraw.Draw(image)
        
        dc.ellipse((10, 20, 40, 50), fill=(135, 206, 250))
        dc.ellipse((25, 10, 55, 40), fill=(135, 206, 250))
        dc.ellipse((20, 30, 60, 55), fill=(135, 206, 250))
        
        dc.rectangle((15, 32, 42, 38), fill=(0, 0, 139))
        dc.polygon([(42, 25), (42, 45), (57, 35)], fill=(0, 0, 139))
        
        return image

    def hide_window(self):
        self.root.withdraw()
        image = self.create_icon_image()
        
        menu = pystray.Menu(
            pystray.MenuItem('画面を表示', self.show_window),
            pystray.MenuItem('完全に終了', self.quit_app)
        )
        self.icon = pystray.Icon("gpv_app", image, "GPV自動取得ツール", menu)
        
        threading.Thread(target=self.icon.run, daemon=True).start()

    def show_window(self, icon=None, item=None):
        self.icon.stop()
        self.root.after(0, self.root.deiconify)

    def quit_app(self, icon=None, item=None):
        if hasattr(self, 'icon') and self.icon is not None:
            self.icon.stop()
        self.download_event.set()
        self.period_download_event.set() 
        self.delete_event.set()
        os._exit(0)

    def start_download(self):
        if not self.save_dir_var.get():
            messagebox.showwarning("確認", "出力先のフォルダを選択してください。")
            return
            
        self.btn_dl_start.config(state="disabled")
        self.btn_dl_stop.config(state="normal")
        self.download_event.clear()
        
        threading.Thread(target=self.download_loop, daemon=True).start()
        
    def stop_download(self):
        self.log("取得処理の停止信号を送信しました...")
        self.download_event.set()
        self.btn_dl_start.config(state="normal")
        self.btn_dl_stop.config(state="disabled")

    def download_loop(self):
        self.log("自動取得を開始しました。")
        while not self.download_event.is_set():
            utc_today_str = datetime.now(timezone.utc).strftime("%Y/%m/%d")
            
            self.root.after(0, lambda: self.target_date_var.set(utc_today_str))
            
            self._fetch_data(utc_today_str)
            
            if self.download_event.wait(600): 
                break
        self.log("自動取得を完全に停止しました。")

    def _fetch_data(self, target_date_str):
        save_dir = self.save_dir_var.get()
        base_url = f"https://database.rish.kyoto-u.ac.jp/arch/jmadata/data/gpv/original/{target_date_str}/"
            
        try:
            response = requests.get(base_url)
            response.raise_for_status()
            soup = BeautifulSoup(response.text, 'html.parser')
            links = soup.find_all('a')
            
            downloaded_count = 0
            for link in links:
                if self.download_event.is_set():
                    return
                    
                href = link.get('href')
                if not href or href.startswith('?') or href == '../' or href.endswith('/'):
                    continue
                    
                file_url = urljoin(base_url, href)
                file_name = os.path.join(save_dir, href)
                
                if not os.path.exists(file_name):
                    self.log(f"新着発見・ダウンロード中: {href} ...")
                    try:
                        with requests.get(file_url, stream=True) as r:
                            r.raise_for_status()
                            with open(file_name, 'wb') as f:
                                for chunk in r.iter_content(chunk_size=8192):
                                    f.write(chunk)
                        downloaded_count += 1
                        time.sleep(1)
                    except Exception as e:
                        self.log(f"エラー ({href}): {e}")
            
            if downloaded_count > 0:
                self.log(f"更新完了 (UTC {target_date_str} の新規取得: {downloaded_count}件)")
                
        except requests.exceptions.RequestException:
            self.log(f"待機中: UTC {target_date_str} のデータはまだアップロードされていません。")

    def start_period_download(self):
        if not self.save_dir_var.get():
            messagebox.showwarning("確認", "出力先のフォルダを選択してください。")
            return
            
        start_str = self.start_date_var.get().strip()
        end_str = self.end_date_var.get().strip()
        
        try:
            start_date = datetime.strptime(start_str, "%Y/%m/%d")
            end_date = datetime.strptime(end_str, "%Y/%m/%d")
        except ValueError:
            messagebox.showerror("エラー", "日付は YYYY/MM/DD の形式で入力してください。")
            return
            
        if start_date > end_date:
            messagebox.showerror("エラー", "開始日は終了日より前の日付にしてください。")
            return
            
        self.btn_period_start.config(state="disabled")
        self.btn_period_stop.config(state="normal")
        self.period_download_event.clear()
        
        threading.Thread(target=self.period_download_loop, args=(start_date, end_date), daemon=True).start()
        
    def stop_period_download(self):
        self.log("期間取得の停止信号を送信しました...")
        self.period_download_event.set()
        
    def period_download_loop(self, start_date, end_date):
        self.log("期間指定ダウンロード(Rjp限定)を開始しました。")
        current_date = start_date
        save_dir = self.save_dir_var.get()
        
        while current_date <= end_date:
            if self.period_download_event.is_set():
                self.log("期間指定ダウンロードが中断されました。")
                break
                
            date_str = current_date.strftime("%Y/%m/%d")
            self.log(f"--- {date_str} のデータを確認中 ---")
            
            base_url = f"https://database.rish.kyoto-u.ac.jp/arch/jmadata/data/gpv/original/{date_str}/"
            
            try:
                response = requests.get(base_url)
                if response.status_code == 404:
                    self.log(f"データが存在しません (404): {date_str}")
                else:
                    response.raise_for_status()
                    soup = BeautifulSoup(response.text, 'html.parser')
                    links = soup.find_all('a')
                    
                    downloaded_count = 0
                    for link in links:
                        if self.period_download_event.is_set():
                            break
                            
                        href = link.get('href')
                        if not href or href.startswith('?') or href == '../' or href.endswith('/'):
                            continue
                        
                        if "Rjp" not in href:
                            continue
                            
                        file_url = urljoin(base_url, href)
                        file_name = os.path.join(save_dir, href)
                        
                        if not os.path.exists(file_name):
                            self.log(f"Rjpファイル発見・ダウンロード中: {href} ...")
                            try:
                                with requests.get(file_url, stream=True) as r:
                                    r.raise_for_status()
                                    with open(file_name, 'wb') as f:
                                        for chunk in r.iter_content(chunk_size=8192):
                                            f.write(chunk)
                                downloaded_count += 1
                                time.sleep(1) 
                            except Exception as e:
                                self.log(f"エラー ({href}): {e}")
                                
                    if downloaded_count > 0:
                        self.log(f"{date_str} のRjpファイル取得完了: {downloaded_count}件")
                    else:
                        self.log(f"{date_str} に新たな対象Rjpファイルはありません。")
                        
            except requests.exceptions.RequestException as e:
                self.log(f"通信エラー ({date_str}): {e}")
                
            current_date += timedelta(days=1)
            
        self.log("期間指定ダウンロード処理が終了しました。")
        self.root.after(0, lambda: self.btn_period_start.config(state="normal"))
        self.root.after(0, lambda: self.btn_period_stop.config(state="disabled"))

    def start_delete(self):
        if not self.save_dir_var.get():
            messagebox.showwarning("確認", "対象のフォルダを選択してください。")
            return
            
        self.btn_del_start.config(state="disabled")
        self.btn_del_stop.config(state="normal")
        self.delete_event.clear()
        
        threading.Thread(target=self.delete_loop, daemon=True).start()

    def stop_delete(self):
        self.log("削除監視の停止信号を送信しました...")
        self.delete_event.set()
        self.btn_del_start.config(state="normal")
        self.btn_del_stop.config(state="disabled")

    def delete_loop(self):
        self.log("24時間経過データの自動削除監視をスタートしました。")
        while not self.delete_event.is_set():
            save_dir = self.save_dir_var.get()
            now = time.time()
            
            if os.path.exists(save_dir):
                for filename in os.listdir(save_dir):
                    filepath = os.path.join(save_dir, filename)
                    if os.path.isfile(filepath):
                        file_mtime = os.path.getmtime(filepath)
                        
                        if (now - file_mtime) > (24 * 60 * 60):
                            try:
                                os.remove(filepath)
                                self.log(f"自動削除: {filename}")
                            except Exception as e:
                                self.log(f"削除エラー ({filename}): {e}")
            
            if self.delete_event.wait(60): 
                break
        self.log("自動削除監視を完全に停止しました。")

if __name__ == "__main__":
    root = tk.Tk()
    app = GPVManagerApp(root)
    root.mainloop()

コメント