3597 lines
136 KiB
Python
Executable File
3597 lines
136 KiB
Python
Executable File
import data.qa as qa
|
|
import wx
|
|
import wx.lib.delayedresult as delayedresult
|
|
import wx.lib.dialogs
|
|
import os
|
|
import subprocess
|
|
import traceback
|
|
import time
|
|
import zipfile
|
|
import copy
|
|
import shutil
|
|
import codecs
|
|
import struct
|
|
import re
|
|
|
|
import sys
|
|
import platform
|
|
|
|
import mysql.connector as connector
|
|
|
|
|
|
import xml.etree.ElementTree
|
|
import json
|
|
|
|
|
|
from multiprocessing.connection import Listener, Client
|
|
import socket
|
|
import SocketServer
|
|
import threading
|
|
|
|
ZIP_MODE = False
|
|
MACHINE_NAME_MODE = False
|
|
#IN_SOCKET = None
|
|
|
|
DEBUG_LOGGING = 0
|
|
|
|
def DebugLog(msg, override=0):
|
|
if override == 1 or DEBUG_LOGGING == 1:
|
|
filePath = os.path.join(sys.path[0],'mpLogGrabber_debug_log.txt')
|
|
if os.path.exists(filePath):
|
|
stats = os.stat(filePath)
|
|
if stats.st_size > 41000: #>5mb
|
|
f = open('mpLogGrabber_debug_log.txt', 'w')
|
|
else:
|
|
f = open('mpLogGrabber_debug_log.txt', 'a')
|
|
f.write(msg+"[%s]\n" % stats.st_size)
|
|
f.close()
|
|
|
|
class MainPanel(wx.Panel):
|
|
def __init__(self, *args, **kwargs):
|
|
wx.Panel.__init__(self, *args, **kwargs)
|
|
|
|
self.datafile = 'data\\mpLogGrabber.ini'
|
|
|
|
self.abort_transfer = False
|
|
self.transfer_aborted = False
|
|
|
|
self.default_game_id = None
|
|
self.checked_settings = []
|
|
self.unchecked_settings = []
|
|
self.CustomMessageBugPlaceholder = ""
|
|
self.CustomClipboardMessage = ""
|
|
|
|
## self.update_timer_id = wx.NewId()
|
|
## self.update_timer = wx.Timer(self, self.update_timer_id)
|
|
## self.Bind(wx.EVT_TIMER, self.OnTimedUpdate,
|
|
## id=self.update_timer_id)
|
|
|
|
self.copier = None
|
|
self.pct_copied = 0
|
|
|
|
self.total_bytes = 0
|
|
self.bytes_copied = 0
|
|
|
|
self.data = qa.SettingsLoader()
|
|
self.data.LoadFile(self.datafile)
|
|
|
|
db_info = self.data['db']
|
|
self.db = DB(db_info['db_host'],
|
|
db_info['db_database'],
|
|
db_info['db_user'],
|
|
db_info['db_pass'])
|
|
|
|
self.menubar = wx.MenuBar()
|
|
self.GetParent().SetMenuBar(self.menubar)
|
|
p_frame = self.GetParent()
|
|
p_frame.CreateStatusBar()
|
|
m_data = wx.Menu()
|
|
self.menubar.Append(m_data, 'Players')
|
|
|
|
m_data_refresh = wx.MenuItem(m_data,
|
|
wx.NewId(),
|
|
'Refresh Data\tF5',
|
|
'Refresh player lists from the database')
|
|
m_data.AppendItem(m_data_refresh)
|
|
p_frame.Bind(wx.EVT_MENU, self.RefreshData,
|
|
id=m_data_refresh.GetId())
|
|
|
|
m_data.AppendSeparator()
|
|
|
|
m_data_addplayer = wx.MenuItem(m_data,
|
|
wx.NewId(),
|
|
'&New Player',
|
|
'Add a new player to the database')
|
|
m_data.AppendItem(m_data_addplayer)
|
|
p_frame.Bind(wx.EVT_MENU, self.OnAddPlayer,
|
|
id=m_data_addplayer.GetId())
|
|
|
|
m_data_removeplayer = wx.MenuItem(m_data,
|
|
wx.NewId(),
|
|
'&Remove a Player',
|
|
'Remove a player from the database')
|
|
m_data.AppendItem(m_data_removeplayer)
|
|
p_frame.Bind(wx.EVT_MENU, self.OnRemovePlayer,
|
|
id=m_data_removeplayer.GetId())
|
|
|
|
|
|
m_data_modplayer = wx.MenuItem(m_data,
|
|
wx.NewId(),
|
|
'&Modify a player',
|
|
'Modify a players details')
|
|
m_data.AppendItem(m_data_modplayer)
|
|
p_frame.Bind(wx.EVT_MENU, self.OnModifyPlayer,
|
|
id=m_data_modplayer.GetId())
|
|
|
|
|
|
m_bug = wx.Menu()
|
|
self.menubar.Append(m_bug, 'Bug Folder')
|
|
|
|
m_bug_open = wx.MenuItem(m_bug,
|
|
wx.NewId(),
|
|
'&Open bug folder\tF3',
|
|
'Open the bug folder in explorer')
|
|
m_bug.AppendItem(m_bug_open)
|
|
p_frame.Bind(wx.EVT_MENU, self.OnOpenBugFolder,
|
|
id=m_bug_open.GetId())
|
|
|
|
m_bug_copy = wx.MenuItem(m_bug,
|
|
wx.NewId(),
|
|
'&Copy bug location\tF4',
|
|
'Copy bug network location to clipboard')
|
|
m_bug.AppendItem(m_bug_copy)
|
|
p_frame.Bind(wx.EVT_MENU, self.OnCopy,
|
|
id=m_bug_copy.GetId())
|
|
|
|
m_bug_copy_custom = wx.MenuItem(m_bug,
|
|
wx.NewId(),
|
|
'&Copy bug location w/ message\tF6',
|
|
'Copy bug network location and custom comment message to clipboard')
|
|
m_bug.AppendItem(m_bug_copy_custom)
|
|
p_frame.Bind(wx.EVT_MENU, self.OnCopyCustom,
|
|
id=m_bug_copy_custom.GetId())
|
|
|
|
m_bug.AppendSeparator()
|
|
|
|
m_bug_report = wx.MenuItem(m_bug,
|
|
wx.NewId(),
|
|
'&View transfer report',
|
|
'Open the transfer report for the current bug folder')
|
|
m_bug.AppendItem(m_bug_report)
|
|
p_frame.Bind(wx.EVT_MENU, self.OnViewReport,
|
|
id=m_bug_report.GetId())
|
|
|
|
m_groups = wx.Menu()
|
|
self.menubar.Append(m_groups, 'Groups')
|
|
|
|
|
|
m_groups_load = wx.MenuItem(m_groups,
|
|
wx.NewId(),
|
|
'&Load groups\tF8',
|
|
'Set the active players list from a previously stored groups')
|
|
m_groups.AppendItem(m_groups_load)
|
|
p_frame.Bind(wx.EVT_MENU, self.OnLoadGroup,
|
|
id=m_groups_load.GetId())
|
|
|
|
m_groups_add = wx.MenuItem(m_groups,
|
|
wx.NewId(),
|
|
'&Add groups to current list\tF9',
|
|
'Append stored groups to current active players list')
|
|
m_groups.AppendItem(m_groups_add)
|
|
p_frame.Bind(wx.EVT_MENU, self.OnAddGroup,
|
|
id=m_groups_add.GetId())
|
|
|
|
m_groups.AppendSeparator()
|
|
|
|
|
|
m_groups_new = wx.MenuItem(m_groups,
|
|
wx.NewId(),
|
|
'Create &new group',
|
|
'Create a new group from the current list of active players')
|
|
m_groups.AppendItem(m_groups_new)
|
|
p_frame.Bind(wx.EVT_MENU, self.OnNewGroup,
|
|
id=m_groups_new.GetId())
|
|
|
|
m_groups_replace = wx.MenuItem(m_groups,
|
|
wx.NewId(),
|
|
'&Update group',
|
|
'Replace the contents of a group with the current list of active players')
|
|
m_groups.AppendItem(m_groups_replace)
|
|
p_frame.Bind(wx.EVT_MENU, self.OnReplaceGroup,
|
|
id=m_groups_replace.GetId())
|
|
|
|
|
|
m_groups_del = wx.MenuItem(m_groups,
|
|
wx.NewId(),
|
|
'&Delete group',
|
|
'Select a group to delete')
|
|
m_groups.AppendItem(m_groups_del)
|
|
p_frame.Bind(wx.EVT_MENU, self.OnDeleteGroup,
|
|
id=m_groups_del.GetId())
|
|
|
|
|
|
|
|
self.m_settings = wx.Menu()
|
|
self.menubar.Append(self.m_settings, 'Settings')
|
|
|
|
m_set_zipmode = wx.MenuItem(self.m_settings,
|
|
wx.NewId(),
|
|
'Auto-Zip Logs',
|
|
'Automatically compresses logfiles into zips after finishing the copy operation',
|
|
wx.ITEM_CHECK)
|
|
|
|
self.m_settings.AppendItem(m_set_zipmode)
|
|
#if m_set_zipmode.GetLabel().lower() in self.checked_settings:
|
|
# m_set_zipmode.Check(True)
|
|
#elif m_set_zipmode.GetLabel().lower() in self.unchecked_settings:
|
|
# m_set_zipmode.Check(False)
|
|
#else: # This is for the default value for this option (if the user settings file is missing)
|
|
m_set_zipmode.Check(True)
|
|
|
|
#p_frame.Bind(wx.EVT_MENU, self.OnToggleZipMode,
|
|
#id=m_set_zipmode.GetId())
|
|
|
|
self.m_set_zipmode = m_set_zipmode
|
|
|
|
m_set_usefilter = wx.MenuItem(self.m_settings,
|
|
wx.NewId(),
|
|
'Use Filters',
|
|
'Skip files that match the selected patterns.',
|
|
wx.ITEM_CHECK)
|
|
self.m_settings.AppendItem(m_set_usefilter)
|
|
#if m_set_usefilter.GetLabel().lower() in self.checked_settings:
|
|
# m_set_usefilter.Check(True)
|
|
#elif m_set_usefilter.GetLabel().lower() in self.unchecked_settings:
|
|
# m_set_usefilter.Check(False)
|
|
#else: # This is for the default value for this option (if the user settings file is missing)
|
|
m_set_usefilter.Check(False)
|
|
|
|
self.m_set_usefilter = m_set_usefilter
|
|
|
|
|
|
m_set_selectfilter = wx.MenuItem(self.m_settings,
|
|
wx.NewId(),
|
|
'Select Filters',
|
|
'Select the filters that will be applied to filenames.')
|
|
self.m_settings.AppendItem(m_set_selectfilter)
|
|
p_frame.Bind(wx.EVT_MENU, self.OnSetFilters,
|
|
id = m_set_selectfilter.GetId())
|
|
|
|
m_set_usemachinename = wx.MenuItem(self.m_settings,
|
|
wx.NewId(),
|
|
'Use Machine Names',
|
|
'Add machine name next to gamer tag.',
|
|
wx.ITEM_CHECK)
|
|
self.m_settings.AppendItem(m_set_usemachinename)
|
|
p_frame.Bind(wx.EVT_MENU, self.OnUseMachineName,
|
|
id = m_set_usemachinename.GetId())
|
|
|
|
self.m_set_usemachinename = m_set_usemachinename
|
|
|
|
m_set_custommessage = wx.MenuItem(self.m_settings,
|
|
wx.NewId(),
|
|
'Set Comment Message',
|
|
'Set custom text to be copied into clipboard together with bug location.')
|
|
self.m_settings.AppendItem(m_set_custommessage)
|
|
p_frame.Bind(wx.EVT_MENU, self.OnSetCustomMessage,
|
|
id = m_set_custommessage.GetId())
|
|
|
|
self.m_set_custommessage = m_set_custommessage
|
|
|
|
m_set_remote = wx.MenuItem(self.m_settings,
|
|
wx.NewId(),
|
|
'Experimental Remote Control',
|
|
'send commands over the network IT IS AN EXPERIMENT',
|
|
wx.ITEM_CHECK)
|
|
self.m_settings.AppendItem(m_set_remote)
|
|
self.m_set_remote = m_set_remote
|
|
|
|
HOST, PORT = "0.0.0.0", 9999
|
|
rc = RemoteControl
|
|
rc.parent = self
|
|
#SocketServer.TCPServer.allow_reuse_address = True
|
|
self.remote_server = SocketServer.TCPServer((HOST, PORT), rc)
|
|
self.server_thread = threading.Thread(target=self.remote_server.serve_forever)
|
|
self.server_thread.daemon = True
|
|
self.server_thread.start()
|
|
|
|
self.remote_busy = False
|
|
|
|
if self.m_set_remote.GetLabel().lower() in self.checked_settings:
|
|
self.m_set_remote.Check(True)
|
|
elif self.m_set_remote.GetLabel().lower() in self.unchecked_settings:
|
|
self.m_set_remote.Check(False)
|
|
else:
|
|
self.m_set_remote.Check(True)
|
|
|
|
|
|
main_sizer = wx.BoxSizer(wx.VERTICAL)
|
|
self.SetSizer(main_sizer)
|
|
|
|
## player_sizer = wx.StaticBoxSizer(wx.StaticBox(self,
|
|
## label='Players'),
|
|
## wx.HORIZONTAL)
|
|
player_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
main_sizer.Add(player_sizer, flag=wx.EXPAND |
|
|
wx.TOP | wx.LEFT | wx.RIGHT,
|
|
border=3, proportion=5)
|
|
|
|
player_box_sizer = wx.StaticBoxSizer(wx.StaticBox(self,
|
|
label='Inactive Players'),
|
|
wx.VERTICAL)
|
|
|
|
player_sizer.Add(player_box_sizer, flag=wx.EXPAND, proportion=1)
|
|
self.players = wx.ListBox(self, style=wx.LB_EXTENDED)
|
|
player_box_sizer.Add(self.players, flag=wx.EXPAND, proportion=1)
|
|
self.players_filter = wx.Choice(self)
|
|
player_box_sizer.Add(self.players_filter, flag=wx.EXPAND | wx.TOP, proportion=0, border=1)
|
|
self.players_filter.Bind(wx.EVT_CHOICE, self.OnFilterChange)
|
|
|
|
|
|
player_btn_sizer = wx.BoxSizer(wx.VERTICAL)
|
|
player_sizer.Add(player_btn_sizer, flag=wx.ALIGN_CENTER_VERTICAL)
|
|
|
|
btn_add = wx.Button(self, label='>', size=(32, 32))
|
|
player_btn_sizer.Add(btn_add)
|
|
btn_add.Bind(wx.EVT_BUTTON, self.ActivateSelectedPerson)
|
|
self.players.Bind(wx.EVT_LISTBOX_DCLICK, self.ActivateSelectedPerson)
|
|
self.btn_add = btn_add
|
|
|
|
btn_remove = wx.Button(self, label='<', size=(32, 32))
|
|
player_btn_sizer.Add(btn_remove)
|
|
btn_remove.Bind(wx.EVT_BUTTON, self.DeActivateSelectedPerson)
|
|
self.btn_remove = btn_remove
|
|
|
|
players_actives_sizer = wx.StaticBoxSizer(wx.StaticBox(self,
|
|
label='Active Players'))
|
|
player_sizer.Add(players_actives_sizer, flag=wx.EXPAND, proportion=1)
|
|
self.actives = wx.ListBox(self, style=wx.LB_EXTENDED)
|
|
players_actives_sizer.Add(self.actives, flag=wx.EXPAND, proportion=1)
|
|
self.actives.Bind(wx.EVT_LISTBOX_DCLICK, self.DeActivateSelectedPerson)
|
|
|
|
#data_sizer = wx.StaticBoxSizer(wx.StaticBox(self, label='Data'),
|
|
#wx.HORIZONTAL)
|
|
#main_sizer.Add(data_sizer, flag=wx.EXPAND |
|
|
#wx.TOP | wx.LEFT | wx.RIGHT,
|
|
#border=3, proportion=0)
|
|
|
|
#self.btn_refresh = wx.Button(self, label = 'Refresh Data')
|
|
#data_sizer.Add(self.btn_refresh)
|
|
#self.btn_refresh.Bind(wx.EVT_BUTTON, self.RefreshData)
|
|
|
|
#data_sizer.AddSpacer(5)
|
|
|
|
#self.btn_add_player = wx.Button(self, label = 'Add Player')
|
|
#data_sizer.Add(self.btn_add_player)
|
|
#self.btn_add_player.Bind(wx.EVT_BUTTON, self.OnAddPlayer)
|
|
|
|
#self.btn_remove_player = wx.Button(self, label = 'Remove Player')
|
|
#data_sizer.Add(self.btn_remove_player)
|
|
#self.btn_remove_player.Bind(wx.EVT_BUTTON, self.OnRemovePlayer)
|
|
|
|
controls_sizer = wx.StaticBoxSizer(wx.StaticBox(self, label='Controls'),
|
|
wx.VERTICAL)
|
|
|
|
controls_row1 = wx.BoxSizer(wx.HORIZONTAL)
|
|
controls_row2 = wx.BoxSizer(wx.HORIZONTAL)
|
|
controls_row3 = wx.BoxSizer(wx.HORIZONTAL)
|
|
|
|
self.game_choice = wx.Choice(self)
|
|
controls_row1.Add(self.game_choice, flag=wx.EXPAND, proportion=0)
|
|
|
|
controls_row1.AddSpacer(5)
|
|
|
|
bug_number_text = wx.StaticText(self, label='#')
|
|
controls_row1.Add(bug_number_text, flag=wx.ALIGN_CENTER_VERTICAL)
|
|
|
|
self.bug_number = wx.TextCtrl(self)
|
|
controls_row1.Add(self.bug_number, flag=wx.EXPAND, proportion=1)
|
|
|
|
|
|
|
|
v_label = wx.StaticText(self, label=' v')
|
|
controls_row1.Add(v_label, flag=wx.ALIGN_CENTER_VERTICAL)
|
|
|
|
self.version_text = wx.TextCtrl(self)
|
|
controls_row1.Add(self.version_text, flag=wx.EXPAND, proportion=1)
|
|
|
|
|
|
#controls_row1.AddSpacer(5)
|
|
|
|
|
|
|
|
|
|
|
|
#extra_controls_sizer = wx.StaticBoxSizer(wx.StaticBox(self,
|
|
#label='Extra Controls'),
|
|
#wx.HORIZONTAL)
|
|
#main_sizer.Add(extra_controls_sizer, flag=wx.EXPAND |
|
|
#wx.TOP | wx.LEFT | wx.RIGHT,
|
|
#border = 3, proportion = 0)
|
|
|
|
#self.btn_copy = wx.Button(self, label = 'Copy Bug Location')
|
|
#controls_row2.Add(self.btn_copy)
|
|
#self.btn_copy.Bind(wx.EVT_BUTTON, self.OnCopy)
|
|
|
|
#controls_row2.AddSpacer(2)
|
|
|
|
#self.btn_open = wx.Button(self, label='Open Bug Folder')
|
|
#controls_row2.Add(self.btn_open)
|
|
#self.btn_open.Bind(wx.EVT_BUTTON, self.OnOpenBugFolder)
|
|
|
|
#controls_row2.AddSpacer(2)
|
|
|
|
self.clear_logs = wx.Button(self, label='Clear Logs')
|
|
controls_row2.Add(self.clear_logs)
|
|
self.clear_logs.Bind(wx.EVT_BUTTON, self.OnClearLogs)
|
|
|
|
controls_row2.AddSpacer(2)
|
|
|
|
self.kill_ragandsys = wx.Button(self, label='Kill Rag and SysTray')
|
|
controls_row2.Add(self.kill_ragandsys)
|
|
self.kill_ragandsys.Bind(wx.EVT_BUTTON, self.KillRagAndSystray)
|
|
|
|
controls_row2.AddSpacer(2)
|
|
|
|
self.start_ragandsys = wx.Button(self, label='Start Rag and SysTray')
|
|
controls_row2.Add(self.start_ragandsys)
|
|
self.start_ragandsys.Bind(wx.EVT_BUTTON, self.StartRagAndSystray)
|
|
|
|
#controls_row2.AddSpacer(30)
|
|
#This element pushes everything to the bottom/right of the sizer
|
|
controls_row2.Add((0,0),1)
|
|
|
|
self.btn_start = wx.Button(self, label='Start')
|
|
controls_row2.Add(self.btn_start)
|
|
self.btn_start.Bind(wx.EVT_BUTTON, self.OnStart)
|
|
|
|
controls_row2.AddSpacer(2)
|
|
|
|
self.btn_abort = wx.Button(self, label='Abort')
|
|
controls_row2.Add(self.btn_abort)
|
|
self.btn_abort.Bind(wx.EVT_BUTTON, self.OnAbort)
|
|
self.btn_abort.Enable(False)
|
|
|
|
|
|
self.dump_platform = wx.Choice(self, choices=['PS4','XboxOne','PC/x64','PS3','360'])
|
|
self.dump_platform.SetSelection(0)
|
|
controls_row3.Add(self.dump_platform, flag=wx.TOP, border=1)
|
|
self.dump_platform.Bind(wx.EVT_CHOICE, self.OnDumpFilterChange)
|
|
self.dump_platform.Show(False)
|
|
|
|
controls_row3.AddSpacer(5)
|
|
|
|
self.dump_exe = wx.Choice(self, choices=['Beta','BankRelease','Release','Final'])
|
|
self.dump_exe.SetSelection(0)
|
|
controls_row3.Add(self.dump_exe, flag=wx.TOP, border=1)
|
|
self.dump_exe.Show(False)
|
|
|
|
controls_row3.AddSpacer(3)
|
|
|
|
self.btn_dump = wx.Button(self, label='Grab Dump')
|
|
controls_row3.Add(self.btn_dump)
|
|
self.btn_dump.Bind(wx.EVT_BUTTON, self.OnGrabDump)
|
|
self.btn_dump.Show(False)
|
|
|
|
controls_row3.AddSpacer(2)
|
|
|
|
#self.deployed_check = wx.CheckBox(self, label='Deployed')
|
|
#controls_row3.Add(self.deployed_check, flag=wx.TOP, border=5)
|
|
|
|
#This element pushes everything to the bottom/right of the sizer
|
|
#controls_row3.Add((0,0),1)
|
|
|
|
#self.addLogs_check = wx.CheckBox(self, label='Add to folder')
|
|
#controls_row3.Add(self.addLogs_check, flag=wx.RIGHT, border=40)
|
|
|
|
controls_sizer.Add(controls_row1, flag=wx.EXPAND |
|
|
wx.TOP | wx.LEFT | wx.RIGHT)
|
|
#controls_sizer.AddSpacer(10)
|
|
controls_sizer.Add(controls_row2, flag=wx.EXPAND |
|
|
wx.TOP, border=5)
|
|
|
|
#TODO: Row that contains controls for grabbing dumps. Functionality is not yet finished for it
|
|
#controls_sizer.Add(controls_row3, flag=wx.EXPAND |
|
|
# wx.TOP, border=5)
|
|
|
|
main_sizer.Add(controls_sizer, flag=wx.EXPAND |
|
|
wx.TOP | wx.LEFT | wx.RIGHT,
|
|
border=3, proportion=0)
|
|
|
|
#self.btn_open = wx.Button(self, label='Open Bug Folder')
|
|
#extra_controls_sizer.Add(self.btn_open)
|
|
#self.btn_open.Bind(wx.EVT_BUTTON, self.OnOpenBugFolder)
|
|
|
|
#self.btn_copy = wx.Button(self, label = 'Copy Bug Location')
|
|
#extra_controls_sizer.Add(self.btn_copy)
|
|
#self.btn_copy.Bind(wx.EVT_BUTTON, self.OnCopy)
|
|
|
|
self.stage_indicator = wx.StaticText(self, label='Welcome to mpLogGrabber!',
|
|
style=wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE)
|
|
|
|
main_sizer.Add(self.stage_indicator,
|
|
flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border = 8)
|
|
|
|
|
|
file_pct_sizer = wx.StaticBoxSizer(wx.StaticBox(self,
|
|
label='File Progress'),
|
|
wx.VERTICAL)
|
|
main_sizer.Add(file_pct_sizer, flag=wx.EXPAND
|
|
| wx.TOP | wx.LEFT | wx.RIGHT, border = 3,
|
|
proportion=0)
|
|
|
|
self.file_pct = wx.Gauge(self)
|
|
file_pct_sizer.Add(self.file_pct, flag=wx.EXPAND, proportion=1)
|
|
|
|
file_pct_sizer.AddSpacer(3)
|
|
self.file_pct_sizer = file_pct_sizer
|
|
|
|
self.file_pct_text = wx.StaticText(self, label='',
|
|
style=wx.ST_NO_AUTORESIZE | wx.ALIGN_CENTER)
|
|
file_pct_sizer.Add(self.file_pct_text,
|
|
flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
|
|
|
|
|
|
total_pct_sizer = wx.StaticBoxSizer(wx.StaticBox(self,
|
|
label='Overall Progress'),
|
|
wx.VERTICAL)
|
|
self.total_pct_sizer = total_pct_sizer
|
|
main_sizer.Add(total_pct_sizer, flag=wx.EXPAND
|
|
| wx.TOP | wx.LEFT | wx.RIGHT, border = 3, proportion=0)
|
|
|
|
self.total_pct = wx.Gauge(self)
|
|
total_pct_sizer.Add(self.total_pct, flag=wx.EXPAND, proportion = 1)
|
|
|
|
total_pct_sizer.AddSpacer(3)
|
|
|
|
self.total_pct_text = wx.StaticText(self, label='',
|
|
style= wx.ST_NO_AUTORESIZE | wx.ALIGN_CENTRE)
|
|
total_pct_sizer.Add(self.total_pct_text,
|
|
flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
|
|
|
|
|
|
log_sizer = wx.StaticBoxSizer(wx.StaticBox(self,
|
|
label='Log'))
|
|
main_sizer.Add(log_sizer, flag=wx.EXPAND | wx.TOP | wx.LEFT |
|
|
wx.RIGHT | wx.BOTTOM, border = 3, proportion = 2)
|
|
self.log = wx.TextCtrl(self, style=wx.TE_MULTILINE |
|
|
wx.TE_READONLY
|
|
| wx.TE_RICH)
|
|
log_sizer.Add(self.log, flag=wx.EXPAND, proportion=1)
|
|
|
|
self.actives_dict = {}
|
|
|
|
self.RefreshData()
|
|
|
|
# Try loading settings from last time if the local settings file is present
|
|
self.sFilename = os.path.join(sys.path[0],'personal_settings.ini')
|
|
if os.path.exists(self.sFilename):
|
|
try:
|
|
self.settings_ini = codecs.open(self.sFilename, 'r','utf-8')
|
|
self.LoadUserSettings()
|
|
self.settings_ini.close()
|
|
except IOError:
|
|
print ('Error: Unable to access the local settings file, user may not have write'
|
|
'permission for "%s"' % self.sFilename)
|
|
print 'Press any key to exit'
|
|
#qa.wait_for_key()
|
|
sys.exit()
|
|
|
|
self.PopulateGamesList()
|
|
#self.PopulatePeopleList()
|
|
|
|
self.selected_filters = []
|
|
self.filters = self.LoadFilters()
|
|
|
|
#all filters on by default
|
|
#self.selected_filters = self.filters.keys()
|
|
#do this inside self.LoadFilters now, so we can turn things on and off with <default>
|
|
|
|
# This setting has to be loaded at the end as it needs other UI elements (populated people list) to be initialized first
|
|
if self.m_set_usemachinename.GetLabel().lower() in self.checked_settings:
|
|
self.m_set_usemachinename.Check(True)
|
|
self.OnUseMachineName(self.m_set_usemachinename.GetId())
|
|
elif self.m_set_usemachinename.GetLabel().lower() in self.unchecked_settings:
|
|
self.m_set_usemachinename.Check(False)
|
|
else: # This is for the default value for this option (if the user settings file is missing)
|
|
self.m_set_usemachinename.Check(False)
|
|
|
|
self.report_log = []
|
|
|
|
p_frame.Bind(wx.EVT_CLOSE, self.OnWindowClose)
|
|
|
|
#self.parent.LogEvent('###########'+platform.node())
|
|
|
|
def StartRagAndSystray(self, event):
|
|
"""Start RAG and System Tray applications from /tools_ng/ folder"""
|
|
self.LogEvent('Attempting to start RAG and Systray...')
|
|
rag_path = "X:\\gta5\\tools_ng\\bin\\rag\\rag.exe"
|
|
systray_path = "X:\\gta5\\tools_ng\\bin\\SysTrayRfs.exe"
|
|
if os.path.isfile(rag_path) and os.path.isfile(systray_path):
|
|
self.LogEvent('Starting RAG from %s' % rag_path)
|
|
self.LogEvent('Starting SysTray from %s' % systray_path)
|
|
os.startfile(os.path.dirname(os.path.realpath(__file__))+"\\scripts\\Start RAG and SYSTRAYRFS.bat")
|
|
else:
|
|
self.LogEvent('Could not find at least one of the following files:')
|
|
self.LogEvent(rag_path)
|
|
self.LogEvent(systray_path)
|
|
|
|
def KillRagAndSystray(self, event):
|
|
"""Kill RAG and System Tray processes if they're running"""
|
|
self.LogEvent('Attempting to kill RAG and Systray...')
|
|
try:
|
|
os.system("taskkill /F /IM xbwatson.exe /T")
|
|
os.system("taskkill /F /IM rag.exe /T")
|
|
os.system("taskkill /F /IM ragApp.exe /T")
|
|
os.system("taskkill /F /IM sysTrayRFS.exe /T")
|
|
except: #Gotta catch them all!
|
|
e = sys.exc_info()[1]
|
|
self.LogEvent('Error: %s' %e,'__err__')
|
|
|
|
def OnDumpFilterChange(self, event):
|
|
platform = self.dump_platform.GetStringSelection()
|
|
self.LogEvent(platform)
|
|
|
|
#if platform == '360' or platform == 'PS3' or platform == 'PC/x64':
|
|
# self.deployed_check.Enable(False)
|
|
# self.deployed_check.Hide()
|
|
#else:
|
|
# self.LogEvent('trying to show')
|
|
# self.deployed_check.Enable(True)
|
|
# self.deployed_check.Show()
|
|
|
|
def OnGrabDump(self, event):
|
|
"""Grab latest crash dump for the selected platform from appropriate location"""
|
|
platform = self.dump_platform.GetStringSelection()
|
|
exe = self.dump_exe.GetStringSelection()
|
|
exe_name = ""
|
|
valid_extensions = ['.exe','.cmp','.elf','.self','.map','.pdb','.sym','.xdb','.xex']
|
|
dump_path = []
|
|
dump_ext = ""
|
|
dump_keywoard = ""
|
|
files_to_grab = []
|
|
|
|
dump_dir = os.environ.get('RAGE_CRASHDUMP_DIR')
|
|
if dump_dir != None:
|
|
dump_path.append(dump_dir)
|
|
dump_path.append('X:\\gta5\\build\\dev_ng\\')
|
|
dump_path.append('X:\\gta5\\build\\dev\\')
|
|
if 'X:\\dumps' not in dump_path:
|
|
dump_path.append('X:\\dumps')
|
|
|
|
self.LogEvent('Trying to grab '+platform+' '+exe+' dump...')
|
|
if platform == 'PS4':
|
|
exe_name = 'game_orbis_'+exe.lower()
|
|
#sample: ps4_dump_1965903.orbisdmp
|
|
#sample: game_orbis_bankrelease.orbisdmp
|
|
dump_ext = '.orbisdmp'
|
|
elif platform == 'XboxOne':
|
|
exe_name = 'game_durango_'+exe.lower()
|
|
#sample: game_durango_beta-20140725-075054-276.dmp
|
|
#TODO: Grab exes for deployed from N:\RSGEDI\Distribution\QA_Build\gta5\dev_xbone\ with alert screen to choose between current/prebuild or choose manually
|
|
dump_ext = '.dmp'
|
|
dump_keyword = 'durango'
|
|
elif platform == 'PC/x64':
|
|
exe_name = 'game_win64_'+exe.lower()
|
|
dump_ext = '.dmp'
|
|
dump_keyword = 'x64'
|
|
elif platform == 'PS3':
|
|
exe_name = 'game_psn_'+exe.lower()+'_snc'
|
|
#sample: ps3core-1396400493-0x01000500-game_psn_bankrelease_snc.self.elf
|
|
#sample: ps3core-1396400493-0x01000500-game_psn_bankrelease_snc.self.log
|
|
dump_ext = '.elf'
|
|
dump_keyword = 'ps3core'
|
|
elif platform == '360':
|
|
exe_name = 'game_xenon_'+exe.lower()
|
|
#sample: game_xenon_bankrelease.xex.dmp
|
|
#sample: game_xenon_bankrelease.xex1.dmp
|
|
#^might be several around the same time... :(
|
|
dump_ext = '.dmp'
|
|
dump_keyword = 'xenon'
|
|
else:
|
|
self.LogEvent('Platform not recognised: %s' % platform)
|
|
return
|
|
|
|
# Find the latest dump file (by modified date) in any of the dump locations
|
|
latest_mdate = None
|
|
latest_dump = None
|
|
latest_360dumps = []
|
|
for path in dump_path:
|
|
if os.path.exists(path):
|
|
for file in os.listdir(path):
|
|
if platform == '360':
|
|
do_nothing = []
|
|
#self.LogEvent('Searching for 360 dumps...')
|
|
else:
|
|
if file.endswith(dump_ext):
|
|
if (platform != 'PS4') and (dump_keyword not in file):
|
|
continue
|
|
#self.LogEvent('Found dump: %s' % path+file)
|
|
mdate = time.ctime(os.path.getmtime(path+file))
|
|
#self.LogEvent('Last modified: %s' % mdate)
|
|
|
|
if latest_mdate == None:
|
|
latest_mdate = mdate
|
|
latest_dump = path+file
|
|
elif mdate > latest_mdate:
|
|
latest_mdate = mdate
|
|
latest_dump = path+file
|
|
|
|
#grab exe files here
|
|
filename, ext = os.path.splitext(file)
|
|
if (ext in valid_extensions) and (filename == exe_name):
|
|
self.LogEvent('Grabbing: %s' % path+file)
|
|
files_to_grab.append(path+file)
|
|
|
|
#grab the PS3 coredump .log file as well
|
|
if ext == '.log' and platform == 'PS3' and dump_keyword in filename:
|
|
self.LogEvent('Grabbing: %s' % path+file)
|
|
files_to_grab.append(path+file)
|
|
|
|
if latest_dump == None and latest_360dumps == []:
|
|
self.LogEvent('No dumps found for selected platform!')
|
|
return
|
|
elif latest_dump != None:
|
|
self.LogEvent('Grabbing dump: %s' % latest_dump)
|
|
files_to_grab.append(latest_dump)
|
|
elif latest_360dumps != []:
|
|
for dump in latest_360dumps:
|
|
self.LogEvent('Grabbing dump: %s' % dump)
|
|
files_to_grab.append(dump)
|
|
|
|
def OnClearLogs(self, event):
|
|
if self.remote_busy:
|
|
self.LogEvent('Can\'t clear logs now, because busy with remote command.')
|
|
return
|
|
|
|
confirm = wx.MessageDialog(self.GetParent(), 'Are you sure you want to clear logs?', 'Confirm', wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
|
|
#confirm.CenterOnParent()
|
|
|
|
if confirm.ShowModal() == wx.ID_YES:
|
|
mpath = 'x:\\'
|
|
folders_to_clear = []
|
|
for path in self.data[self.GetSelectedGame()]['source']:
|
|
#self.LogEvent('Clearing logs from \"%s\"'% os.path.join(mpath, path))
|
|
if os.path.exists(os.path.join(mpath, path)):
|
|
folders_to_clear.append(os.path.join(mpath, path))
|
|
|
|
# Stop the main controls from being initiated and set machines remote status to busy
|
|
self.btn_start.Enable(False)
|
|
#self.btn_abort.Enable(False)
|
|
self.EnablePlayerControls(False)
|
|
self.remote_busy = True
|
|
|
|
# Start of clear logs script
|
|
iFilesDeleted = 0
|
|
for folderName in folders_to_clear:
|
|
self.LogEvent('Attempting to delete log files from: \"%s\"' % folderName)
|
|
#os.system("PING 1.1.1.1 -n 1 -w 2000 >NUL")
|
|
dirDev=os.listdir(folderName)
|
|
for devFiles in dirDev:
|
|
if str.count(devFiles,'.log') > 0:
|
|
self.deleteLogFile(folderName+'\\'+devFiles)
|
|
iFilesDeleted = iFilesDeleted + 1
|
|
if str.count(devFiles,'.csv') > 0:
|
|
self.deleteLogFile(folderName+'\\'+devFiles)
|
|
iFilesDeleted = iFilesDeleted + 1
|
|
if str.count(devFiles,'.dmp') > 0:
|
|
self.deleteLogFile(folderName+'\\'+devFiles)
|
|
iFilesDeleted = iFilesDeleted + 1
|
|
if str.count(devFiles,'.tmp') > 0:
|
|
self.deleteLogFile(folderName+'\\'+devFiles)
|
|
iFilesDeleted = iFilesDeleted + 1
|
|
if iFilesDeleted == 0:
|
|
self.LogEvent('No log files found')
|
|
|
|
self.btn_start.Enable(True)
|
|
#self.btn_abort.Enable(False)
|
|
self.EnablePlayerControls(True)
|
|
self.remote_busy = False
|
|
|
|
def deleteLogFile(self, ftd):
|
|
try:
|
|
self.LogEvent('Deleting: \"%s\"' % ftd)
|
|
os.remove(ftd)
|
|
except: #Gotta catch them all!
|
|
e = sys.exc_info()[1]
|
|
self.LogEvent('Error: %s' %e,'__err__')
|
|
|
|
def OnWindowClose(self, event):
|
|
|
|
#socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect(("0.0.0.0", 9999))
|
|
#self.remote_server.shutdown()
|
|
|
|
# Save various settings to a local file before closing the application
|
|
self.LogEvent("Saving user settings")
|
|
checked_settings = []
|
|
unchecked_settings = []
|
|
|
|
self.WindowHeight = str(self.GetParent().GetSize().GetHeight())
|
|
self.WindowWidth = str(self.GetParent().GetSize().GetWidth())
|
|
|
|
# Note the checked/unchecked "settings"
|
|
#if self.m_set_usefilter.IsChecked():
|
|
# checked_settings.append(self.m_set_usefilter.GetLabel().lower())
|
|
#else:
|
|
# unchecked_settings.append(self.m_set_usefilter.GetLabel().lower())
|
|
|
|
#if self.m_set_zipmode.IsChecked():
|
|
# checked_settings.append(self.m_set_zipmode.GetLabel().lower())
|
|
#else:
|
|
# unchecked_settings.append(self.m_set_zipmode.GetLabel().lower())
|
|
|
|
if self.m_set_usemachinename.IsChecked():
|
|
checked_settings.append(self.m_set_usemachinename.GetLabel().lower())
|
|
else:
|
|
unchecked_settings.append(self.m_set_usemachinename.GetLabel().lower())
|
|
|
|
#if self.m_set_remote.IsChecked():
|
|
# checked_settings.append(self.m_set_remote.GetLabel().lower())
|
|
#else:
|
|
# unchecked_settings.append(self.m_set_remote.GetLabel().lower())
|
|
|
|
# Note the currently selected default group/path/folder to grab from
|
|
game_name = self.data[self.GetSelectedGame()]['name']
|
|
|
|
try:
|
|
self.settings_ini = codecs.open(self.sFilename, 'w+','utf-8')
|
|
|
|
self.settings_ini.write('[window_size]\n'+self.WindowWidth+','+self.WindowHeight+'\n')
|
|
|
|
self.settings_ini.write('\n')
|
|
self.settings_ini.write('[checked_settings]\n')
|
|
for sett in checked_settings:
|
|
self.settings_ini.write(sett+'\n')
|
|
|
|
self.settings_ini.write('\n')
|
|
self.settings_ini.write('[unchecked_settings]\n')
|
|
for sett in unchecked_settings:
|
|
self.settings_ini.write(sett+'\n')
|
|
|
|
self.settings_ini.write('\n')
|
|
self.settings_ini.write('[default_folder]\n'+game_name)
|
|
|
|
#self.settings_ini.write('\n\n')
|
|
#self.settings_ini.write('[default_d_platform]\n'+self.dump_platform.GetStringSelection())
|
|
|
|
#self.settings_ini.write('\n\n')
|
|
#self.settings_ini.write('[default_d_exe]\n'+self.dump_exe.GetStringSelection())
|
|
|
|
#if self.deployed_check.IsEnabled():
|
|
# self.settings_ini.write('\n\n')
|
|
# self.settings_ini.write('[deployed]\n'+str(self.deployed_check.IsChecked()))
|
|
|
|
if self.CustomClipboardMessage != "":
|
|
self.settings_ini.write('\n\n')
|
|
self.settings_ini.write('[bug_message_template]\n'+self.CustomClipboardMessage)
|
|
|
|
self.settings_ini.close()
|
|
except IOError:
|
|
print ('Logging(): IOError while writing to %s' % self.sFilename)
|
|
#qa.wait_for_key()
|
|
sys.exit()
|
|
|
|
# dlg = wx.MessageDialog(self,"Do you really want to close this application?","Confirm Exit", wx.OK|wx.CANCEL|wx.ICON_QUESTION)
|
|
# result = dlg.ShowModal()
|
|
# dlg.Destroy()
|
|
# if result == wx.ID_OK:
|
|
# self.Destroy()
|
|
|
|
#Propagate the close event up the hierarchy so that frame and app can close
|
|
event.Skip()
|
|
|
|
def OnToggleZipMode(self, *args, **kwargs):
|
|
global ZIP_MODE
|
|
ZIP_MODE = self.m_set_zipmode.IsChecked()
|
|
|
|
def OnStartServer(self, *args, **kwargs):
|
|
pass
|
|
|
|
def LoadUserSettings(self):
|
|
"""Load user settings from an .ini file if there is one"""
|
|
id_start = u'['
|
|
id_end = u']'
|
|
current_id = None
|
|
default_game = None
|
|
bug_message = ""
|
|
|
|
for line in self.settings_ini:
|
|
# Only clean up the text if it's not the custom bug message as we want everything as it was for it
|
|
if current_id != 'bug_message_template':
|
|
# Skip empty lines
|
|
if line in ['\n', '\r\n']:
|
|
continue
|
|
|
|
# Skip comments
|
|
if line.startswith('#'):
|
|
continue
|
|
|
|
# Strip newlines and comments
|
|
line = line.strip('\n')
|
|
if '#' in line:
|
|
line = line[:line.find('#')]
|
|
|
|
line = line.strip().lower()
|
|
|
|
if line.startswith(id_start) and line.endswith(id_end):
|
|
current_id = line.strip('[]')
|
|
continue
|
|
|
|
# Skip the line if we dont have current_id yet
|
|
if current_id is None:
|
|
continue
|
|
elif current_id == 'checked_settings':
|
|
self.checked_settings.append(line)
|
|
elif current_id == 'unchecked_settings':
|
|
self.unchecked_settings.append(line)
|
|
elif current_id == 'default_folder':
|
|
default_game = line
|
|
elif current_id == 'bug_message_template':
|
|
bug_message = bug_message+line
|
|
#elif current_id == 'default_d_platform':
|
|
# self.dump_platform.SetSelection(self.dump_platform.FindString(line))
|
|
#if line == 'ps4' or line == 'xboxone':
|
|
# self.deployed_check.Enable(True)
|
|
# self.deployed_check.Show()
|
|
#else:
|
|
# self.deployed_check.Enable(False)
|
|
# self.deployed_check.Hide()
|
|
#elif current_id == 'default_d_exe':
|
|
# self.dump_exe.SetSelection(self.dump_exe.FindString(line))
|
|
#elif current_id == 'deployed':
|
|
# if line == 'false': self.deployed_check.SetValue(False)
|
|
# elif line == 'true': self.deployed_check.SetValue(True)
|
|
|
|
self.CustomClipboardMessage = bug_message
|
|
|
|
DebugLog("[LoadUserSettings] default game - %s" % default_game)
|
|
|
|
if not default_game is None:
|
|
count = -1
|
|
for game_id in self.data['info']['games']:
|
|
count += 1
|
|
DebugLog("[LoadUserSettings] iterating through games list: current - %s" % self.data[game_id]['name'].lower())
|
|
if self.data[game_id]['name'].lower() == default_game:
|
|
#self.game_choice.SetSelection(count)
|
|
self.default_game_id = count
|
|
DebugLog("[LoadUserSettings] found a match: name - %s; id - %s" % (self.data[game_id]['name'].lower(), count))
|
|
|
|
def RefreshData(self, *args, **kwargs):
|
|
""" refresh player lists and game list """
|
|
|
|
#first, store the IDs of the currently active players
|
|
#so we can restore them later
|
|
|
|
active_player_ids = []
|
|
for idx in range(0, self.actives.GetCount()):
|
|
active_player_ids.append(self.actives.GetClientData(idx).db_id)
|
|
|
|
|
|
self.db.Connect()
|
|
cursor = self.db.cursor
|
|
cursor.execute('SELECT id, machine_name, person_name, filter_group FROM users ORDER BY person_name')
|
|
users = cursor.fetchall()
|
|
self.db.Disconnect()
|
|
|
|
|
|
self.player_objs = []
|
|
self.player_objs_dict = {}
|
|
for player in users:
|
|
self.player_objs_dict[player[0]] = Player(player, self)
|
|
self.player_objs.append(self.player_objs_dict[player[0]])
|
|
|
|
if player[0] in active_player_ids:
|
|
self.player_objs_dict[player[0]].Activate()
|
|
|
|
|
|
self.PopulateFilters()
|
|
self.PopulatePlayerLists()
|
|
#self.PopulateGamesList()
|
|
|
|
def EnablePlayerControls(self, enable):
|
|
ctrls = [self.players,
|
|
self.actives,
|
|
self.btn_add,
|
|
self.btn_remove,
|
|
self.game_choice,
|
|
self.bug_number,
|
|
self.version_text,
|
|
self.m_set_zipmode,
|
|
self.m_set_remote,
|
|
self.m_set_usefilter,
|
|
self.clear_logs]
|
|
for ctrl in ctrls:
|
|
ctrl.Enable(enable)
|
|
|
|
|
|
|
|
|
|
def SaveData(self, *args, **kwargs):
|
|
qa.WriteAllGIDs(self.data, self.datafile)
|
|
|
|
def OnAddPlayer(self, *args, **kwargs):
|
|
dlg = AddPlayerDlg(self)
|
|
if dlg.ShowModal() == wx.ID_CANCEL:
|
|
print 'cancel'
|
|
dlg.Destroy()
|
|
return False
|
|
|
|
if dlg.GetMachine() == '':
|
|
self.LogEvent('Machine name blank, player not added', '__err__')
|
|
dlg.Destroy()
|
|
return False
|
|
|
|
if dlg.GetPlayer() == '':
|
|
dlg.Destroy()
|
|
self.LogEvent('Player name blank, player not added','__err__')
|
|
return False
|
|
|
|
if dlg.GetFilter() == '':
|
|
dlg.Destroy()
|
|
self.LogEvent('Filter left blank, player not added','__err__')
|
|
return False
|
|
|
|
print 'adding player to data'
|
|
self.data['people'][dlg.GetMachine()] = dlg.GetPlayer()
|
|
new_user = {'machine_name': dlg.GetMachine(),
|
|
'person_name': dlg.GetPlayer(),
|
|
'filter_group': dlg.GetFilter()}
|
|
dlg.Destroy()
|
|
|
|
print 'inserting data'
|
|
|
|
self.db.Connect()
|
|
self.db.Insert('users', new_user)
|
|
self.db.Disconnect()
|
|
#self.SaveData()
|
|
|
|
print 'refreshing data'
|
|
self.RefreshData()
|
|
|
|
|
|
def OnModifyPlayer(self, event=None):
|
|
# Start with dialog to choose what are you searching for - player name or machine name
|
|
cdlg = TwoChoicesScreen(self)
|
|
searchFor = cdlg.ShowModal()
|
|
cdlg.Destroy()
|
|
|
|
choice_str = []
|
|
choice_objs = []
|
|
|
|
sortedMachineList = []
|
|
if searchFor == wx.ID_NO:
|
|
# Sorts the list by machine name if searching by it
|
|
sortedMachineList = sorted(self.player_objs, key=lambda x: x.machine_name.lower(), reverse=False)
|
|
|
|
self.LogEvent('Searching by machine name...')
|
|
for player in sortedMachineList:
|
|
choice_str.append('%s (%s)' % (player.machine_name,
|
|
player.person_name))
|
|
choice_objs.append(player)
|
|
|
|
elif (searchFor == wx.ID_YES) or (searchFor == wx.ID_CANCEL):
|
|
self.LogEvent('Searching by player name...')
|
|
for player in self.player_objs:
|
|
choice_str.append('%s (%s)' % (player.person_name,
|
|
player.machine_name))
|
|
choice_objs.append(player)
|
|
|
|
|
|
dlg = wx.SingleChoiceDialog(self,
|
|
'Select player to modify',
|
|
'Modify player',
|
|
choice_str)
|
|
|
|
if dlg.ShowModal() != wx.ID_OK:
|
|
dlg.Destroy()
|
|
return False
|
|
|
|
player = choice_objs[dlg.GetSelection()]
|
|
dlg.Destroy()
|
|
|
|
dlg = AddPlayerDlg(self, '__modify__')
|
|
dlg.SetPlayer(player.person_name)
|
|
dlg.SetMachine(player.machine_name)
|
|
dlg.SetFilter(player.filter_group)
|
|
|
|
if dlg.ShowModal() != wx.ID_OK:
|
|
dlg.Destroy()
|
|
return False
|
|
|
|
|
|
if dlg.GetMachine() == '':
|
|
self.LogEvent('Machine name blank, player not modified', '__err__')
|
|
dlg.Destroy()
|
|
return False
|
|
|
|
if dlg.GetPlayer() == '':
|
|
dlg.Destroy()
|
|
self.LogEvent('Player name blank, player not modified','__err__')
|
|
return False
|
|
|
|
if dlg.GetFilter() == '':
|
|
dlg.Destroy()
|
|
self.LogEvent('Filter left blank, player not modified','__err__')
|
|
return False
|
|
|
|
|
|
stmt_data = {'machine': dlg.GetMachine(),
|
|
'person': dlg.GetPlayer(),
|
|
'filter': dlg.GetFilter(),
|
|
'id': player.db_id}
|
|
|
|
dlg.Destroy()
|
|
|
|
db = self.db
|
|
|
|
try:
|
|
db.Connect()
|
|
db.cursor.execute('UPDATE `users` '
|
|
'SET `machine_name`=%(machine)s, '
|
|
' `person_name`=%(person)s, '
|
|
' `filter_group`=%(filter)s '
|
|
'WHERE id=%(id)s', stmt_data)
|
|
finally:
|
|
db.Disconnect()
|
|
|
|
self.LogEvent('Modified user %s with id: %s' % (player.person_name,
|
|
player.db_id))
|
|
|
|
self.RefreshData()
|
|
|
|
|
|
def OnRemovePlayer(self, *args, **kwargs):
|
|
cdlg = TwoChoicesScreen(self)
|
|
searchFor = cdlg.ShowModal()
|
|
cdlg.Destroy()
|
|
|
|
choice_str = []
|
|
choice_objs = []
|
|
|
|
sortedMachineList = []
|
|
if searchFor == wx.ID_NO:
|
|
# Sorts the list by machine name if searching by it
|
|
sortedMachineList = sorted(self.player_objs, key=lambda x: x.machine_name.lower(), reverse=False)
|
|
|
|
self.LogEvent('Searching by machine name...')
|
|
for player in sortedMachineList:
|
|
choice_str.append('%s (%s)' % (player.machine_name,
|
|
player.person_name))
|
|
choice_objs.append(player)
|
|
|
|
elif (searchFor == wx.ID_YES) or (searchFor == wx.ID_CANCEL):
|
|
self.LogEvent('Searching by player name...')
|
|
for player in self.player_objs:
|
|
choice_str.append('%s (%s)' % (player.person_name,
|
|
player.machine_name))
|
|
choice_objs.append(player)
|
|
|
|
dlg = wx.MultiChoiceDialog(self,
|
|
'Select players to be removed. This will affect all users of mpLogGrabber',
|
|
'Remove Players',
|
|
choice_str)
|
|
|
|
|
|
if not dlg.ShowModal() == wx.ID_OK:
|
|
dlg.Destroy()
|
|
return False
|
|
|
|
print dlg.GetSelections()
|
|
for idx in dlg.GetSelections():
|
|
self.LogEvent('Removing player "%(name)s" with id: %(db_id)s' % {'name': choice_objs[idx].person_name,
|
|
'db_id': choice_objs[idx].db_id})
|
|
self.db.Connect()
|
|
self.db.cursor.execute('DELETE '
|
|
'FROM group_members '
|
|
'WHERE user_id = %s' % (choice_objs[idx].db_id,))
|
|
|
|
self.db.cursor.execute('DELETE FROM users WHERE id = %s', [choice_objs[idx].db_id])
|
|
self.db.Disconnect()
|
|
|
|
self.RefreshData()
|
|
|
|
dlg.Destroy()
|
|
|
|
|
|
def OnStart(self, event):
|
|
if self.remote_busy:
|
|
self.LogEvent('ignoring start button click, because busy with remote command')
|
|
|
|
self.report_log = []
|
|
self.players_used = []
|
|
|
|
# Clean the busy player list and transfer size
|
|
self.busyPlayersForTransfer = []
|
|
self.totalPlayersForTransfer = self.actives.GetCount()
|
|
|
|
self.transfer_started = time.clock()
|
|
|
|
|
|
if not self.IsBugFolderValid():
|
|
return False
|
|
|
|
|
|
#if self.addLogsToFolder.IsChecked():
|
|
if self.CheckForBugFolder():
|
|
self.LogEvent('"%s" already exists, please enter a '
|
|
'different version number' % self.GetDestinationPath(),
|
|
'__err__')
|
|
self.to_transfer = []
|
|
return False
|
|
|
|
self.btn_start.Enable(False)
|
|
self.btn_abort.Enable(True)
|
|
self.EnablePlayerControls(False)
|
|
self.remote_busy = True
|
|
|
|
self.UpdateStageText('Gathering File Lists...')
|
|
|
|
player_list = []
|
|
for idx in range(0, self.actives.GetCount()):
|
|
player_list.append(self.actives.GetClientData(idx))
|
|
|
|
self.LogEvent('Checking machine activity')
|
|
for player in player_list:
|
|
if player.machine_name == socket.gethostname(): self.totalPlayersForTransfer -= 1
|
|
if not player.IsMachineOnline():
|
|
self.ReportEvent('%(name)s (%(machine)s) could not be found (no PING response)' %
|
|
{'name': player.person_name,
|
|
'machine': player.machine_name},
|
|
False)
|
|
|
|
# dont do this in remote mode
|
|
# need to do this on the client only instead
|
|
if not self.m_set_remote.IsChecked():
|
|
self.LogEvent('Checking path availability')
|
|
for player in player_list:
|
|
if player.online:
|
|
player.SetValidPaths(self.GetSelectedGame())
|
|
if len(player.valid_paths) == 0:
|
|
self.ReportEvent('%(name)s (%(machine)s) could not be found (path not shared)' %
|
|
{'name': player.person_name,
|
|
'machine': player.machine_name},
|
|
False)
|
|
|
|
self.LogEvent('Searching for files')
|
|
for player in player_list:
|
|
if len(player.valid_paths) > 0:
|
|
player.SetFileList(self.GetSelectedGame())
|
|
# ----
|
|
|
|
self.LogEvent('Creating destination folders')
|
|
self.CreatePath(self.GetDestinationPath())
|
|
if ZIP_MODE or self.m_set_remote.IsChecked():
|
|
pass
|
|
else:
|
|
for player in player_list:
|
|
if len(player.file_list) > 0:
|
|
self.CreatePath(self.GetDestinationPath(player))
|
|
|
|
self.to_transfer = []
|
|
# need to build the to_transfer list like this on client side in RC mode
|
|
if not self.m_set_remote.IsChecked():
|
|
for player in player_list:
|
|
if len(player.priority_log_file_list) > 0:
|
|
self.to_transfer.extend(player.priority_log_file_list)
|
|
|
|
if len(player.file_list) > 0:
|
|
self.to_transfer.extend(player.file_list)
|
|
|
|
if len(player.console_log_file_list) > 0:
|
|
self.to_transfer.extend(player.console_log_file_list)
|
|
|
|
self.player_list = player_list
|
|
|
|
self.total_bytes = 0
|
|
for transporter in self.to_transfer:
|
|
self.total_bytes += transporter.filesize
|
|
|
|
self.bytes_transferred = 0
|
|
|
|
self.remote_busy = False
|
|
self.UpdateStageText('Transferring Files, please do not reboot consoles')
|
|
self.DoNextTransfer()
|
|
|
|
|
|
def DoNextTransfer(self):
|
|
# self.update_timer.Start(5)
|
|
if self.abort_transfer:
|
|
self.AbortTransfer()
|
|
|
|
if len(self.to_transfer) == 0:
|
|
if self.remote_busy:
|
|
self.RCTransfersComplete()
|
|
return False
|
|
|
|
if self.m_set_remote.IsChecked():
|
|
self.remote_busy = False
|
|
self.SendInitDataToClient()
|
|
self.AllTransfersComplete()
|
|
return False
|
|
|
|
if ZIP_MODE:
|
|
self.AutoZipComplete()
|
|
return False
|
|
else:
|
|
self.AllTransfersComplete()
|
|
return False
|
|
transfer = self.to_transfer.pop(0)
|
|
if transfer.error:
|
|
wx.CallAfter(self.DoNextTransfer)
|
|
return False
|
|
|
|
if ZIP_MODE or self.remote_busy:
|
|
# Gets called on the client only
|
|
self.LogEvent('Copying "%s" to "%s"' % (transfer.source, transfer.destination))
|
|
elif not self.m_set_remote.IsChecked():
|
|
transfer.FindSafeDestination()
|
|
self.LogEvent('Host is checking file paths: source - "%s", destination - "%s"' % (transfer.source, transfer.destination))
|
|
|
|
if (not self.m_set_remote.IsChecked()) or self.remote_busy:
|
|
if not os.path.exists(transfer.source):
|
|
self.LogEvent('FAILED: Source file could not be found', '__err__')
|
|
self.ReportEvent('"%s" Copy Failed (source not found)' % transfer.source,
|
|
False)
|
|
transfer.Finished('Source file not found')
|
|
self.current_transfer = 'None'
|
|
|
|
wx.CallAfter(self.DoNextTransfer)
|
|
return False
|
|
|
|
if not self.m_set_remote.IsChecked():
|
|
if not self.remote_busy:
|
|
if self.m_set_usefilter.IsChecked():
|
|
fname = os.path.basename(transfer.source)
|
|
filters = self.GetActiveFilters()
|
|
for data in filters:
|
|
reg = data['pattern']
|
|
filter_type = data['type']
|
|
match = reg.match(fname)
|
|
#self.LogEvent('%s : %s' % (fname, match))
|
|
#self.LogEvent('%s or %s = %s' % (match, filter_type, match or filter_type))
|
|
if bool(match) == filter_type:
|
|
self.LogEvent('SKIPPED: %s matches %s' % (fname, data['description']), '__err__')
|
|
self.ReportEvent('"%s" Skipped (filter match)' % transfer.source,
|
|
False)
|
|
transfer.Finished('Skipped by %s' % data['description'])
|
|
self.current_transfer = 'None'
|
|
|
|
#trick the total progress bar
|
|
self.total_bytes -= transfer.filesize
|
|
|
|
wx.CallAfter(self.DoNextTransfer)
|
|
return False
|
|
|
|
self.current_transfer = transfer
|
|
|
|
#don't actually do the transfer if we are in RC mode
|
|
if self.m_set_remote.IsChecked() and not self.remote_busy:
|
|
wx.CallAfter(self.DoNextTransfer)
|
|
transfer.error = 'remote'
|
|
transfer.finished = True
|
|
return True
|
|
|
|
if ZIP_MODE or self.remote_busy:
|
|
#print 'starting copy to zip'
|
|
delayedresult.startWorker(self.Consumer,
|
|
self.CopyFileToZip,
|
|
wargs=(transfer,))
|
|
else:
|
|
delayedresult.startWorker(self.Consumer,
|
|
self.CopyFile,
|
|
wargs=(transfer.source,
|
|
transfer.destination))
|
|
|
|
|
|
def SendInitDataToClient(self):
|
|
player_list = []
|
|
for idx in range(0, self.actives.GetCount()):
|
|
player_list.append(self.actives.GetClientData(idx))
|
|
|
|
for player in player_list:
|
|
if player.SendInitData():
|
|
player.stage = 'Requested'
|
|
|
|
self.PopulatePlayerLists()
|
|
|
|
|
|
def AllTransfersComplete(self):
|
|
if self.transfer_aborted:
|
|
self.UpdateStageText('Log Grab ABORTED!')
|
|
self.transfer_aborted = False
|
|
return False
|
|
|
|
if not ZIP_MODE:
|
|
self.WriteTransferReport(self.GetReportFilename())
|
|
|
|
t_time = '%sh %sm %ss' % qa.ConvertTime(int(time.clock() - self.transfer_started))
|
|
if ZIP_MODE:
|
|
self.LogEvent('Compression Stage Completed in %s' % t_time)
|
|
else:
|
|
self.LogEvent('Transfer Stage Completed in %s' % t_time)
|
|
self.UpdateStageText('Log Grab Complete!')
|
|
|
|
if not ZIP_MODE and self.m_set_zipmode.IsChecked() and not self.m_set_remote.IsChecked():
|
|
self.DoAutoZips()
|
|
self.DoNextTransfer()
|
|
return False
|
|
|
|
self.btn_start.Enable(True)
|
|
self.btn_abort.Enable(False)
|
|
self.EnablePlayerControls(True)
|
|
self.remote_busy = False
|
|
#print 'transfers complete'
|
|
|
|
self.player_list = []
|
|
|
|
for idx in range(0, self.actives.GetCount()):
|
|
self.actives.GetClientData(idx).Reset()
|
|
|
|
|
|
def RCStartTransfers(self):
|
|
self.btn_start.Enable(False)
|
|
self.btn_abort.Enable(False)
|
|
self.EnablePlayerControls(False)
|
|
|
|
self.bytes_transferred = 0
|
|
self.total_bytes = 0
|
|
for transporter in self.to_transfer:
|
|
if transporter.error == None:
|
|
self.total_bytes += transporter.filesize
|
|
|
|
self.UpdateStageText('Remote Copying for %s' % self.remote_master)
|
|
self.LogEvent('files to copy: %s' % len(self.to_transfer))
|
|
self.DoNextTransfer()
|
|
|
|
|
|
def RCTransfersComplete(self):
|
|
self.btn_start.Enable(True)
|
|
self.btn_abort.Enable(False)
|
|
self.EnablePlayerControls(True)
|
|
|
|
self.remote_busy = False
|
|
|
|
try:
|
|
self.SendMessage('__complete__')
|
|
except:
|
|
self.LogEvent('Error received while sending completion signal to "%s" (%s): %s' % (self.remote_master,
|
|
self.remote_reply,
|
|
sys.exc_info()[0]),
|
|
'__err__')
|
|
|
|
self.UpdateStageText('Remote Copy for %s Complete!' % self.remote_master)
|
|
|
|
del self.remote_master
|
|
del self.remote_reply
|
|
|
|
|
|
|
|
|
|
def SendData(self, msg):
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
try:
|
|
sock.connect((self.remote_reply, 9999))
|
|
sock.sendall(msg)
|
|
finally:
|
|
sock.close()
|
|
|
|
def SendMessage(self, msg, extra={}):
|
|
data = {'time': time.time(),
|
|
'source': os.environ.get('username'),
|
|
'machine': socket.gethostname(),
|
|
'msg': msg}
|
|
|
|
data.update(extra)
|
|
|
|
json_data = json.dumps(data)
|
|
|
|
self.SendData(json_data)
|
|
|
|
|
|
|
|
|
|
def AutoZipComplete(self):
|
|
|
|
if self.transfer_aborted:
|
|
self.transfer_aborted = False
|
|
self.UpdateStageText('Log Compression ABORTED!')
|
|
return False
|
|
|
|
self.LogEvent('Compression complete, doing cleanup')
|
|
|
|
|
|
self.EraseUncompressedLogs()
|
|
|
|
self.AllTransfersComplete()
|
|
|
|
self.UpdateStageText('Log Grab and Compression Complete!')
|
|
|
|
global ZIP_MODE
|
|
ZIP_MODE = False
|
|
|
|
|
|
|
|
|
|
def EraseUncompressedLogs(self):
|
|
for player in self.player_list:
|
|
path = self.GetDestinationPath(player)
|
|
self.LogEvent('Erasing Directory %s' % path)
|
|
shutil.rmtree(path, ignore_errors=True)
|
|
|
|
|
|
|
|
def RemoteTransferComplete(self, data):
|
|
machine = data['machine']
|
|
player = self.GetPlayer(machine)
|
|
|
|
player.stage = 'Complete'
|
|
|
|
self.PopulatePlayerLists()
|
|
|
|
def RemotePlayerBusy(self, data):
|
|
machine = data['machine']
|
|
player = self.GetPlayer(machine)
|
|
|
|
player.stage = 'Busy'
|
|
|
|
self.PopulatePlayerLists()
|
|
|
|
if machine not in self.busyPlayersForTransfer:
|
|
self.busyPlayersForTransfer.append(machine)
|
|
|
|
if len(self.busyPlayersForTransfer) == self.totalPlayersForTransfer:
|
|
showBusyList = []
|
|
for idx in range(0, self.actives.GetCount()):
|
|
player_machine = self.actives.GetClientData(idx).machine_name
|
|
player_name = self.actives.GetClientData(idx).GetPlayerName()
|
|
if player_machine in self.busyPlayersForTransfer:
|
|
showBusyList.append(player_name)
|
|
|
|
dial = wx.MessageDialog(self, 'Some players were busy:\n\n'+'\n'.join(map(str, showBusyList))+'\n\nWould you like to update \'Active Players\' list to try grabbing their logs again?', 'Busy Players', wx.YES_NO)
|
|
result = dial.ShowModal()
|
|
if result == wx.ID_YES:
|
|
for idx in range(0, self.actives.GetCount()):
|
|
player_machine = self.actives.GetClientData(idx).machine_name
|
|
player_name = self.actives.GetClientData(idx).GetPlayerName()
|
|
if player_machine not in self.busyPlayersForTransfer:
|
|
self.actives.GetClientData(idx).Deactivate()
|
|
|
|
self.PopulatePlayerLists()
|
|
|
|
self.busyPlayersForTransfer = []
|
|
self.totalPlayersForTransfer = 0
|
|
|
|
|
|
|
|
def GetPlayer(self, machine):
|
|
for player in self.player_objs:
|
|
if player.machine_name.lower() == machine.lower():
|
|
return player
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def OnViewReport(self, event=None):
|
|
tr_path = os.path.join(self.GetDestinationPath(), 'TransferReport.html')
|
|
if not os.path.exists(tr_path):
|
|
self.LogEvent('Transfer report not found (%s)' % tr_path,
|
|
'__err__')
|
|
else:
|
|
os.startfile(tr_path)
|
|
|
|
|
|
def CheckForBugFolder(self):
|
|
return os.path.exists(self.GetDestinationPath())
|
|
|
|
def OnAbort(self, event):
|
|
self.abort_transfer = True
|
|
self.LogEvent('Attempting to ABORT')
|
|
|
|
def AbortTransfer(self):
|
|
if not ZIP_MODE:
|
|
self.copier.OnAbort()
|
|
self.abort_transfer = False
|
|
self.transfer_aborted = True
|
|
self.btn_start.Enable(True)
|
|
self.btn_abort.Enable(False)
|
|
self.EnablePlayerControls(True)
|
|
|
|
self.UpdateFileProgressBar(0, 0, 0)
|
|
self.LogEvent('Transfers ABORTED')
|
|
|
|
self.WriteTransferReport(self.GetReportFilename())
|
|
|
|
for player in self.player_objs:
|
|
player.Reset()
|
|
|
|
self.to_transfer = []
|
|
|
|
|
|
def DoAutoZips(self):
|
|
self.LogEvent('Starting Auto-Zip Procedure')
|
|
self.UpdateStageText('Compressing Logs, players may now reboot their consoles')
|
|
#self.player_list_zip_backup = copy.deepcopy(self.player_list)
|
|
|
|
zip_dest = '%s\\logs_%s_%s.zip' % (self.GetDestinationPath(),
|
|
self.GetBugNumber(),
|
|
self.GetVersionText())
|
|
|
|
self.LogEvent('Creating archive %s' % zip_dest)
|
|
|
|
for player in self.player_list:
|
|
zip_file_list = []
|
|
for transfer in player.priority_log_file_list:
|
|
if os.path.exists(transfer.destination):
|
|
zip_file_list.append(FileTransporter(transfer.destination,
|
|
zip_dest,
|
|
player))
|
|
player.priority_log_file_list = zip_file_list
|
|
|
|
zip_file_list = []
|
|
for transfer in player.file_list:
|
|
if os.path.exists(transfer.destination):
|
|
zip_file_list.append(FileTransporter(transfer.destination,
|
|
zip_dest,
|
|
player))
|
|
player.file_list = zip_file_list
|
|
|
|
zip_file_list = []
|
|
for transfer in player.console_log_file_list:
|
|
if os.path.exists(transfer.destination):
|
|
zip_file_list.append(FileTransporter(transfer.destination,
|
|
zip_dest,
|
|
player))
|
|
player.console_log_file_list = zip_file_list
|
|
|
|
|
|
global ZIP_MODE
|
|
ZIP_MODE = True
|
|
|
|
self.to_transfer = []
|
|
for player in self.player_list:
|
|
if len(player.priority_log_file_list) > 0:
|
|
self.to_transfer.extend(player.file_list)
|
|
|
|
for player in self.player_list:
|
|
if len(player.file_list) > 0:
|
|
self.to_transfer.extend(player.file_list)
|
|
|
|
for player in self.console_log_file_list:
|
|
if len(player.file_list) > 0:
|
|
self.to_transfer.extend(player.file_list)
|
|
|
|
self.bytes_transferred = 0
|
|
self.UpdateTotalProgressBar()
|
|
|
|
|
|
def PopulateGamesList(self):
|
|
self.game_choice.Clear()
|
|
for game_id in self.data['info']['games']:
|
|
self.game_choice.Append(self.data[game_id]['name'],
|
|
game_id)
|
|
|
|
DebugLog("[PopulateGamesList] populating with default id - %s" % self.default_game_id)
|
|
|
|
if not self.default_game_id is None:
|
|
self.game_choice.SetSelection(self.default_game_id)
|
|
else:
|
|
self.game_choice.SetSelection(0)
|
|
|
|
|
|
def PopulatePlayerLists(self):
|
|
self.players.Clear()
|
|
self.actives.Clear()
|
|
|
|
for player in self.player_objs:
|
|
if player.active:
|
|
#self.actives.Append(player.person_name, player)
|
|
self.actives.Append(player.GetActivesText(), player)
|
|
else:
|
|
if self.GetActivePlayerFilter() == None:
|
|
self.players.Append(player.GetPlayerName(), player)
|
|
elif self.GetActivePlayerFilter() == player.filter_group:
|
|
self.players.Append(player.GetPlayerName(), player)
|
|
else:
|
|
pass
|
|
|
|
def PopulateFilters(self):
|
|
db = self.db
|
|
|
|
db.Connect()
|
|
db.cursor.execute('SELECT DISTINCT `filter_group` '
|
|
'FROM `users`')
|
|
filters = db.cursor.fetchall()
|
|
db.Disconnect()
|
|
|
|
self.players_filter.Clear()
|
|
self.players_filter.Append('No Filter', None)
|
|
for item in filters:
|
|
self.players_filter.Append(item[0],
|
|
item[0])
|
|
|
|
self.players_filter.SetSelection(0)
|
|
|
|
|
|
def GetActivePlayerFilter(self):
|
|
sel = self.players_filter.GetSelection()
|
|
if self.players_filter.GetSelection() == -1:
|
|
return None
|
|
else:
|
|
return self.players_filter.GetClientData(self.players_filter.GetSelection())
|
|
|
|
def OnFilterChange(self, event=None):
|
|
self.PopulatePlayerLists()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ActivateSelectedPerson(self, event):
|
|
sel = self.players.GetSelections()
|
|
|
|
if len(sel) == 0:
|
|
return False
|
|
|
|
for selection in sel:
|
|
self.players.GetClientData(selection).Activate()
|
|
|
|
self.PopulatePlayerLists()
|
|
|
|
|
|
def DeActivateSelectedPerson(self, event):
|
|
sel = self.actives.GetSelections()
|
|
|
|
if len(sel) == 0:
|
|
return False
|
|
|
|
for selection in sel:
|
|
self.actives.GetClientData(selection).Deactivate()
|
|
|
|
self.PopulatePlayerLists()
|
|
|
|
|
|
|
|
def TestCopy(self, event):
|
|
self.OnStart(None)
|
|
## for machine, player in self.actives_dict.iteritems():
|
|
## print '%s: %s' % (player, self.IsMachineActive(machine))
|
|
|
|
## self.LogEvent('mainpath: %s' % self.GetDestinationPath())
|
|
## self.CreatePath(self.GetDestinationPath())
|
|
## for machine, player in self.actives_dict.iteritems():
|
|
## self.LogEvent('%s: %s' % (machine, self.GetDestinationPath(machine)))
|
|
## self.CreatePath(self.GetDestinationPath(machine))
|
|
|
|
|
|
|
|
## src = 'N:\\RSGEDI\\GameLogs\\GTA E1 Japan.zip'
|
|
## dst = 'd:\\test_copy.zip'
|
|
##
|
|
## self.abort_transfer = False
|
|
## self.btn_start.Enable(False)
|
|
## self.btn_abort.Enable(True)
|
|
##
|
|
#### self.copier = qa.CopyFile(src, dst)
|
|
#### self.CopyFile()
|
|
##
|
|
## self.update_timer.Start(500)
|
|
####
|
|
## delayedresult.startWorker(self.Consumer,
|
|
## self.CopyFile,
|
|
## wargs=(src, dst))
|
|
|
|
|
|
def Consumer(self, result):
|
|
#print '%s bytes copied' % result.get()
|
|
try:
|
|
self.bytes_transferred += result.get()
|
|
except IOError:
|
|
self.LogEvent('"%s": Copy Failed (IOError)' % self.current_transfer,
|
|
'__err__')
|
|
self.ReportEvent('"%s": Copy Failed (IOError)' % self.current_transfer,
|
|
False)
|
|
self.current_transfer.Finished('IOError')
|
|
else:
|
|
self.ReportEvent('"%s": Copy Succesful' % self.current_transfer,
|
|
True)
|
|
self.current_transfer.Finished()
|
|
|
|
self.current_transfer = 'None'
|
|
self.UpdateTotalProgressBar()
|
|
self.DoNextTransfer()
|
|
|
|
|
|
def CopyFileToZip(self, transfer):
|
|
if self.abort_transfer:
|
|
self.AbortTransfer()
|
|
return False
|
|
|
|
if self.remote_busy:
|
|
player_path = ''
|
|
else:
|
|
player_path = '%s_%s' % (transfer.player.machine_name,
|
|
self.GetCleanUsername(transfer.player))
|
|
|
|
arc_fname = os.path.join(player_path,
|
|
os.path.basename(transfer.source))
|
|
zip_out = zipfile.ZipFile(transfer.destination, 'a', compression=zipfile.ZIP_DEFLATED, allowZip64=True)
|
|
zip_out.write(transfer.source, arcname=arc_fname)
|
|
zip_out.close()
|
|
|
|
transfer.Finished()
|
|
|
|
return transfer.filesize
|
|
|
|
def CopyFile(self, source, destination):
|
|
self.copier = qa.CopyFile(source, destination)
|
|
copier = self.copier
|
|
#print 'starting file copy'
|
|
|
|
while not copier.file_copied:
|
|
copier.CopyNextBlock()
|
|
#self.pct_copied = copier.PercentageCopied()
|
|
self.UpdateFileProgressBar(copier.PercentageCopied(),
|
|
copier.bytes_copied,
|
|
copier.filesize)
|
|
self.UpdateTotalProgressBar(self.bytes_transferred + copier.bytes_copied)
|
|
if self.abort_transfer:
|
|
#print 'aborting transfer'
|
|
self.AbortTransfer()
|
|
return False
|
|
#print 'copy complete'
|
|
|
|
return copier.bytes_copied
|
|
|
|
def UpdateFileProgressBar(self, pct, copied, total):
|
|
self.file_pct.SetValue(pct)
|
|
pct_str = '%(pct).2f%% %(copied)s / %(total)s bytes copied' % {'pct': pct,
|
|
'copied': copied,
|
|
'total': total}
|
|
self.file_pct_text.SetLabel(pct_str)
|
|
self.file_pct_sizer.Layout()
|
|
|
|
def UpdateTotalProgressBar(self, bytes_transferred = None):
|
|
if bytes_transferred == None:
|
|
bytes_transferred = self.bytes_transferred
|
|
try:
|
|
overall_percentage = (1.0 * bytes_transferred /
|
|
self.total_bytes) * 100
|
|
except ZeroDivisionError:
|
|
overall_percentage = 100
|
|
|
|
self.total_pct.SetValue(overall_percentage)
|
|
pct_str = '%(pct).2f%% %(copied)s / %(total)s bytes copied' % {'pct': overall_percentage,
|
|
'copied': bytes_transferred,
|
|
'total': self.total_bytes}
|
|
self.total_pct_text.SetLabel(pct_str)
|
|
self.total_pct_sizer.Layout()
|
|
|
|
def UpdateStageText(self, text):
|
|
self.stage_indicator.SetLabel(text)
|
|
|
|
def LogEvent(self, msg, val='__msg__'):
|
|
in_point = self.log.GetInsertionPoint()
|
|
if val == '__msg__':
|
|
self.log.SetDefaultStyle(wx.TextAttr('BLACK', 'WHITE'))
|
|
elif val == '__err__':
|
|
self.log.SetDefaultStyle(wx.TextAttr('RED', 'WHITE'))
|
|
|
|
try:
|
|
self.log.AppendText('\n%s' % msg)
|
|
self.log.ScrollLines(1)
|
|
#self.log.ShowPosition(0)
|
|
#self.log.ShowPosition(in_point)
|
|
#self.log.Layout()
|
|
except:
|
|
pass
|
|
|
|
qa.logging(msg)
|
|
|
|
def ReportEvent(self, msg, val):
|
|
self.report_log.append([msg, val])
|
|
|
|
def IsMachineActive(self, machine):
|
|
self.LogEvent('Sending PING request to %s' % machine)
|
|
if int(os.system('PING %s -n 1' % machine)) == 0:
|
|
self.LogEvent('%s responded to PING' % machine)
|
|
return True
|
|
else:
|
|
self.LogEvent('%s did not respond to PING' % machine,
|
|
'__err__')
|
|
return False
|
|
|
|
##
|
|
## def IsPathAvailable(self, machine, path):
|
|
## if os.path.exists(os.path.join(machine, path)):
|
|
## return True
|
|
|
|
def GetSelectedGame(self):
|
|
sel = self.game_choice.GetSelection()
|
|
|
|
if sel == -1:
|
|
return None
|
|
|
|
game_id = self.game_choice.GetClientData(sel)
|
|
|
|
return game_id
|
|
|
|
def GetFullPath(self, machine):
|
|
mpath = '\\\\%s' % machine
|
|
game = self.GetSelectedGame()
|
|
if game == None:
|
|
return mpath
|
|
|
|
fpath = os.path.join(mpath,
|
|
self.data[game]['source'])
|
|
return fpath
|
|
|
|
def GetFullPaths(self, machine):
|
|
#print 'GetFullPaths\n========='
|
|
mpath = '\\\\%s' % machine
|
|
game = self.GetSelectedGame()
|
|
if game == None:
|
|
return mpath
|
|
|
|
paths = []
|
|
for path in self.data[game]['source']:
|
|
print path
|
|
paths.append(os.path.join(mpath,
|
|
path))
|
|
|
|
return paths
|
|
|
|
def GetFileList(self, path):
|
|
dirlist = os.listdir(path)
|
|
extensions = self.data[self.GetSelectedGame()]['filetypes']
|
|
valid_files = []
|
|
priority_logs = []
|
|
console_logs = []
|
|
|
|
#prioritize prediction and network logs because they get overwritten quickly!
|
|
for fname in dirlist:
|
|
for ext in extensions:
|
|
if fname.endswith(ext):
|
|
if fname.lower().find("prediction") != -1:
|
|
priority_logs.append(os.path.join(path, fname))
|
|
elif fname.lower().find("console") != -1:
|
|
console_logs.append(os.path.join(path, fname))
|
|
else:
|
|
valid_files.append(os.path.join(path, fname))
|
|
continue
|
|
|
|
valid_files[0:0] = priority_logs
|
|
valid_files.extend(console_logs)
|
|
|
|
return valid_files
|
|
|
|
#def GetFilters(self):
|
|
#filters = []
|
|
#with open('filters.txt', 'r') as f:
|
|
#for line in f:
|
|
#pattern = line.strip('\n')
|
|
#if len(pattern) == 0:
|
|
#continue
|
|
#filters.append(re.compile(pattern))
|
|
|
|
#return filters
|
|
|
|
def OnUseMachineName(self, id):
|
|
"""Sets the global variable MACHINE_NAME_MODE to true or false.
|
|
|
|
Global variable necessary for the Player class to know if the settings item is checked.
|
|
"""
|
|
global MACHINE_NAME_MODE
|
|
|
|
if MACHINE_NAME_MODE:
|
|
MACHINE_NAME_MODE = False
|
|
self.m_set_usemachinename.Check(False)
|
|
self.RefreshData()
|
|
else:
|
|
MACHINE_NAME_MODE = True
|
|
self.m_set_usemachinename.Check(True)
|
|
self.RefreshData()
|
|
|
|
def OnSetCustomMessage(self, *args, **kwargs):
|
|
"""Settings option to set a custom message to be copied together with bug location (self.OnCopyCustom())"""
|
|
self.CustomMessageBugPlaceholder = 'X:\\I_AM_A\\PLACEHOLDER\\PATH\\'
|
|
default_msg = '"%s"' % self.CustomMessageBugPlaceholder
|
|
if self.CustomClipboardMessage != "":
|
|
default_msg = self.CustomClipboardMessage
|
|
dialog = wx.TextEntryDialog(self,"Enter text to be copied together with bug location. Placeholder path will\nbe replaced with correct one when you copy location.\n\nPress Cancel to reset the template.","Comment Template", default_msg, style=wx.TE_MULTILINE|wx.OK|wx.CANCEL|wx.TE_NOHIDESEL)
|
|
dialog.SetSize((380,300))
|
|
dialog.CenterOnParent()
|
|
result = dialog.ShowModal()
|
|
if result == wx.ID_OK:
|
|
self.CustomClipboardMessage = dialog.GetValue()
|
|
elif result == wx.ID_CANCEL:
|
|
self.CustomClipboardMessage = ""
|
|
|
|
def OnSetFilters(self, *keys, **kwargs):
|
|
filter_list = []
|
|
for key, filter_data in self.filters.iteritems():
|
|
filter_list.append(filter_data['description'])
|
|
|
|
selections = []
|
|
for key in self.selected_filters:
|
|
selections.append(self.filters.keys().index(key))
|
|
|
|
dlg = wx.MultiChoiceDialog(self,
|
|
'Select filters to stop certain files from being transferred',
|
|
'Filter Selection',
|
|
filter_list)
|
|
|
|
dlg.SetSelections(selections)
|
|
|
|
dlg_ret = dlg.ShowModal()
|
|
|
|
if dlg_ret == wx.ID_CANCEL:
|
|
dlg.Destroy()
|
|
return False
|
|
|
|
self.selected_filters = []
|
|
for sel_id in dlg.GetSelections():
|
|
self.selected_filters.append(self.filters.keys()[sel_id])
|
|
|
|
return True
|
|
|
|
def LoadFilters(self):
|
|
filters = {}
|
|
with open('data\\filters.xml', 'r') as f:
|
|
tree = xml.etree.ElementTree.fromstring(f.read())
|
|
|
|
for ff in tree.findall('filter'):
|
|
filter_id = ff.attrib['id']
|
|
f_dict = {'pattern': re.compile(ff.find('pattern').text),
|
|
'description': ff.find('description').text}
|
|
|
|
filter_type = ff.find('type').text
|
|
|
|
filter_types = {'exclude': True,
|
|
'include': False}
|
|
|
|
f_dict['type'] = filter_types[filter_type]
|
|
|
|
|
|
filters[filter_id] = f_dict
|
|
|
|
if ff.find('default').text.lower() == 'on':
|
|
self.selected_filters.append(filter_id)
|
|
|
|
return filters
|
|
|
|
def GetActiveFilters(self):
|
|
filters = []
|
|
if self.m_set_usefilter.IsChecked():
|
|
for key in self.selected_filters:
|
|
filters.append(self.filters[key])
|
|
|
|
return filters
|
|
|
|
def GetBugNumber(self):
|
|
return self.bug_number.GetValue().strip()
|
|
|
|
def GetVersionText(self):
|
|
return self.version_text.GetValue().strip()
|
|
|
|
def GetMainPath(self):
|
|
return os.path.join(self.GetBugNumber(),
|
|
self.GetVersionText())
|
|
|
|
def addressInNetwork(self, ip, net_n_bits):
|
|
ipaddr = struct.unpack('<L', socket.inet_aton(ip))[0]
|
|
net, bits = net_n_bits.split('/')
|
|
netaddr = struct.unpack('<L', socket.inet_aton(net))[0]
|
|
netmask = ((1L << int(bits)) - 1)
|
|
return ipaddr & netmask == netaddr & netmask
|
|
|
|
def GetDestinationPath(self, player=None):
|
|
"""Returns bug folder location.
|
|
|
|
Destination path is game_id[folder] + bug number"""
|
|
localMachineIPAddrStr = ([(s.connect(('8.8.8.8', 80)), s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1])
|
|
|
|
|
|
backUpNetworkList = (("10.10.0.0/16","rsgedi"),
|
|
("10.11.0.0/16","rsgedi"),
|
|
("10.8.0.0/16","rsglds"),
|
|
("10.12.0.0/16","rsglin"),
|
|
("10.13.0.0/16","rsgldn"),
|
|
("10.0.0.0/16","rsgsan"),
|
|
("10.24.0.0/16","rsgnwe"),
|
|
("10.2.0.0/16","rsgtor"),
|
|
("10.1.0.0/16","rsgnyc"),
|
|
("10.99.172.0/24","technicolor"),
|
|
("10.36.0.0/16","rsglic"))
|
|
|
|
networkList = []
|
|
|
|
toolsPath = os.environ.get('RS_TOOLSROOT')
|
|
if not toolsPath == None:
|
|
toolsPath = toolsPath + '\\'
|
|
configPath = os.path.join(toolsPath,'etc\\globals\\studios.meta')
|
|
|
|
tree = xml.etree.ElementTree.parse(configPath)
|
|
root = tree.getroot()
|
|
|
|
for studios in root.getiterator('Studios'):
|
|
for studio in studios:
|
|
studioNetDrive = studio.find('NetworkDrive').text
|
|
studionSubnet = studio.find('SubnetMask').text
|
|
|
|
if not studioNetDrive == None:
|
|
drive,stdName = studioNetDrive.split('\\')
|
|
stdName = stdName.lower()
|
|
networkList.append((studionSubnet,stdName))
|
|
else:
|
|
self.LogEvent("Your default tools directory (in RS_TOOLSROOT environmental variable) is not set up correctly. Try reinstalling project tools!",'__err__')
|
|
|
|
selectedFolder = ""
|
|
#self.LogEvent("Local machine's IP address - %s" % localMachineIPAddrStr)
|
|
for (subnet,folder) in networkList:
|
|
if self.addressInNetwork(localMachineIPAddrStr,subnet):
|
|
selectedFolder = folder
|
|
if selectedFolder == "":
|
|
self.LogEvent("Could not find appropriate studio destination folder from your IP address! Check if <studios> information in $RS_TOOLSROOT$\\config.xml is set up correctly and/or try grabbing latest on that file! Will try using hardcoded subnet values...",'__err__')
|
|
|
|
for (subnet,folder) in backUpNetworkList:
|
|
if self.addressInNetwork(localMachineIPAddrStr,subnet):
|
|
selectedFolder = folder
|
|
if selectedFolder == "":
|
|
self.LogEvent("Could not find appropriate studio destination folder even from a hardcoded list! Double check that $RS_TOOLSROOT$\\config.xml is set up correctly and/or try grabbing latest on that file! ",'__err__')
|
|
|
|
settingsFolder = self.data[self.GetSelectedGame()]['folder'];
|
|
studio_path = os.path.join(settingsFolder,
|
|
selectedFolder)
|
|
|
|
if not os.access(studio_path,os.F_OK):
|
|
self.LogEvent("Chosen studio log path (\"%s\") does not exist!!!" % studio_path,'__err__')
|
|
elif not os.access(studio_path, os.W_OK | os.X_OK):
|
|
self.LogEvent("Chosen studio log path (\"%s\") exists, but you don't have write access to it!" % studio_path,'__err__')
|
|
|
|
main_path = os.path.join(studio_path,
|
|
self.GetMainPath())
|
|
|
|
#self.LogEvent("Full destination path for logs - \"%s\"" % main_path)
|
|
|
|
if player == None:
|
|
return main_path
|
|
else:
|
|
player_path = '%s_%s' % (player.machine_name,
|
|
self.GetCleanUsername(player))
|
|
return os.path.join(main_path,
|
|
player_path)
|
|
|
|
|
|
def GetCleanUsername(self, player):
|
|
""" turn a username into something we can add to a folder path """
|
|
name = player.person_name.lower()
|
|
|
|
name = name.replace(' ', '.')
|
|
|
|
dodgy_chars = ['\\',
|
|
'/',
|
|
':',
|
|
'*',
|
|
'?',
|
|
'"',
|
|
'<',
|
|
'>',
|
|
'|']
|
|
|
|
for char in dodgy_chars:
|
|
name = name.replace(char, '-')
|
|
|
|
return name
|
|
|
|
|
|
|
|
def IsBugFolderValid(self):
|
|
if self.GetBugNumber() == '':
|
|
self.LogEvent('Bug number is blank', '__err__')
|
|
is_valid = False
|
|
|
|
if self.GetVersionText() == '':
|
|
self.LogEvent('Version text is blank', '__err__')
|
|
is_valid = False
|
|
|
|
|
|
|
|
try:
|
|
return is_valid
|
|
except UnboundLocalError:
|
|
return True
|
|
|
|
|
|
|
|
def GetReportFilename(self):
|
|
fname = 'TransferReport.html'
|
|
return os.path.join(self.GetDestinationPath(), fname)
|
|
|
|
def OnOpenBugFolder(self, *args, **kwargs):
|
|
self.LogEvent('Opening "%s"' % self.GetDestinationPath())
|
|
if os.path.exists(self.GetDestinationPath()):
|
|
subprocess.Popen('explorer %s' % self.GetDestinationPath())
|
|
else:
|
|
self.LogEvent('Path does not exist, cannot open',
|
|
'__err__')
|
|
def OnCopy(self, *args, **kwargs):
|
|
"""Copies bug location to the clipboard"""
|
|
clip_data = wx.TextDataObject()
|
|
clip_data.SetText('"%s"' % self.GetDestinationPath())
|
|
|
|
if wx.TheClipboard.Open():
|
|
self.LogEvent('Copying "%s" to clipboard' %
|
|
self.GetDestinationPath())
|
|
wx.TheClipboard.SetData(clip_data)
|
|
wx.TheClipboard.Close()
|
|
wx.TheClipboard.Flush()
|
|
|
|
def OnCopyCustom(self, *args, **kwargs):
|
|
"""Copies bug location and the custom message (from self.OnSetCustomMessage()) if there is one to clipboard"""
|
|
clip_data = wx.TextDataObject()
|
|
#Replace the placeholder with correct bug folder path
|
|
bug_path = self.GetDestinationPath()
|
|
clipboard_message = self.CustomClipboardMessage.replace(self.CustomMessageBugPlaceholder, bug_path)
|
|
|
|
if self.CustomClipboardMessage == "":
|
|
clip_data.SetText('"%s"' % self.GetDestinationPath())
|
|
else:
|
|
clip_data.SetText('%s' % clipboard_message)
|
|
|
|
if wx.TheClipboard.Open():
|
|
if self.CustomClipboardMessage == "":
|
|
self.LogEvent('Copying %s to clipboard' %
|
|
clip_data.GetText())
|
|
else:
|
|
self.LogEvent('Copying: " %s " to clipboard' %
|
|
clip_data.GetText())
|
|
wx.TheClipboard.SetData(clip_data)
|
|
wx.TheClipboard.Close()
|
|
wx.TheClipboard.Flush()
|
|
|
|
def CreatePath(self, path):
|
|
if not os.path.exists(path):
|
|
self.LogEvent('Creating path "%s"' % path)
|
|
os.makedirs(path)
|
|
|
|
|
|
def GetSafeDestinations(self, transfers):
|
|
""" take the list of transfers and ensure that no files will be
|
|
overwritten, return an updated list of transfers with decorated
|
|
filenames """
|
|
new_dests = []
|
|
new_src = [x[0] for x in transfers]
|
|
for src, dest in transfers:
|
|
if dest not in new_dests:
|
|
new_dests.append(dest)
|
|
else:
|
|
i = 1
|
|
new_dest = self.DecorateFilename(dest, i)
|
|
while new_dest in new_dests:
|
|
i += 1
|
|
new_dest = self.DecorateFilename(dest, i)
|
|
new_dests.append(new_dest)
|
|
|
|
return zip(new_src, new_dests)
|
|
|
|
|
|
|
|
def DecorateFilename(self, fname, num):
|
|
template = '%(filename)s(%(num)s)%(ext)s'
|
|
data = {}
|
|
|
|
data['filename'], data['ext'] = os.path.splitext(fname)
|
|
data['num'] = num
|
|
|
|
return template % data
|
|
|
|
|
|
def WriteTransferReport(self, path):
|
|
out_file = open(path, 'w')
|
|
|
|
big_template = """
|
|
<html>
|
|
<title>mpLogGrabber Transfer Report (%(bugnumber)s)</title>
|
|
<body bgcolor=#bafeff>
|
|
<font size=+3><b>mpLogGrabber Transfer Report</b></font><br>
|
|
<hr>
|
|
<font size=-1>Transfer by %(username)s on %(current_date_time)s</font><br>
|
|
<font size=-1>Destination: %(destination_path)s</font><br><br>
|
|
<font size=+1><b>Players</b></font><br>
|
|
<table border=1>%(player_list)s</table><br><br>
|
|
<font size=+1><b>Files</b></font><br>
|
|
<table border=1>%(file_list)s</table>
|
|
<br><br><br><br>
|
|
<font size=-3 align=center>File generated by mpLogGrabber</font>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
report_dict = {
|
|
'bugnumber': self.GetBugNumber(),
|
|
'username': os.environ.get('username'),
|
|
'current_date_time': time.strftime('%Y-%m-%d %H:%M:%S'),
|
|
'destination_path': self.GetDestinationPath()
|
|
}
|
|
|
|
|
|
player_temp = """
|
|
<tr>
|
|
<td>%(playername)s</td>
|
|
<td>%(machinename)s</td>
|
|
<td bgcolor=%(stat_col)s>%(status)s</td>
|
|
</tr>
|
|
"""
|
|
trans_temp = """
|
|
<tr>
|
|
<td>%(source)s</td>
|
|
<td>%(destination)s</td>
|
|
<td bgcolor=%(stat_col)s>%(status)s</td>
|
|
</tr>
|
|
"""
|
|
|
|
player_table = ''
|
|
file_table = ''
|
|
|
|
for player in self.player_list:
|
|
if player.online == False:
|
|
player_table += player_temp % {'playername': player.person_name,
|
|
'machinename': player.machine_name,
|
|
'stat_col': 'red',
|
|
'status': 'No PING response'}
|
|
elif len(player.file_list) == 0:
|
|
player_table += player_temp % {'playername': player.person_name,
|
|
'machinename': player.machine_name,
|
|
'stat_col': 'red',
|
|
'status': 'No files found'}
|
|
else:
|
|
player_table += player_temp % {'playername': player.person_name,
|
|
'machinename': player.machine_name,
|
|
'stat_col': '#6fbf59',
|
|
'status': '%s files found' % len(player.file_list)}
|
|
|
|
for transfer in player.file_list:
|
|
trans_dict = {'source': transfer.source,
|
|
'destination': transfer.destination}
|
|
|
|
if transfer.finished == False:
|
|
trans_dict['stat_col'] = 'red'
|
|
trans_dict['status'] = 'Aborted'
|
|
elif transfer.error == None:
|
|
trans_dict['stat_col'] = '#6fbf59'
|
|
trans_dict['status'] = 'OK'
|
|
elif transfer.error == 'remote':
|
|
trans_dict['stat_col'] = '#6fbf59'
|
|
trans_dict['status'] = 'Remote Request Sent'
|
|
else:
|
|
trans_dict['stat_col'] = 'red'
|
|
trans_dict['status'] = transfer.error
|
|
|
|
file_table += trans_temp % trans_dict
|
|
|
|
|
|
report_dict['player_list'] = player_table
|
|
report_dict['file_list'] = file_table
|
|
|
|
|
|
|
|
#for item in report:
|
|
#if item[1]:
|
|
#template = true_template
|
|
#else:
|
|
#template = false_template
|
|
|
|
#file_list_html += template % {'report': item[0]}
|
|
|
|
#report_dict['file_list'] = file_list_html
|
|
|
|
out_file.write(big_template % report_dict)
|
|
out_file.close()
|
|
|
|
|
|
def OnNewGroup(self, event=None):
|
|
|
|
dlg = wx.TextEntryDialog(self,
|
|
'Please enter a name for this group')
|
|
|
|
if dlg.ShowModal() != wx.ID_OK:
|
|
dlg.Destroy()
|
|
return False
|
|
|
|
group_id = self.AddNewGroup(dlg.GetValue())
|
|
self.LogEvent('New group \'%s\' created' % dlg.GetValue())
|
|
group_name = dlg.GetValue()
|
|
dlg.Destroy()
|
|
|
|
|
|
for idx in range(0, self.actives.GetCount()):
|
|
self.AddGroupMember(group_id,
|
|
self.actives.GetClientData(idx).db_id)
|
|
self.LogEvent('Player \'%s\' added to group \'%s\'' % (self.actives.GetClientData(idx).person_name,
|
|
group_name))
|
|
|
|
def AddNewGroup(self, name):
|
|
db = self.db
|
|
|
|
data = {'name': name,
|
|
'owner': os.environ.get('username')}
|
|
|
|
db.Connect()
|
|
db.Insert('groups', data)
|
|
group_id = db.cursor.execute('SELECT `id` '
|
|
'FROM `groups` '
|
|
'WHERE `name`=%s AND `owner`=%s',
|
|
(data['name'], data['owner']))
|
|
|
|
new_id = db.cursor.fetchone()
|
|
db.Disconnect()
|
|
|
|
return new_id[0]
|
|
|
|
def AddGroupMember(self, group_id, user_id):
|
|
db = self.db
|
|
|
|
data = {'group_id': group_id,
|
|
'user_id': user_id}
|
|
|
|
db.Connect()
|
|
db.Insert('group_members', data)
|
|
db.Disconnect()
|
|
|
|
def GetGroupMembers(self, group_id):
|
|
db = self.db
|
|
|
|
db.Connect()
|
|
db.cursor.execute('SELECT user_id '
|
|
'FROM group_members '
|
|
'WHERE group_id=%s',
|
|
group_id)
|
|
members = db.cursor.fetchall()
|
|
db.Disconnect()
|
|
|
|
return members
|
|
|
|
def GetGroupList(self):
|
|
db = self.db
|
|
|
|
db.Connect()
|
|
db.cursor.execute('SELECT `id`, `name` FROM groups ORDER BY name')
|
|
groups = db.cursor.fetchall()
|
|
db.Disconnect()
|
|
|
|
return groups
|
|
|
|
def OnAddGroup(self, event=None):
|
|
groups = self.GetGroupList()
|
|
|
|
|
|
dlg = wx.MultiChoiceDialog(self,
|
|
'Please select a group to add to current player list',
|
|
'Add Group To Current List',
|
|
[x[1] for x in groups])
|
|
|
|
if dlg.ShowModal() != wx.ID_OK:
|
|
dlg.Destroy()
|
|
return False
|
|
|
|
group_ids = []
|
|
for id in dlg.GetSelections():
|
|
group_id = groups[id][0]
|
|
group_ids.append(group_id)
|
|
group_name = groups[id][1]
|
|
self.LogEvent('Group \'%s\' loaded' % group_name)
|
|
|
|
dlg.Destroy()
|
|
|
|
self.AddGroups(group_ids)
|
|
|
|
def AddGroups(self, group_ids):
|
|
db = self.db
|
|
|
|
db.Connect()
|
|
group_members = []
|
|
for group_id in group_ids:
|
|
db.cursor.execute('SELECT user_id '
|
|
'FROM group_members '
|
|
'WHERE group_id=%s',
|
|
(group_id,))
|
|
temp_members = db.cursor.fetchall()
|
|
temp_members = [x[0] for x in temp_members]
|
|
group_members += temp_members
|
|
db.Disconnect()
|
|
|
|
for player in self.player_objs:
|
|
if player.active == True:
|
|
continue
|
|
|
|
if player.db_id in group_members:
|
|
player.Activate()
|
|
|
|
self.PopulatePlayerLists()
|
|
|
|
def OnLoadGroup(self, event=None):
|
|
groups = self.GetGroupList()
|
|
|
|
|
|
dlg = wx.MultiChoiceDialog(self,
|
|
'Please select a group to load',
|
|
'Load Group',
|
|
[x[1] for x in groups])
|
|
|
|
if dlg.ShowModal() != wx.ID_OK:
|
|
dlg.Destroy()
|
|
return False
|
|
|
|
group_ids = []
|
|
for id in dlg.GetSelections():
|
|
group_id = groups[id][0]
|
|
group_ids.append(group_id)
|
|
group_name = groups[id][1]
|
|
self.LogEvent('Group \'%s\' loaded' % group_name)
|
|
|
|
dlg.Destroy()
|
|
|
|
self.LoadGroups(group_ids)
|
|
|
|
|
|
def LoadGroups(self, group_ids):
|
|
db = self.db
|
|
|
|
db.Connect()
|
|
group_members = []
|
|
for group_id in group_ids:
|
|
self.LogEvent("Trying to query group %s" % group_id)
|
|
db.cursor.execute('SELECT user_id '
|
|
'FROM group_members '
|
|
'WHERE group_id=%s',
|
|
(group_id,))
|
|
temp_members = db.cursor.fetchall()
|
|
temp_members = [x[0] for x in temp_members]
|
|
group_members += temp_members
|
|
db.Disconnect()
|
|
|
|
for player in self.player_objs:
|
|
if player.db_id in group_members:
|
|
player.Activate()
|
|
else:
|
|
player.Deactivate()
|
|
|
|
self.PopulatePlayerLists()
|
|
|
|
|
|
def OnDeleteGroup(self, event=None):
|
|
groups = self.GetGroupList()
|
|
|
|
dlg = wx.SingleChoiceDialog(self,
|
|
'Please select a group to delete',
|
|
'Delete Group',
|
|
[x[1] for x in groups])
|
|
|
|
if dlg.ShowModal() != wx.ID_OK:
|
|
dlg.Destroy()
|
|
return False
|
|
|
|
to_delete = groups[dlg.GetSelection()]
|
|
|
|
dlg.Destroy()
|
|
|
|
dlg = wx.MessageDialog(self,
|
|
'Are you sure you want to delete the group %s?'
|
|
' Note: Only the group will be deleted, not the '
|
|
'members of the group. ' % to_delete[1],
|
|
'Confirm Group Deletion',
|
|
wx.YES_NO | wx.ICON_QUESTION)
|
|
|
|
if dlg.ShowModal() == wx.ID_NO:
|
|
dlg.Destroy()
|
|
return False
|
|
|
|
self.DeleteGroup(to_delete[0])
|
|
self.LogEvent('Deleting group \'%s\' with id: \'%s\'' % (to_delete[1],
|
|
to_delete[0]))
|
|
|
|
|
|
def DeleteGroup(self, group_id):
|
|
db = self.db
|
|
db.Connect()
|
|
|
|
#first delete any entries in group_members
|
|
db.cursor.execute('DELETE '
|
|
'FROM `group_members` '
|
|
'WHERE `group_members`.`group_id`=%s',
|
|
(group_id,))
|
|
|
|
#then delete the group from groups
|
|
db.cursor.execute('DELETE '
|
|
'FROM `groups` '
|
|
'WHERE `groups`.`id`=%s',
|
|
(group_id,))
|
|
|
|
db.Disconnect()
|
|
|
|
|
|
|
|
def GetGroupName(self, group_id):
|
|
db = self.db
|
|
db.Connect()
|
|
|
|
db.cursor.execute('SELECT `name` '
|
|
'FROM groups '
|
|
'WHERE `id`=%s',
|
|
(group_id,))
|
|
|
|
name = db.cursor.fetchall()
|
|
|
|
db.Disconnect()
|
|
|
|
#this will cause indexerror if group_id is not valid,
|
|
#catch indexerror further up the chain
|
|
name_final = name[0]
|
|
return name[0][0]
|
|
|
|
|
|
|
|
def OnReplaceGroup(self, event):
|
|
groups = self.GetGroupList()
|
|
|
|
dlg = wx.SingleChoiceDialog(self,
|
|
'Please select a group to replace with the current player list',
|
|
'Replace Group',
|
|
[x[1] for x in groups])
|
|
|
|
if dlg.ShowModal() != wx.ID_OK:
|
|
dlg.Destroy()
|
|
return False
|
|
|
|
to_replace = groups[dlg.GetSelection()]
|
|
|
|
dlg.Destroy()
|
|
|
|
#are you SURE?
|
|
|
|
dlg = wx.MessageDialog(self,
|
|
'Are you sure you want to replace the members of '
|
|
'"%s" with your current active players list?' % to_replace[1],
|
|
'Confirm Group Replace',
|
|
wx.YES_NO | wx.ICON_QUESTION)
|
|
|
|
if dlg.ShowModal() == wx.ID_NO:
|
|
dlg.Destroy()
|
|
return False
|
|
|
|
|
|
self.ReplaceGroup(to_replace[0])
|
|
|
|
|
|
|
|
def ReplaceGroup(self, group_id):
|
|
#get the group name
|
|
group_name = self.GetGroupName(group_id)
|
|
|
|
db = self.db
|
|
db.Connect()
|
|
|
|
#delete all old groupmembers using this ID
|
|
db.cursor.execute('DELETE '
|
|
'FROM `group_members` '
|
|
'WHERE `group_members`.`group_id`=%s',
|
|
(group_id,))
|
|
|
|
db.Disconnect()
|
|
|
|
#insert new group members
|
|
for idx in range(0, self.actives.GetCount()):
|
|
self.AddGroupMember(group_id,
|
|
self.actives.GetClientData(idx).db_id)
|
|
self.LogEvent('Player \'%s\' added to group \'%s\'' % (self.actives.GetClientData(idx).person_name,
|
|
group_name))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TwoChoicesScreen(wx.Dialog):
|
|
def __init__(self, parent, *args, **kw):
|
|
wx.Dialog.__init__(self, parent, title='Sort By', *args, **kw)
|
|
|
|
#pnl = wx.Panel(self)
|
|
hsizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
btn1 = wx.Button(self,wx.ID_ANY,'Player Names')
|
|
btn2 = wx.Button(self,wx.ID_ANY,'Machine Names')
|
|
hsizer.Add(btn1,flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
|
|
hsizer.Add(btn2,flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
|
|
|
|
self.Bind(wx.EVT_BUTTON, self.OnSearchPlayerName, id=btn1.GetId())
|
|
self.Bind(wx.EVT_BUTTON, self.OnSearchMachineName, id=btn2.GetId())
|
|
|
|
self.SetSizerAndFit(hsizer)
|
|
self.CenterOnParent()
|
|
|
|
def OnSearchPlayerName(self, event):
|
|
self.EndModal(wx.ID_YES)
|
|
|
|
def OnSearchMachineName(self, event):
|
|
self.EndModal(wx.ID_NO)
|
|
|
|
|
|
class ReportScreen(wx.Dialog):
|
|
def __init__(self, parent, report_log, *args, **kwargs):
|
|
wx.Dialog.__init__(self, parent,
|
|
title='Transfer Report',
|
|
style= wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
|
|
*args, **kwargs)
|
|
|
|
main_sizer = wx.BoxSizer(wx.VERTICAL)
|
|
self.SetSizer(main_sizer)
|
|
|
|
self.text = wx.TextCtrl(self, style = wx.TE_MULTILINE
|
|
| wx.TE_READONLY
|
|
| wx.TE_RICH)
|
|
main_sizer.Add(self.text, flag=wx.EXPAND | wx.ALL, border = 3,
|
|
proportion=1)
|
|
|
|
self.btn_close = wx.Button(self, label='Close')
|
|
main_sizer.Add(self.btn_close, flag=wx.ALIGN_RIGHT)
|
|
self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
|
|
|
|
self.FillReport(report_log)
|
|
self.CenterOnParent()
|
|
|
|
|
|
def OnClose(self, *args, **kwargs):
|
|
self.Close()
|
|
|
|
def FillReport(self, rep):
|
|
for msg, val in rep:
|
|
if val:
|
|
self.text.SetDefaultStyle(wx.TextAttr('GREEN', 'WHITE'))
|
|
else:
|
|
self.text.SetDefaultStyle(wx.TextAttr('RED', 'WHITE'))
|
|
|
|
self.text.AppendText('%s\n' % msg)
|
|
self.text.SetScrollPos(wx.HORIZONTAL, 0)
|
|
self.text.SetInsertionPoint(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AddPlayerDlg(wx.Dialog):
|
|
def __init__(self, parent, mode='__add__', *args, **kwargs):
|
|
wx.Dialog.__init__(self, parent, title='Add Player', *args, **kwargs)
|
|
|
|
self.SetInitialSize((350,180))
|
|
main_sizer = wx.BoxSizer(wx.VERTICAL)
|
|
#self.SetSizer(main_sizer)
|
|
|
|
if mode == '__add__':
|
|
description_str = 'Add a new player to the database'
|
|
elif mode == '__modify__':
|
|
description_str = 'Modify an existing player'
|
|
self.SetTitle('Modify Player')
|
|
|
|
description_text = wx.StaticText(self,
|
|
label=description_str)
|
|
main_sizer.Add(description_text, flag=wx.ALL, border=5)
|
|
|
|
sizer_grid = wx.FlexGridSizer(3, 2, 5, 5)
|
|
sizer_grid.AddGrowableCol(1, 1)
|
|
main_sizer.Add(sizer_grid, flag=wx.EXPAND | wx.ALL, border = 3,
|
|
proportion = 10)
|
|
|
|
name_text = wx.StaticText(self, label = 'Player Name')
|
|
sizer_grid.Add(name_text, flag=wx.ALIGN_CENTER_VERTICAL |
|
|
wx.ALIGN_RIGHT)
|
|
|
|
self.name_ctrl = wx.TextCtrl(self)
|
|
sizer_grid.Add(self.name_ctrl, flag=wx.EXPAND, proportion=1)
|
|
|
|
|
|
machine_text = wx.StaticText(self, label='Machine Name')
|
|
sizer_grid.Add(machine_text, flag=wx.ALIGN_CENTER_VERTICAL |
|
|
wx.ALIGN_RIGHT)
|
|
|
|
self.machine_ctrl = wx.TextCtrl(self)
|
|
sizer_grid.Add(self.machine_ctrl, flag=wx.EXPAND, proportion = 1)
|
|
|
|
filter_text = wx.StaticText(self, label = 'Filter')
|
|
sizer_grid.Add(filter_text, flag=wx.ALIGN_CENTER_VERTICAL |
|
|
wx.ALIGN_RIGHT)
|
|
self.filter_ctrl = wx.TextCtrl(self)
|
|
sizer_grid.Add(self.filter_ctrl, flag=wx.EXPAND, proportion = 1)
|
|
|
|
|
|
hrule = wx.StaticLine(self)
|
|
main_sizer.Add(hrule, flag=wx.EXPAND | wx.LEFT | wx.RIGHT,
|
|
border = 10, proportion = 0)
|
|
|
|
btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
main_sizer.Add(btn_sizer, flag=wx.ALIGN_RIGHT | wx.ALL, border = 5)
|
|
|
|
## btn_ok = wx.Button(self, label='Ok')
|
|
## btn_sizer.Add(btn_ok)
|
|
## btn_ok.Bind(wx.EVT_BUTTON, self.OnOk)
|
|
##
|
|
## btn_cancel = wx.Button(self, label = 'Cancel')
|
|
## btn_sizer.Add(btn_cancel)
|
|
## btn_cancel.Bind(wx.EVT_BUTTON, self.OnCancel)
|
|
|
|
btns = self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL)
|
|
btn_sizer.Add(btns)
|
|
|
|
|
|
#self.SetSizerAndFit(main_sizer)
|
|
self.SetSizer(main_sizer)
|
|
self.CenterOnParent()
|
|
|
|
def GetPlayer(self):
|
|
return self.name_ctrl.GetValue()
|
|
|
|
def SetPlayer(self, data):
|
|
self.name_ctrl.SetValue(data)
|
|
|
|
def GetMachine(self):
|
|
return self.machine_ctrl.GetValue()
|
|
|
|
def SetMachine(self, data):
|
|
self.machine_ctrl.SetValue(data)
|
|
|
|
def GetFilter(self):
|
|
return self.filter_ctrl.GetValue()
|
|
|
|
def SetFilter(self, data):
|
|
self.filter_ctrl.SetValue(data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DB:
|
|
def __init__(self, host, database, user, password = None):
|
|
self.host = host
|
|
self.user = user
|
|
self.password = password
|
|
self.database = database
|
|
self.connected = False
|
|
|
|
def Connect(self):
|
|
try:
|
|
del self.connection_error
|
|
except AttributeError:
|
|
pass
|
|
print 'opening connection'
|
|
try:
|
|
self.db = connector.Connect(host=self.host, user=self.user, password=self.password,
|
|
database=self.database)
|
|
self.cursor = self.db.cursor()
|
|
print 'connected'
|
|
self.connected = True
|
|
return True
|
|
except:
|
|
self.connection_error = traceback.format_exc()
|
|
return False
|
|
|
|
def Disconnect(self):
|
|
if self.connected:
|
|
self.db.commit()
|
|
self.cursor.close()
|
|
self.db.close()
|
|
print 'disconnected'
|
|
self.connected = False
|
|
|
|
#def Execute(self, cmd):
|
|
#self.Connect()
|
|
#self.cursor.execute(cmd)
|
|
#self.Disconnect()
|
|
|
|
def InsertTest(self):
|
|
#statement = 'INSERT INTO junctions (name, x, y) VALUES (%s, %s, %s)'
|
|
#self.Connect()
|
|
#self.cursor.execute(statement, ('error', 123, 100))
|
|
#warnings = self.cursor.fetchwarnings()
|
|
#if warnings:
|
|
#print warnings
|
|
#messages = self.cursor
|
|
#self.Disconnect()
|
|
self.Insert('junctions', {'name': 'test1',
|
|
'x': 3,
|
|
'y': 4})
|
|
#self.Insert('junctions', ['name', 'x', 'y'], ['insertest',80085, 1111])
|
|
|
|
|
|
def SelectTest(self):
|
|
statement = 'SELECT * FROM junctions'
|
|
self.Connect()
|
|
self.cursor.execute(statement)
|
|
for row in self.cursor.fetchall():
|
|
print row
|
|
self.Disconnect()
|
|
|
|
|
|
|
|
def Insert(self, table, data):
|
|
keys = []
|
|
vals = []
|
|
for key, value in data.iteritems():
|
|
keys.append('`%s`' % key)
|
|
vals.append(value)
|
|
|
|
keys_str = self.GetRowsString(keys)
|
|
vals_str = self.GetValsString(len(vals))
|
|
|
|
print keys_str
|
|
print vals_str % tuple(vals)
|
|
|
|
stmt = 'INSERT INTO %s %s VALUES %s' % (table, keys_str, vals_str)
|
|
print stmt % tuple(vals)
|
|
|
|
|
|
self.cursor.execute(stmt, tuple(vals))
|
|
#self.Disconnect()
|
|
|
|
|
|
|
|
def GetRowsString(self, rows):
|
|
row_str = '('
|
|
for row in rows:
|
|
row_str += '%s, ' % row
|
|
|
|
row_str = row_str.strip(', ')
|
|
row_str += ')'
|
|
|
|
return row_str
|
|
|
|
def GetValsString(self, num_vals):
|
|
val_str = '('
|
|
for x in range(0, num_vals):
|
|
val_str += '%s, '
|
|
val_str = val_str.strip(', ')
|
|
val_str += ')'
|
|
|
|
return val_str
|
|
|
|
|
|
class Player:
|
|
def __init__(self, player_data, parent):#app_data, app_logger, parent):#db_id, machine_name, person_name, filter_group):
|
|
self.db_id = player_data[0]
|
|
self.machine_name = player_data[1]
|
|
self.person_name = player_data[2]
|
|
self.filter_group = player_data[3]
|
|
|
|
self.parent = parent
|
|
self.app_data = parent.data
|
|
self.LogEvent = parent.LogEvent
|
|
|
|
self.colour = (255, 255, 0)#None
|
|
self.stage = None
|
|
|
|
|
|
#bool to determine if this player is in the 'active' list or not
|
|
self.active = False
|
|
|
|
#set up the 'transfer setup' variables
|
|
self.Reset()
|
|
|
|
|
|
def GetActivesText(self):
|
|
#Returns player name to display in the 'actives' list box
|
|
if MACHINE_NAME_MODE:
|
|
if self.stage:
|
|
return '%s (%s) [%s]' % (self.person_name, self.machine_name, self.stage)
|
|
else:
|
|
return '%s (%s)' % (self.person_name, self.machine_name)
|
|
else:
|
|
if self.stage:
|
|
return '%s [%s]' % (self.person_name, self.stage)
|
|
else:
|
|
return self.person_name
|
|
|
|
def GetPlayerName(self):
|
|
#Returns the player name to display in the 'players' list box
|
|
if MACHINE_NAME_MODE:
|
|
return '%s (%s)' % (self.person_name, self.machine_name)
|
|
else:
|
|
return self.person_name
|
|
|
|
|
|
def Reset(self):
|
|
self.online = None
|
|
self.valid_paths = []
|
|
self.invalid_paths = []
|
|
self.file_list = []
|
|
self.priority_log_file_list = []
|
|
self.console_log_file_list = []
|
|
|
|
def GetFullPaths(self, game):
|
|
paths = []
|
|
for path in self.app_data[game]['source']:
|
|
paths.append(os.path.join('\\\\%s' % self.machine_name,
|
|
path))
|
|
|
|
return paths
|
|
|
|
|
|
def SetValidPaths(self, game):
|
|
for path in self.GetFullPaths(game):
|
|
if not os.path.exists(path):
|
|
self.LogEvent('%s not available' % path,
|
|
'__err__')
|
|
self.invalid_paths.append(path)
|
|
else:
|
|
self.valid_paths.append(path)
|
|
|
|
def SetFileList(self, game):
|
|
exts = self.app_data[game]['filetypes']
|
|
dest_path = self.parent.GetDestinationPath(self)
|
|
|
|
for path in self.valid_paths:
|
|
for fname in os.listdir(path):
|
|
for ext in exts:
|
|
if fname.endswith(ext):
|
|
if ZIP_MODE:
|
|
#print 'zip mode enabled'
|
|
dest = '%s.zip' % dest_path
|
|
#print dest
|
|
else:
|
|
dest = os.path.join(dest_path, fname)
|
|
|
|
if fname.endswith(".log") and (fname.lower().find("prediction") != -1):
|
|
self.priority_log_file_list.append(FileTransporter(os.path.join(path, fname),dest,self))
|
|
self.LogEvent('[prediction] '+os.path.join(path, fname))
|
|
elif fname.endswith(".log") and (fname.lower().find("console") != -1):
|
|
self.console_log_file_list.append(FileTransporter(os.path.join(path, fname),dest,self))
|
|
self.LogEvent('[console] '+os.path.join(path, fname))
|
|
else:
|
|
self.file_list.append(FileTransporter(os.path.join(path, fname),dest,self))
|
|
self.LogEvent('[normal] '+os.path.join(path, fname))
|
|
|
|
|
|
def Activate(self):
|
|
self.active = True
|
|
#test remote connection
|
|
self.TestRemote()
|
|
|
|
|
|
def TestRemote(self):
|
|
if self.parent.m_set_remote.IsChecked():
|
|
try:
|
|
self.SendMessage('__hiya__')
|
|
self.stage = 'Available'
|
|
return True
|
|
except:
|
|
self.LogEvent('%s (%s) could not be reached' % (self.person_name, self.machine_name))
|
|
#e = sys.exc_info()[1]
|
|
#self.LogEvent('%s' %e)
|
|
self.stage = 'UNAVAILABLE'
|
|
return False
|
|
|
|
def Deactivate(self):
|
|
self.active = False
|
|
|
|
def IsActive(self):
|
|
return self.active
|
|
|
|
|
|
def IsMachineOnline(self):
|
|
|
|
if self.online != None:
|
|
return self.online
|
|
|
|
self.LogEvent('sending PING request to %s' % self.machine_name)
|
|
if int(os.system('PING %s -n 1' % self.machine_name)) == 0:
|
|
self.LogEvent('%s responded to PING' % self.machine_name)
|
|
self.online = True
|
|
else:
|
|
self.LogEvent('%s did not respond to PING' % self.machine_name,
|
|
'__err__')
|
|
self.online = False
|
|
|
|
return self.online
|
|
|
|
def SendData(self, msg):
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
try:
|
|
sock.connect((self.machine_name, 9999))
|
|
sock.sendall(msg)
|
|
finally:
|
|
sock.close()
|
|
|
|
def SendMessage(self, msg, extra={}):
|
|
data = {'time': time.time(),
|
|
'source': os.environ.get('username'),
|
|
'msg': msg}
|
|
|
|
data.update(extra)
|
|
|
|
json_data = json.dumps(data)
|
|
|
|
self.SendData(json_data)
|
|
|
|
def SendInitData(self):
|
|
if not self.TestRemote():
|
|
return False
|
|
|
|
selected_game = self.parent.GetSelectedGame()
|
|
|
|
filters = self.parent.GetActiveFilters()
|
|
|
|
player_path = '%s_%s' % (self.parent.GetCleanUsername(self),
|
|
self.machine_name)
|
|
|
|
zip_dest = '%s\\%s_logs_%s_%s.zip' % (self.parent.GetDestinationPath(), player_path,
|
|
self.parent.GetBugNumber(),
|
|
self.parent.GetVersionText())
|
|
|
|
extra = {'machine_name': self.machine_name}
|
|
extra['person_name'] = self.person_name
|
|
extra['clean_name'] = self.parent.GetCleanUsername(self)
|
|
extra['selected_game'] = selected_game
|
|
extra['filters'] = filters
|
|
extra['zip_dest'] = zip_dest
|
|
extra['player_path'] = player_path
|
|
self.SendMessage('__client_prepare__', extra)
|
|
return True
|
|
|
|
|
|
class FileTransporter:
|
|
"""object that holds all the details of a file transfer"""
|
|
deco_template = '%(filename)s (%(num)s)%(ext)s'
|
|
|
|
def __init__(self, source, destination, player=None):
|
|
self.source = source
|
|
self.destination = destination
|
|
self.player = player
|
|
|
|
os_stat = os.stat(source)
|
|
self.filesize = os_stat.st_size
|
|
self.mod_time = os_stat.st_mtime
|
|
|
|
self.finished = False
|
|
|
|
self.error = None
|
|
|
|
|
|
def Start(self):
|
|
self.started = True
|
|
|
|
def Finished(self, err=None):
|
|
self.finished = True
|
|
self.error = err
|
|
|
|
def isDestinationSafe(self):
|
|
if os.path.exists(self.destination):
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def FindSafeDestination(self):
|
|
fname, ext = os.path.splitext(self.destination)
|
|
num = 0
|
|
while not self.isDestinationSafe():
|
|
num += 1
|
|
self.destination = self.deco_template % {'filename': fname,
|
|
'ext': ext,
|
|
'num': num}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RemoteControl(SocketServer.BaseRequestHandler):
|
|
def handle(self):
|
|
|
|
data_list = []
|
|
while True:
|
|
data = self.request.recv(1024)
|
|
if not data:
|
|
break
|
|
data_list.append(data)
|
|
|
|
data_str = ''.join(data_list)
|
|
data = json.loads(data_str)
|
|
|
|
if 'msg' in data:
|
|
if 'machine' in data:
|
|
machine_name = data['machine']#
|
|
self.parent.LogEvent('%s %s (%s), %s' % (data['time'], data['source'],data['machine'], data['msg']))
|
|
else:
|
|
self.parent.LogEvent('%s %s, %s' % (data['time'], data['source'], data['msg']))
|
|
msg = data['msg']
|
|
else:
|
|
msg = None
|
|
machine_name = None
|
|
|
|
|
|
if msg == '__client_prepare__':
|
|
self.PrepareForCopy(data)
|
|
if msg == '__complete__':
|
|
self.parent.RemoteTransferComplete(data)
|
|
if msg == '__busy__':
|
|
self.parent.RemotePlayerBusy(data)
|
|
if msg == '__hiya__':
|
|
self.BusyReply(data)
|
|
|
|
|
|
def PrepareForCopy(self, data):
|
|
if self.parent.remote_busy:
|
|
self.parent.LogEvent('copy command from %s ignored, still busy' % data['source'])
|
|
self.SendMessage('__busy__')
|
|
return False
|
|
|
|
try:
|
|
if len(self.parent.to_transfer) > 0:
|
|
self.parent.LogEvent('copy command from %s ignored, local transfer list is not empty' % data['source'])
|
|
return False
|
|
except AttributeError:
|
|
pass
|
|
|
|
|
|
self.parent.remote_busy = True
|
|
self.parent.remote_master = data['source']
|
|
self.parent.remote_reply = self.request.getpeername()[0]
|
|
|
|
selected_game = data['selected_game']
|
|
|
|
machine_name = data['machine_name']
|
|
person_name = data['person_name']
|
|
clean_name = data['clean_name']
|
|
zip_dest = data['zip_dest']
|
|
|
|
self.parent.LogEvent('Checking path availability')
|
|
|
|
paths = []
|
|
for path in self.parent.data[selected_game]['source']:
|
|
paths.append(os.path.join('X:\\', path))
|
|
|
|
invalid_paths = []
|
|
valid_paths = []
|
|
|
|
for path in paths:
|
|
if not os.path.exists(path):
|
|
self.parent.LogEvent('%s not available' % path,
|
|
'__err__')
|
|
invalid_paths.append(path)
|
|
else:
|
|
valid_paths.append(path)
|
|
|
|
if len(valid_paths) == 0:
|
|
self.parent.ReportEvent('%(name)s (%(machine)s) could not be found (path not shared)' %
|
|
{'name': person_name,
|
|
'machine': machine_name},
|
|
False)
|
|
|
|
priority_log_file_list = []
|
|
console_log_file_list = []
|
|
file_list = []
|
|
|
|
self.parent.LogEvent('Searching for files')
|
|
if valid_paths > 0:
|
|
exts = self.parent.data[selected_game]['filetypes']
|
|
|
|
main_path = self.parent.GetDestinationPath()
|
|
|
|
player_path = '%s_%s' % (machine_name,
|
|
clean_name)
|
|
dest_path = os.path.join(main_path, player_path)
|
|
|
|
for path in valid_paths:
|
|
for fname in os.listdir(path):
|
|
for ext in exts:
|
|
if fname.endswith(ext):
|
|
if ZIP_MODE:
|
|
dest = '%s.zip' % dest_path
|
|
else:
|
|
dest = os.path.join(dest_path, fname)
|
|
|
|
#TODO: check if source already exists in the folder, and rename it
|
|
if fname.endswith(".log") and (fname.lower().find("prediction") != -1):
|
|
priority_log_file_list.append(FileTransporter(os.path.join(path, fname),zip_dest))
|
|
self.parent.LogEvent('[prediction] '+os.path.join(path, fname))
|
|
elif fname.endswith(".log") and (fname.lower().find("console") != -1):
|
|
console_log_file_list.append(FileTransporter(os.path.join(path, fname),zip_dest))
|
|
self.parent.LogEvent('[console] '+os.path.join(path, fname))
|
|
else:
|
|
file_list.append(FileTransporter(os.path.join(path, fname),zip_dest))
|
|
self.parent.LogEvent('[normal] '+os.path.join(path, fname))
|
|
|
|
self.parent.to_transfer = []
|
|
if len(priority_log_file_list) > 0:
|
|
self.parent.to_transfer.extend(priority_log_file_list)
|
|
|
|
if len(file_list) > 0:
|
|
self.parent.to_transfer.extend(file_list)
|
|
|
|
if len(console_log_file_list) > 0:
|
|
self.parent.to_transfer.extend(console_log_file_list)
|
|
|
|
self.parent.RCStartTransfers()
|
|
|
|
def BusyReply(self, data):
|
|
if self.parent.remote_busy:
|
|
self.SendMessage('__busy__')
|
|
|
|
def CopyFiles(self, data):
|
|
self.parent.LogEvent('!!!!! BEGINNING LOG TRANSFER !!!!!')
|
|
flist = data['files']
|
|
|
|
transporters = []
|
|
|
|
for src, dst, error in flist:
|
|
t = FileTransporter(src, dst)
|
|
t.error = error
|
|
transporters.append(t)
|
|
|
|
for idx, t in enumerate(transporters):
|
|
if t.error != 'remote':
|
|
print '%s %s' % (os.path.basename(t.source),
|
|
t.error)
|
|
continue
|
|
|
|
self.parent.LogEvent('copying %s [%s of %s]' % (os.path.basename(t.source),
|
|
idx+1,
|
|
len(transporters)))
|
|
arc_fname = os.path.join(data['player_path'],
|
|
os.path.basename(t.source))
|
|
zip_out = zipfile.ZipFile(data['zip_dest'], 'a', compression=zipfile.ZIP_DEFLATED, allowZip64=True)
|
|
zip_out.write(t.source, arcname=arc_fname)
|
|
zip_out.close()
|
|
|
|
self.parent.LogEvent("\n!!!!! LOG TRANSFER COMPLETE !!!!!")
|
|
|
|
self.SendMessage('__complete__')
|
|
|
|
|
|
|
|
|
|
def SendData(self, msg):
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
try:
|
|
sock.connect((self.request.getpeername()[0], 9999))
|
|
sock.sendall(msg)
|
|
finally:
|
|
sock.close()
|
|
|
|
def SendMessage(self, msg, extra={}):
|
|
data = {'time': time.time(),
|
|
'source': os.environ.get('username'),
|
|
'machine': socket.gethostname(),
|
|
'msg': msg}
|
|
|
|
data.update(extra)
|
|
|
|
json_data = json.dumps(data)
|
|
|
|
self.SendData(json_data)
|
|
|
|
|
|
|
|
|
|
|
|
class CreateGroupDlg(wx.Dialog):
|
|
def __init__(self, parent, *args, **kwargs):
|
|
wx.Dialog.__init__(self, parent, title='Create Group', *args, **kwargs)
|
|
|
|
|
|
class ExceptionDialog(wx.Dialog):
|
|
def __init__(self, etype, value, tb, *args, **kwargs):
|
|
wx.Dialog.__init__(self, None, title='Exception Caught',
|
|
*args, **kwargs)
|
|
|
|
tb_str = ''.join(traceback.format_exception(etype, value, tb))
|
|
exception_str = ''.join(traceback.format_exception_only(etype, value))
|
|
|
|
main_sizer = wx.BoxSizer(wx.VERTICAL)
|
|
self.SetSizer(main_sizer)
|
|
|
|
err_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
main_sizer.Add(err_sizer, flag=wx.EXPAND)
|
|
|
|
err_icon = wx.ArtProvider.GetIcon(wx.ART_ERROR, wx.ART_CMN_DIALOG)
|
|
err_img = wx.StaticBitmap(self)
|
|
err_img.SetIcon(err_icon)
|
|
err_sizer.Add(err_img, flag=wx.TOP | wx.LEFT | wx.RIGHT , border=10)
|
|
|
|
|
|
err_text = wx.StaticText(self,
|
|
label='An exception has occured:\n%s' % exception_str.strip('\n'))
|
|
err_sizer.Add(err_text, flag=wx.ALL, border=10)
|
|
|
|
tb_txt = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_READONLY,
|
|
value = '%s' % tb_str)
|
|
main_sizer.Add(tb_txt, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border = 10, proportion=1)
|
|
|
|
main_sizer.Add(self.CreateSeparatedButtonSizer(wx.OK),
|
|
flag=wx.EXPAND | wx.ALL, border=10)
|
|
|
|
|
|
#self.Fit()
|
|
err_text.Wrap(350)
|
|
self.CentreOnParent()
|
|
|
|
|
|
def MyExceptHook(etype, value, tb):
|
|
error_list = traceback.format_exception(etype, value, tb)
|
|
error_str = ''
|
|
for line in error_list:
|
|
error_str += line
|
|
|
|
#dlg = wx.MessageDialog(None,
|
|
#'An exception has occured: %s' % error_str,
|
|
#'EXCEPTION',
|
|
#wx.OK | wx.ICON_ERROR)
|
|
|
|
dlg = ExceptionDialog(etype, value, tb)
|
|
dlg.ShowModal()
|
|
dlg.Destroy()
|
|
|
|
|
|
class print2(object):
|
|
def write(*args, **kwargs):
|
|
pass
|
|
|
|
def LoadWindowSize(frame, panel, settings, *args, **kwargs):
|
|
id_start = u'['
|
|
id_end = u']'
|
|
current_id = None
|
|
|
|
panel.LogEvent('Loading window size from file')
|
|
|
|
for line in settings:
|
|
# Skip empty lines
|
|
if line in ['\n', '\r\n']:
|
|
continue
|
|
|
|
# Skip comments
|
|
if line.startswith('#'):
|
|
continue
|
|
|
|
# Strip newlines and comments
|
|
line = line.strip('\n')
|
|
if '#' in line:
|
|
line = line[:line.find('#')]
|
|
|
|
line = line.strip().lower()
|
|
|
|
if line.startswith(id_start) and line.endswith(id_end):
|
|
current_id = line.strip('[]')
|
|
continue
|
|
|
|
# Skip the line if we dont have current_id yet
|
|
if current_id is None:
|
|
continue
|
|
elif current_id == 'window_size':
|
|
tokens = line.split(',')
|
|
if len(tokens) == 2 and tokens[0].isdigit() and tokens[1].isdigit():
|
|
panel.LogEvent('Setting size to: %s,%s' % (int(tokens[0]),int(tokens[1])))
|
|
frame.SetSize((int(tokens[0]),int(tokens[1])))
|
|
return
|
|
|
|
frame.SetSize((550, 700))
|
|
|
|
#class BusyPlayerEvent(wx.PyCommandEvent):
|
|
# def __init__(self,etype,eid,player=None):
|
|
# wxPyCommandEvent.__init__(self,etype,eid)
|
|
# self._player = player
|
|
#
|
|
# def GetPlayer(self):
|
|
# return self._value
|
|
|
|
class BusyPlayersThread(threading.Thread):
|
|
def __init__(self, parent):
|
|
threading.Thread.__init__(self)
|
|
self._parent = parent
|
|
|
|
def run(self):
|
|
busy_app = wx.App(False)
|
|
busy_frame = BusyPlayersFrame(None, 'Busy Players')
|
|
busy_frame.Show(True)
|
|
busy_app.MainLoop()
|
|
time.sleep(10)
|
|
|
|
class BusyPlayersFrame(wx.Frame):
|
|
def __init__(self, parent, title):
|
|
wx.Frame.__init__(self,parent,title=title)
|
|
|
|
self.player_list = []
|
|
# Do layout
|
|
self.list_view = wx.ListView(parent=self, id=wx.ID_ANY)
|
|
|
|
#self.Bind(EVT_BUSYPL, self.UpdateBusyPlayerList)
|
|
|
|
#def UpdateBusyPlayerList(self, evt):
|
|
# player = evt.GetPlayer()
|
|
# if player not in self.player_list:
|
|
# self.player_list.append(player)
|
|
# self.list_view.InsertStringItem(1,player)
|
|
|
|
# Custom frame which shows only a error dialog message when another instance of LogGrabber
|
|
# is detected to be already running. Can't use the same frame because we want to detect
|
|
# the old grabber instance before we create new one (basically the check needs to be in
|
|
# the top level of code).
|
|
#class ErrorFrame(wx.Frame):
|
|
# def __init__(self, parent, title, msg):
|
|
# wx.Frame.__init__(self,parent,title=title)
|
|
# dial = wx.MessageDialog(None, 'Another instance of LogGrabber is running. Please close it or kill \'pythonw.exe\' process and try again!\n\n%s'%msg, 'Error', wx.OK | wx.ICON_ERROR)
|
|
# dial.ShowModal()
|
|
# sys.exit(1)
|
|
|
|
#try:
|
|
# Set up a socket listening to 9996 port to detect if another instance of the program is already running
|
|
# in_HOST, in_PORT = "0.0.0.0", 9996
|
|
# try:
|
|
# IN_SOCKET = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
# IN_SOCKET.bind((in_HOST,in_PORT))
|
|
# except: #Gotta catch them all
|
|
#Another log grabber instance is listening on this socket, so dont launch another one#
|
|
# e = sys.exc_info()[1]
|
|
# Create a custom frame to be able to show error dialog. Exit the script afterwards
|
|
# temp_app = wx.App(False)
|
|
# temp_frame = ErrorFrame(None, 'Error',e)
|
|
# temp_app.MainLoop()
|
|
|
|
DebugLog("#######################################################################")
|
|
|
|
temp = os.environ['temp']
|
|
stdout_log = os.path.join(temp, 'mplg_stdout_%s.log' % time.strftime('%Y-%m-%d_%H%M%S'))
|
|
|
|
sys.excepthook = MyExceptHook
|
|
#app = wx.PySimpleApp()
|
|
|
|
app = wx.App(redirect=True,
|
|
filename = stdout_log)
|
|
#app = MyApp(redirect=True, filename = stdout_log)
|
|
|
|
mainframe = wx.Frame(None)
|
|
mainpanel = MainPanel(mainframe)
|
|
|
|
mainframe.SetTitle('mpLogGrabber')
|
|
|
|
# Try setting the last used window size (need to do this before loading all other settings in the panel)
|
|
settingsF= os.path.join(sys.path[0],'personal_settings.ini')
|
|
if os.path.exists(settingsF):
|
|
try:
|
|
settingsF_ini = codecs.open(settingsF, 'r','utf-8')
|
|
LoadWindowSize(mainframe, mainpanel, settingsF_ini)
|
|
settingsF_ini.close()
|
|
except IOError:
|
|
print ('Error: Unable to access the local settings file, user may not have write'
|
|
'permission for "%s"' % settingsF)
|
|
print 'Press any key to exit'
|
|
#qa.wait_for_key()
|
|
sys.exit()
|
|
else:
|
|
mainframe.SetSize((550, 700))
|
|
|
|
mainframe.Show()
|
|
|
|
|
|
#import wx.lib.inspection
|
|
#wx.lib.inspection.InspectionTool().Show()
|
|
|
|
|
|
app.MainLoop()
|
|
|
|
#finally:
|
|
# If this is the only running LogGrabber instance then
|
|
# if IN_SOCKET != None:
|
|
# IN_SOCKET.close()
|
|
# sys.exit(0)
|
|
|
|
|
|
|
|
|