def OnHelp(self, event=None): #@UnusedVariable if u'alpha' in GetTestList(): HelpDialog(self).ShowModal() else: # FIXME: files should be re-cached when Debreate upgraded to new version # TODO: trim unneeded text cached = False manual_cache = ConcatPaths(PATH_cache, u'manual') manual_index = ConcatPaths(manual_cache, u'index.html') if not os.path.isdir(manual_cache): os.makedirs(manual_cache) elif os.path.isfile(manual_index): cached = True url_manual = u'https://debreate.wordpress.com/manual/' # NOTE: use urllib.request.urlopen for Python 3 manual_data = urllib.urlopen(url_manual) url_state = manual_data.getcode() if url_state == 200: # cache files if not cached: self.progress = ProgressDialog( self, message=GT(u'Caching manual files'), style=wx.PD_APP_MODAL | wx.PD_AUTO_HIDE) self.Disable() self.timer.Start(100) Thread(self.__cacheManualFiles, ( url_manual, manual_cache, manual_index, )).Start() self.progress.ShowModal() manual_dialog = wx.Dialog(self, title=u'Debreate Manual', size=(800, 500), style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) manual = wx.html.HtmlWindow(manual_dialog) wx.Yield() if manual.LoadFile(manual_index): manual_dialog.CenterOnParent() manual_dialog.ShowModal() else: wx.Yield() webbrowser.open(url_manual) manual_dialog.Destroy() else: # open local document wx.Yield() subprocess.call( [u'xdg-open', u'{}/docs/usage.pdf'.format(PATH_app)])
def AddPaths(self, dirs, fileCount=None, showDialog=False): target = self.GetTarget() if fileCount == None: fileCount = 0 for D in dirs: for F in dirs[D]: fileCount += 1 progress = None Logger.Debug(__name__, u'Adding {} files ...'.format(fileCount)) if showDialog: progress = ProgressDialog(GetMainWindow(), GT(u'Adding Files'), maximum=fileCount, style=PD_DEFAULT_STYLE | wx.PD_CAN_ABORT) progress.Show() completed = 0 for D in sorted(dirs): for F in sorted(dirs[D]): if progress and progress.WasCancelled(): progress.Destroy() return False if progress: wx.Yield() progress.Update(completed, GT(u'Adding file {}').format(F)) self.lst_files.AddFile(F, D, target) completed += 1 if progress: wx.Yield() progress.Update(completed) progress.Destroy() return True
class MainWindow(wx.Frame, ModuleAccessCtrl): ## Constructor # # \param pos # <b><i>Integer tuple</i></b> or <b><i>wx.Point</i></b> instance indicating the screen position of the window # \param size # <b><i>Integer tuple</i></b> or <b><i>wx.Size</i></b> instance indicating the dimensions of the window def __init__(self, pos, size): wx.Frame.__init__(self, None, wx.ID_ANY, default_title, pos, size) ModuleAccessCtrl.__init__(self, __name__) self.timer = DebreateTimer(self) # placeholder for progress dialog self.progress = None self.Bind(wx.EVT_TIMER, self.__onTimerEvent) self.Bind(EVT_TIMER_STOP, self.__onTimerStop) # Make sure that this frame is set as the top window if not wx.GetApp().GetTopWindow() == self: Logger.Debug(__name__, GT(u'Setting MainWindow instance as top window')) wx.GetApp().SetTopWindow(self) if DebugEnabled(): self.SetTitle(u'{} ({})'.format(default_title, GT(u'debugging'))) self.SetMinSize(wx.Size(640, 400)) # ----- Set Titlebar Icon self.SetIcon(Icon(LOGO)) # *** Status Bar *** # StatusBar(self) # *** Menus *** # createMenuBar(self) self.Wizard = Wizard(self) # *** Current Project Status *** # self.LoadedProject = None self.ProjectDirty = False # *** Event Handling *** # wx.EVT_MENU(self, menuid.NEW, self.OnProjectNew) wx.EVT_MENU(self, menuid.OPEN, self.OnProjectOpen) wx.EVT_MENU(self, menuid.SAVE, self.OnProjectSave) wx.EVT_MENU(self, menuid.SAVEAS, self.OnProjectSave) wx.EVT_MENU(self, menuid.QBUILD, self.OnQuickBuild) wx.EVT_MENU(self, menuid.EXIT, self.OnQuit) wx.EVT_MENU(self, menuid.TOOLTIPS, self.OnToggleToolTips) wx.EVT_MENU(self, menuid.DIST, self.OnUpdateDistNamesCache) wx.EVT_MENU(self, menuid.UPDATE, self.OnCheckUpdate) wx.EVT_MENU(self, menuid.HELP, self.OnHelp) wx.EVT_MENU(self, menuid.ABOUT, self.OnAbout) self.Bind(EVT_CHANGE_PAGE, self.OnWizardBtnPage) # Custom close event shows a dialog box to confirm quit wx.EVT_CLOSE(self, self.OnQuit) # *** Layout *** # lyt_main = BoxSizer(wx.VERTICAL) lyt_main.Add(self.Wizard, 1, wx.EXPAND) self.SetAutoLayout(True) self.SetSizer(lyt_main) self.Layout() ## Retrieves menu by ID def GetMenu(self, menuId): return self.GetMenuBar().GetMenuById(menuId) ## Retrieves the Wizard instance # # \return # wiz.wizard.Wizard def GetWizard(self): return self.Wizard ## Sets the pages in the wiz.wizard.Wizard instance def InitWizard(self): self.Wizard.AddPage(PageInit(self.Wizard)) self.Wizard.SetModeBin(0) ## TODO: Doxygen def IsNewProject(self): title = self.GetTitle() if title == default_title: return True else: return False ## TODO: Doxygen def IsSaved(self): title = self.GetTitle() if title[-1] == u'*': return False else: return True ## Opens a dialog box with information about the program def OnAbout(self, event=None): #@UnusedVariable about = AboutDialog(self) about.SetGraphic(LOGO) about.SetVersion(VERSION_string) about.SetDescription(GT(u'A package builder for Debian based systems')) about.SetAuthor(AUTHOR_name) about.SetWebsites(( (GT(u'Homepage'), APP_homepage), (GT(u'GitHub Project'), APP_project_gh), (GT(u'Sourceforge Project'), APP_project_sf), )) about.AddJobs(AUTHOR_name, ( GT(u'Head Developer'), GT(u'Packager'), u'{} (es, it)'.format(GT(u'Translation')), ), AUTHOR_email) about.AddJobs(u'Hugo Posnic', ( GT(u'Code Contributor'), GT(u'Website Designer & Author'), ), u'*****@*****.**') about.AddJob(u'Lander Usategui San Juan', GT(u'General Contributor'), u'*****@*****.**') about.AddTranslator( u'Karim Oulad Chalha', u'*****@*****.**', u'ar', ) about.AddTranslator(u'Philippe Dalet', u'*****@*****.**', u'fr') about.AddTranslator(u'Zhmurkov Sergey', u'*****@*****.**', u'ru') about.AddJob(u'Benji Park', GT(u'Button Base Image Designer')) about.SetChangelog() about.SetLicense() about.ShowModal() about.Destroy() ## Checks for new release availability def OnCheckUpdate(self, event=None): #@UnusedVariable update_test = u'update-fail' in GetTestList() if UsingDevelopmentVersion() and not update_test: DetailedMessageDialog( self, GT(u'Update'), text=GT(u'Update checking is disabled in development versions' )).ShowModal() return wx.SafeYield() if update_test: # Set a bad url to force error current = GetCurrentVersion(u'http://dummyurl.blah/') else: current = GetCurrentVersion() Logger.Debug(__name__, GT(u'URL request result: {}').format(current)) error_remote = GT( u'An error occurred attempting to contact remote website') if isinstance(current, (URLError, HTTPError)): current = GS(current) ShowErrorDialog(error_remote, current) elif isinstance(current, tuple) and current > VERSION_tuple: current = u'{}.{}.{}'.format(current[0], current[1], current[2]) l1 = GT(u'Version {} is available!').format(current) l2 = GT(u'Would you like to go to Debreate\'s website?') if ConfirmationDialog(self, GT(u'Update'), u'{}\n\n{}'.format(l1, l2)).Confirmed(): wx.LaunchDefaultBrowser(APP_homepage) elif isinstance(current, (unicode, str)): ShowErrorDialog(error_remote, current) else: DetailedMessageDialog( self, GT(u'Debreate'), text=GT(u'Debreate is up to date!')).ShowModal() def __cacheManualFiles(self, args): url_manual = args[0] manual_cache = args[1] manual_index = args[2] main_dir = os.getcwd() os.chdir(manual_cache) try: subprocess.Popen([ u'wget', u'-rkp', u'-nd', u'-np', u'-H', u'-D', u'debreate.wordpress.com,antumdeluge.github.io', url_manual ]).communicate() # FIXME: use Python commands subprocess.Popen([ u'sed', u'-i', u'-e', u's|<a.*>||g', u'-e', u's|</a>||g', manual_index ]).communicate() except: # FIXME: show error message pass os.chdir(main_dir) self.timer.Stop() ## Calls Pulse method on progress dialog when timer event occurs def __onTimerEvent(self, event=None): if self.progress: self.progress.Pulse() def __onTimerStop(self, event=None): if self.progress: self.progress.EndModal(0) self.progress = None if not self.IsEnabled(): self.Enable() return not self.progress ## Action to take when 'Help' is selected from the help menu # # Opens a help dialog if using 'alpha' test. Otherwise, opens # PDF usage document. If openening usage document fails, attempts # to open web browser in remote usage page. # TODO: Use dialog as main method def OnHelp(self, event=None): #@UnusedVariable if u'alpha' in GetTestList(): HelpDialog(self).ShowModal() else: # FIXME: files should be re-cached when Debreate upgraded to new version # TODO: trim unneeded text cached = False manual_cache = ConcatPaths(PATH_cache, u'manual') manual_index = ConcatPaths(manual_cache, u'index.html') if not os.path.isdir(manual_cache): os.makedirs(manual_cache) elif os.path.isfile(manual_index): cached = True url_manual = u'https://debreate.wordpress.com/manual/' # NOTE: use urllib.request.urlopen for Python 3 manual_data = urllib.urlopen(url_manual) url_state = manual_data.getcode() if url_state == 200: # cache files if not cached: self.progress = ProgressDialog( self, message=GT(u'Caching manual files'), style=wx.PD_APP_MODAL | wx.PD_AUTO_HIDE) self.Disable() self.timer.Start(100) Thread(self.__cacheManualFiles, ( url_manual, manual_cache, manual_index, )).Start() self.progress.ShowModal() manual_dialog = wx.Dialog(self, title=u'Debreate Manual', size=(800, 500), style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) manual = wx.html.HtmlWindow(manual_dialog) wx.Yield() if manual.LoadFile(manual_index): manual_dialog.CenterOnParent() manual_dialog.ShowModal() else: wx.Yield() webbrowser.open(url_manual) manual_dialog.Destroy() else: # open local document wx.Yield() subprocess.call( [u'xdg-open', u'{}/docs/usage.pdf'.format(PATH_app)]) ## Opens the logs directory in the system's default file manager def OnLogDirOpen(self, event=None): #@UnusedVariable Logger.Debug(__name__, GT(u'Opening log directory ...')) subprocess.check_output( [GetExecutable(u'xdg-open'), u'{}/logs'.format(PATH_local)], stderr=subprocess.STDOUT) ## Changes wizard page from menu def OnMenuChangePage(self, event=None): if isinstance(event, int): page_id = event else: page_id = event.GetId() self.Wizard.ShowPage(page_id) ## TODO: Doxygen def OnProjectNew(self, event=None): #@UnusedVariable self.ResetPages() ## TODO: Doxygen def OnProjectOpen(self, event=None): #@UnusedVariable projects_filter = u'|*.{};*.{}'.format(PROJECT_ext, PROJECT_txt) d = GT(u'Debreate project files') dia = wx.FileDialog(self, GT(u'Open Debreate Project'), os.getcwd(), u'', u'{}{}'.format(d, projects_filter), wx.FD_CHANGE_DIR) if dia.ShowModal() != wx.ID_OK: return # Get the path and set the saved project project = dia.GetPath() filename = os.path.basename(project) if self.OpenProject(filename): # Only set project open in memory if loaded completely self.LoadedProject = project else: self.LoadedProject = None ## TODO: Doxygen def OnProjectSave(self, event=None): event_id = event.GetId() def SaveIt(path): # Gather data from different pages data = ( GetPage(pgid.CONTROL).GetSaveData(), GetPage(pgid.FILES).GetSaveData(), GetPage(pgid.SCRIPTS).GetSaveData(), GetPage(pgid.CHANGELOG).GetSaveData(), GetPage(pgid.COPYRIGHT).GetSaveData(), GetPage(pgid.MENU).GetSaveData(), GetPage(pgid.BUILD).GetSaveData(), ) # Create a backup of the project overwrite = False if os.path.isfile(path): backup = u'{}.backup'.format(path) shutil.copy(path, backup) overwrite = True # This try statement can be removed when unicode support is enabled try: WriteFile( path, u'[DEBREATE-{}]\n{}'.format(VERSION_string, u'\n'.join(data))) if overwrite: os.remove(backup) except UnicodeEncodeError: detail1 = GT( u'Unfortunately Debreate does not support unicode yet.') detail2 = GT( u'Remove any non-ASCII characters from your project.') ShowErrorDialog(GT(u'Save failed'), u'{}\n{}'.format(detail1, detail2), title=GT(u'Unicode Error')) if overwrite: os.remove(path) # Restore project backup shutil.move(backup, path) def OnSaveAs(): dbp = u'|*.dbp' d = GT(u'Debreate project files') dia = wx.FileDialog( self, GT(u'Save Debreate Project'), os.getcwd(), u'', u'{}{}'.format(d, dbp), wx.FD_SAVE | wx.FD_CHANGE_DIR | wx.FD_OVERWRITE_PROMPT) if dia.ShowModal() == wx.ID_OK: filename = dia.GetFilename() if filename.split(u'.')[-1] == u'dbp': filename = u'.'.join(filename.split(u'.')[:-1]) self.LoadedProject = u'{}/{}.dbp'.format( os.path.split(dia.GetPath())[0], filename) SaveIt(self.LoadedProject) if event_id == wx.ID_SAVE: # Define what to do if save is pressed # If project already exists, don't show dialog if not self.IsSaved( ) or not self.LoadedProject or not os.path.isfile( self.LoadedProject): OnSaveAs() else: SaveIt(self.LoadedProject) else: # If save as is press, show the save dialog OnSaveAs() ## TODO: Doxygen def OnQuickBuild(self, event=None): #@UnusedVariable QB = QuickBuild(self) QB.ShowModal() QB.Destroy() ## Shows a dialog to confirm quit and write window settings to config file def OnQuit(self, event=None): #@UnusedVariable if ConfirmationDialog(self, GT(u'Quit?'), text=GT(u'You will lose any unsaved information') ).ShowModal() in (wx.ID_OK, wx.OK): maximized = self.IsMaximized() WriteConfig(u'maximize', maximized) if maximized: WriteConfig(u'position', GetDefaultConfigValue(u'position')) WriteConfig(u'size', GetDefaultConfigValue(u'size')) WriteConfig(u'center', True) else: WriteConfig(u'position', self.GetPositionTuple()) WriteConfig(u'size', self.GetSizeTuple()) WriteConfig(u'center', False) WriteConfig(u'workingdir', os.getcwd()) self.Destroy() ## TODO: Doxygen def OnToggleTheme(self, event=None): #@UnusedVariable self.ToggleTheme(self) ## Shows or hides tooltips def OnToggleToolTips(self, event=None): #@UnusedVariable enabled = self.opt_tooltips.IsChecked() wx.ToolTip.Enable(enabled) WriteConfig(u'tooltips', enabled) ## Opens a dialog for creating/updating list of distribution names cache file def OnUpdateDistNamesCache(self, event=None): #@UnusedVariable DistNamesCacheDialog().ShowModal() ## Updates the page menu to reflect current page def OnWizardBtnPage(self, event=None): #@UnusedVariable ID = self.Wizard.GetCurrentPageId() Logger.Debug(__name__, GT(u'Event: EVT_CHANGE_PAGE, Page ID: {}').format(ID)) menu_page = self.GetMenu(menuid.PAGE) if not menu_page.IsChecked(ID): menu_page.Check(ID, True) ## Deletes cache directory located at ~/.local/share/debreate/cache def OnClearCache(self, event=None): if os.path.isdir(PATH_cache): shutil.rmtree(PATH_cache) ## Opens web links from the help menu def OpenPolicyManual(self, event=None): if isinstance(event, wx.CommandEvent): event_id = event.GetId() elif isinstance(event, int): event_id = event else: Logger.Error( __name__, u'Cannot open policy manual link with object type {}'.format( type(event))) return url = self.menu_policy.GetHelpString(event_id) webbrowser.open(url) ## Retrieves filename of loaded project def ProjectGetLoaded(self): return self.LoadedProject ## Tests project type & calls correct method to read project file # # \param project_file # \b \e unicode|str : Path to project file def OpenProject(self, project_file): Logger.Debug(__name__, u'Opening project: {}'.format(project_file)) if not os.path.isfile(project_file): ShowErrorDialog( GT(u'Could not open project file'), GT(u'File does not exist or is not a regular file: {}').format( project_file)) return False data = ReadFile(project_file) lines = data.split(u'\n') # FIXME: Need a better way to determine valid project app = lines[0].lstrip(u'[') if not app.startswith(u'DEBREATE'): ShowErrorDialog( GT(u'Could not open project file'), GT(u'Not a valid Debreate project: {}').format(project_file)) return False if self.LoadedProject and not self.ResetPages(): return False # *** Get Control Data *** # control_data = data.split(u'<<CTRL>>\n')[1].split(u'\n<</CTRL>>')[0] depends_data = self.Wizard.GetPage(pgid.CONTROL).Set(control_data) self.Wizard.GetPage(pgid.DEPENDS).Set(depends_data) # *** Get Files Data *** # files_data = data.split(u'<<FILES>>\n')[1].split(u'\n<</FILES>>')[0] opened = self.Wizard.GetPage(pgid.FILES).Set(files_data) # *** Get Scripts Data *** # scripts_data = data.split(u'<<SCRIPTS>>\n')[1].split( u'\n<</SCRIPTS>>')[0] self.Wizard.GetPage(pgid.SCRIPTS).Set(scripts_data) # *** Get Changelog Data *** # clog_data = data.split(u'<<CHANGELOG>>\n')[1].split( u'\n<</CHANGELOG>>')[0] self.Wizard.GetPage(pgid.CHANGELOG).Set(clog_data) # *** Get Copyright Data *** # try: cpright_data = data.split(u'<<COPYRIGHT>>\n')[1].split( u'\n<</COPYRIGHT')[0] self.Wizard.GetPage(pgid.COPYRIGHT).Set(cpright_data) except IndexError: pass # *** Get Menu Data *** # m_data = data.split(u'<<MENU>>\n')[1].split(u'\n<</MENU>>')[0] self.Wizard.GetPage(pgid.MENU).SetLauncherData(m_data, enabled=True) # Get Build Data build_data = data.split(u'<<BUILD>>\n')[1].split(u'\n<</BUILD')[ 0] #.split(u'\n') self.Wizard.GetPage(pgid.BUILD).Set(build_data) return opened ## TODO: Doxygen def ProjectChanged(self, event=None): if DebugEnabled(): Logger.Debug(__name__, u'MainWindow.OnProjectChanged:') print(u' Object: {}'.format(event.GetEventObject())) self.ProjectDirty = True ## TODO: Doxygen def ResetPages(self): warn_msg = GT(u'You will lose any unsaved information.') warn_msg = u'{}\n\n{}'.format(warn_msg, GT(u'Continue?')) if ConfirmationDialog(self, text=warn_msg).ShowModal() not in (wx.ID_OK, wx.OK): return False for page in self.Wizard.GetAllPages(): page.Reset() self.SetTitle(default_title) # Reset the saved project field so we know that a project file doesn't exists self.LoadedProject = None return True ## TODO: Doxygen def SetSavedStatus(self, status): if status: # If status is changing to unsaved this is True title = self.GetTitle() if self.IsSaved() and title != default_title: self.SetTitle(u'{}*'.format(title)) ## TODO: Doxygen # # TODO: Finish definition def ToggleTheme(self, window): for C in window.GetChildren(): self.ToggleTheme(C) bg_color = window.GetBackgroundColour() fg_color = window.GetForegroundColour() window.SetBackgroundColour(fg_color) window.SetForegroundColour(bg_color)
def Set(self, data): # Clear files list self.lst_files.DeleteAllItems() files_data = data.split(u'\n') if int(files_data[0]): # Get file count from list minus first item "1" files_total = len(files_data) # Store missing files here missing_files = [] progress = None if files_total >= efficiency_threshold: progress = ProgressDialog(GetMainWindow(), GT(u'Adding Files'), maximum=files_total, style=PD_DEFAULT_STYLE|wx.PD_CAN_ABORT) wx.Yield() progress.Show() current_file = files_total while current_file > 1: if progress and progress.WasCancelled(): progress.Destroy() # Project continues opening even if file import is cancelled msg = ( GT(u'File import did not complete.'), GT(u'Project files may be missing in file list.'), ) ShowMessageDialog(u'\n'.join(msg), GT(u'Import Cancelled')) return False current_file -= 1 executable = False file_info = files_data[current_file].split(u' -> ') absolute_filename = file_info[0] if absolute_filename[-1] == u'*': # Set executable flag and remove "*" executable = True absolute_filename = absolute_filename[:-1] filename = file_info[1] source_dir = absolute_filename[:len(absolute_filename) - len(filename)] target_dir = file_info[2] if not self.lst_files.AddFile(filename, source_dir, target_dir, executable): Logger.Warn(__name__, GT(u'File not found: {}').format(absolute_filename)) missing_files.append(absolute_filename) if progress: update_value = files_total - current_file wx.Yield() progress.Update(update_value+1, GT(u'Imported file {} of {}').format(update_value, files_total)) if progress: progress.Destroy() Logger.Debug(__name__, u'Missing file count: {}'.format(len(missing_files))) # If files are missing show a message if missing_files: alert = DetailedMessageDialog(GetMainWindow(), GT(u'Missing Files'), ICON_EXCLAMATION, GT(u'Could not locate the following files:'), u'\n'.join(missing_files)) alert.ShowModal() return True
def LoadPaths(self, pathsList): if isinstance(pathsList, tuple): pathsList = list(pathsList) if not pathsList or not isinstance(pathsList, list): return False file_list = [] dir_list = {} prep = ProgressDialog(GetMainWindow(), GT(u'Processing Files'), GT(u'Scanning files ...'), style=wx.PD_APP_MODAL|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT) # Only update the gauge every N files (hack until I figure out timer) update_interval = 450 count = 0 prep.Show() if not self.chk_preserve_top.GetValue(): for INDEX in reversed(range(len(pathsList))): path = pathsList[INDEX] if os.path.isdir(path): # Remove top-level directory from list pathsList.pop(INDEX) insert_index = INDEX for P in os.listdir(path): pathsList.insert(insert_index, ConcatPaths((path, P))) insert_index += 1 try: for P in pathsList: if prep.WasCancelled(): prep.Destroy() return False count += 1 if count >= update_interval: wx.Yield() prep.Pulse() count = 0 if not self.chk_individuals.GetValue() or os.path.isfile(P): file_list.append(P) continue if os.path.isdir(P): parent_dir = os.path.dirname(P) if parent_dir not in dir_list: dir_list[parent_dir] = [] for ROOT, DIRS, FILES in os.walk(P): if prep.WasCancelled(): prep.Destroy() return False wx.Yield() prep.SetMessage(GT(u'Scanning directory {} ...').format(ROOT)) count += 1 if count >= update_interval: wx.Yield() prep.Pulse() count = 0 for F in FILES: if prep.WasCancelled(): prep.Destroy() return False count += 1 if count >= update_interval: wx.Yield() prep.Pulse() count = 0 # os.path.dirname preserves top level directory ROOT = ROOT.replace(os.path.dirname(P), u'').strip(u'/') F = u'{}/{}'.format(ROOT, F).strip(u'/') if F not in dir_list[parent_dir]: dir_list[parent_dir].append(F) except: prep.Destroy() ShowErrorDialog(GT(u'Could not retrieve file list'), traceback.format_exc()) return False wx.Yield() prep.Pulse(GT(u'Counting Files')) file_count = len(file_list) count = 0 for D in dir_list: for F in dir_list[D]: file_count += 1 count += 1 if count >= update_interval: wx.Yield() prep.Pulse() count = 0 prep.Destroy() # Add files to directory list for F in file_list: f_name = os.path.basename(F) f_dir = os.path.dirname(F) if f_dir not in dir_list: dir_list[f_dir] = [] dir_list[f_dir].append(f_name) if file_count > warning_threshhold: count_warnmsg = GT(u'Importing {} files'.format(file_count)) count_warnmsg = u'{}. {}.'.format(count_warnmsg, GT(u'This could take a VERY long time')) count_warnmsg = u'{}\n{}'.format(count_warnmsg, GT(u'Are you sure you want to continue?')) if not ConfirmationDialog(GetMainWindow(), text=count_warnmsg).Confirmed(): return False return self.AddPaths(dir_list, file_count, showDialog=file_count >= efficiency_threshold)
def BuildPrep(self): wizard = GetWizard() prep_ids = [] pages = wizard.GetAllPages() for P in pages: if P.prebuild_check: Logger.Debug( __name__, GT(u'Pre-build check for page "{}"'.format(P.GetName()))) prep_ids.append(P.GetId()) try: main_window = GetMainWindow() # List of page IDs to process during build pg_build_ids = [] steps_count = len(prep_ids) current_step = 0 msg_label1 = GT(u'Prepping page "{}"') msg_label2 = GT(u'Step {}/{}') msg_label = u'{} ({})'.format(msg_label1, msg_label2) prep_progress = ProgressDialog( main_window, GT(u'Preparing Build'), msg_label2.format(current_step, steps_count), maximum=steps_count, style=PD_DEFAULT_STYLE | wx.PD_CAN_ABORT) for P in pages: if prep_progress.WasCancelled(): break p_id = P.GetId() p_label = P.GetTitle() if p_id in prep_ids: Logger.Debug( __name__, msg_label.format(p_label, current_step + 1, steps_count)) wx.Yield() prep_progress.Update( current_step, msg_label.format(p_label, current_step + 1, steps_count)) if P.IsOkay(): pg_build_ids.append(p_id) current_step += 1 if not prep_progress.WasCancelled(): wx.Yield() prep_progress.Update(current_step, GT(u'Prepping finished')) # Show finished dialog for short period time.sleep(1) prep_progress.Destroy() return pg_build_ids except: prep_progress.Destroy() ShowErrorDialog(GT(u'Error occurred during pre-build'), traceback.format_exc()) return None
def Build(self, outFile): def log_message(msg, current_step, total_steps): return u'{} ({}/{})'.format(msg, current_step, total_steps) wizard = GetWizard() pages_build_ids = self.BuildPrep() if pages_build_ids != None: main_window = GetMainWindow() # Reported at the end of build build_summary = [] steps_count = len(pages_build_ids) current_step = 0 # Steps from build page for chk in self.chk_md5, self.chk_lint, self.chk_rmstage: if chk.IsChecked(): steps_count += 1 # Control file & .deb build step steps_count += 2 stage = CreateStage() log_msg = GT(u'Starting build') wx.YieldIfNeeded() # FIXME: Enable PD_CAN_ABORT build_progress = ProgressDialog(main_window, GT(u'Building'), log_msg, maximum=steps_count) build_summary.append(u'{}:'.format(log_msg)) try: for P in wizard.GetAllPages(): if build_progress.WasCancelled(): break if P.GetId() in pages_build_ids: p_label = P.GetTitle() log_msg = log_message( GT(u'Processing page "{}"').format(p_label), current_step + 1, steps_count) # FIXME: Progress bar not updating??? wx.YieldIfNeeded() build_progress.Update(current_step, log_msg) ret_code, ret_value = P.ExportBuild(stage) build_summary.append(u'\n{}:\n{}'.format( log_msg, ret_value)) if ret_code > 0: build_progress.Destroy() ShowErrorDialog(GT(u'Error occurred during build'), ret_value) return current_step += 1 # *** Control File *** # if not build_progress.WasCancelled(): wx.YieldIfNeeded() log_msg = log_message(GT(u'Creating control file'), current_step + 1, steps_count) build_progress.Update(current_step, log_msg) Logger.Debug(__name__, log_msg) # Retrieve control page pg_control = wizard.GetPage(pgid.CONTROL) if not pg_control: build_progress.Destroy() ShowErrorDialog( GT(u'Could not retrieve control page'), GT(u'Please contact the developer: {}').format( AUTHOR_email), title=u'Fatal Error') return installed_size = self.OnBuildGetInstallSize(stage) Logger.Debug( __name__, GT(u'Installed size: {}').format(installed_size)) build_summary.append(u'\n{}:'.format(log_msg)) build_summary.append( pg_control.ExportBuild( u'{}/DEBIAN'.format(stage).replace(u'//', u'/'), installed_size)) current_step += 1 # *** MD5 Checksum *** # if not build_progress.WasCancelled(): if self.chk_md5.GetValue() and GetExecutable(u'md5sum'): log_msg = log_message(GT(u'Creating MD5 checksum'), current_step + 1, steps_count) #log_msg = GT(u'Creating MD5 checksum') #step = u'{}/{}'.format(current_step+1, steps_count) Logger.Debug(__name__, log_msg) wx.YieldIfNeeded() build_progress.Update(current_step, log_msg) build_summary.append(u'\n{}:'.format(log_msg)) build_summary.append(self.OnBuildMD5Sum(stage)) current_step += 1 # *** Create .deb from Stage *** # if not build_progress.WasCancelled(): log_msg = log_message(GT(u'Creating .deb package'), current_step + 1, steps_count) wx.YieldIfNeeded() build_progress.Update(current_step, log_msg) build_summary.append(u'\n{}:'.format(log_msg)) build_summary.append( self.OnBuildCreatePackage(stage, outFile)) current_step += 1 # *** Lintian *** # if not build_progress.WasCancelled(): if self.chk_lint.IsChecked(): log_msg = log_message( GT(u'Checking package with lintian'), current_step + 1, steps_count) wx.YieldIfNeeded() build_progress.Update(current_step, log_msg) build_summary.append(u'\n{}:'.format(log_msg)) build_summary.append(self.OnBuildCheckPackage(outFile)) current_step += 1 # *** Delete Stage *** # if not build_progress.WasCancelled(): if self.chk_rmstage.IsChecked(): log_msg = log_message( GT(u'Removing staged build tree'), current_step + 1, steps_count) wx.YieldIfNeeded() build_progress.Update(current_step, log_msg) build_summary.append(u'\n{}:'.format(log_msg)) RemoveStage(stage) if not os.path.isdir(stage): build_summary.append( GT(u'Staged build tree removed successfully')) else: build_summary.append( GT(u'Failed to remove staged build tree')) current_step += 1 # *** Show Completion Status *** # wx.YieldIfNeeded() build_progress.Update(steps_count, GT(u'Build completed')) # Show finished dialog for short moment time.sleep(1) # TODO: Add error count to build summary build_progress.Destroy() build_summary = u'\n'.join(build_summary) summary_dialog = DetailedMessageDialog(main_window, GT(u'Build Summary'), ICON_INFORMATION, GT(u'Build completed'), build_summary) summary_dialog.ShowModal() except: build_progress.Destroy() ShowErrorDialog(GT(u'Error occurred during build'), traceback.format_exc()) return
class DistNamesCacheDialog(BaseDialog): def __init__(self): BaseDialog.__init__(self, title=GT(u'Update Dist Names Cache'), style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) self.SetMinSize(wx.Size(300, 150)) txt_types = wx.StaticText(self, label=GT(u'Include the following:')) pnl_types = BorderedPanel(self) self.chk_unstable = wx.CheckBox(pnl_types, label=GT(u'Unstable')) self.chk_obsolete = wx.CheckBox(pnl_types, label=GT(u'Obsolete')) self.chk_generic = wx.CheckBox( pnl_types, label=GT(u'Generic (Debian names only)')) self.btn_preview = wx.Button(self, label=GT(u'Preview cache')) btn_update = wx.Button(self, label=GT(u'Update cache')) btn_clear = wx.Button(self, label=GT(u'Clear cache')) # Keep preview dialog in memory so position/size is saved self.preview = TextPreview(self, title=GT(u'Available Distribution Names'), size=(500, 400)) # Is instantiated as ProgressDialog when OnUpdateCache is called self.progress = None self.timer = DebreateTimer(self) # For setting error messages from other threads self.error_message = None # *** Event Handling *** # self.btn_preview.Bind(wx.EVT_BUTTON, self.OnPreviewCache) btn_update.Bind(wx.EVT_BUTTON, self.OnUpdateCache) btn_clear.Bind(wx.EVT_BUTTON, self.OnClearCache) self.Bind(wx.EVT_TIMER, self.OnTimerEvent) self.Bind(EVT_TIMER_STOP, self.OnTimerStop) # *** Layout *** # lyt_types = BoxSizer(wx.VERTICAL) lyt_types.AddSpacer(5) for CHK in ( self.chk_unstable, self.chk_obsolete, self.chk_generic, ): lyt_types.Add(CHK, 0, lyt.PAD_LR, 5) lyt_types.AddSpacer(5) pnl_types.SetAutoLayout(True) pnl_types.SetSizerAndFit(lyt_types) pnl_types.Layout() lyt_buttons = BoxSizer(wx.HORIZONTAL) lyt_buttons.Add(self.btn_preview, 1) lyt_buttons.Add(btn_update, 1) lyt_buttons.Add(btn_clear, 1) lyt_main = BoxSizer(wx.VERTICAL) lyt_main.Add(txt_types, 0, wx.ALIGN_CENTER | lyt.PAD_LRT, 5) lyt_main.Add(pnl_types, 0, wx.ALIGN_CENTER | lyt.PAD_LR, 5) lyt_main.Add(lyt_buttons, 1, wx.ALIGN_CENTER | wx.ALL, 5) self.SetAutoLayout(True) self.SetSizer(lyt_main) self.Layout() # *** Post-layout Actions *** # if not os.path.isfile(FILE_distnames): self.btn_preview.Disable() if self.Parent: self.CenterOnParent() ## Checks for present error message & displays dialog # # \return # \b \e False if no errors present def CheckErrors(self): if self.error_message: ShowErrorDialog(self.error_message, parent=self, linewrap=410) # Clear error message & return self.error_message = None return True return False ## Deletes the distribution names cache file def OnClearCache(self, event=None): if os.path.isfile(FILE_distnames): os.remove(FILE_distnames) # Update list on changelog page distname_input = GetField(pgid.CHANGELOG, inputid.DIST) if isinstance(distname_input, OwnerDrawnComboBox): distname_input.Set(GetOSDistNames()) self.btn_preview.Disable() cache_exists = os.path.exists(FILE_distnames) self.btn_preview.Enable(cache_exists) return not cache_exists ## Opens cache file for previewing def OnPreviewCache(self, event=None): self.preview.SetValue(ReadFile(FILE_distnames)) self.preview.ShowModal() ## Calls Pulse method on progress dialog when timer event occurs def OnTimerEvent(self, event=None): if self.progress: self.progress.Pulse() ## Closes & resets the progress dialog to None when timer stops def OnTimerStop(self, event=None): if self.progress: self.progress.EndModal(0) self.progress = None return not self.progress ## Creates/Updates the distribution names cache file def OnUpdateCache(self, event=None): try: # Timer must be started before executing new thread self.timer.Start(100) if not self.timer.IsRunning(): self.error_message = GT( u'Could not start progress dialog timer') self.CheckErrors() return False self.Disable() # Start new thread for updating cache in background Thread(self.UpdateCache, None).Start() # Create the progress dialog & start timer # NOTE: Progress dialog is reset by timer stop event self.progress = ProgressDialog( self, message=GT(u'Contacting remote sites'), style=wx.PD_APP_MODAL | wx.PD_AUTO_HIDE) # Use ShowModal to wait for timer to stop before continuing self.progress.ShowModal() self.Enable() if self.CheckErrors(): return False # FIXME: Should check timestamps to make sure file was updated cache_updated = os.path.isfile(FILE_distnames) if cache_updated: distname_input = GetField(pgid.CHANGELOG, inputid.DIST) if isinstance(distname_input, OwnerDrawnComboBox): distname_input.Set(GetOSDistNames()) else: ShowMessageDialog(GT( u'The distribution names cache has been updated but Debreate needs to restart to reflect the changes on the changelog page' ), parent=self, linewrap=410) self.btn_preview.Enable(cache_updated) return cache_updated except: # Make sure dialog is re-enabled self.Enable() # Make sure progress dialog & background thread instances are reset to None if self.progress: self.progress.EndModal(0) self.progress = None cache_exists = os.path.isfile(FILE_distnames) err_msg = GT( u'An error occurred when trying to update the distribution names cache' ) err_msg2 = GT( u'The cache file exists but may not have been updated') if cache_exists: err_msg = u'{}\n\n{}'.format(err_msg, err_msg2) ShowErrorDialog(err_msg, traceback.format_exc(), self) self.btn_preview.Enable(cache_exists) return False ## Method that does the actual updating of the names cache list # # Called from a new thread # FIXME: Show error if could not contact 1 or more remote sites??? def UpdateCache(self, args=None): Logger.Debug(__name__, GT(u'Updating cache ...')) UpdateDistNamesCache(self.chk_unstable.GetValue(), self.chk_obsolete.GetValue(), self.chk_generic.GetValue()) self.timer.Stop()
def OnUpdateCache(self, event=None): try: # Timer must be started before executing new thread self.timer.Start(100) if not self.timer.IsRunning(): self.error_message = GT( u'Could not start progress dialog timer') self.CheckErrors() return False self.Disable() # Start new thread for updating cache in background Thread(self.UpdateCache, None).Start() # Create the progress dialog & start timer # NOTE: Progress dialog is reset by timer stop event self.progress = ProgressDialog( self, message=GT(u'Contacting remote sites'), style=wx.PD_APP_MODAL | wx.PD_AUTO_HIDE) # Use ShowModal to wait for timer to stop before continuing self.progress.ShowModal() self.Enable() if self.CheckErrors(): return False # FIXME: Should check timestamps to make sure file was updated cache_updated = os.path.isfile(FILE_distnames) if cache_updated: distname_input = GetField(pgid.CHANGELOG, inputid.DIST) if isinstance(distname_input, OwnerDrawnComboBox): distname_input.Set(GetOSDistNames()) else: ShowMessageDialog(GT( u'The distribution names cache has been updated but Debreate needs to restart to reflect the changes on the changelog page' ), parent=self, linewrap=410) self.btn_preview.Enable(cache_updated) return cache_updated except: # Make sure dialog is re-enabled self.Enable() # Make sure progress dialog & background thread instances are reset to None if self.progress: self.progress.EndModal(0) self.progress = None cache_exists = os.path.isfile(FILE_distnames) err_msg = GT( u'An error occurred when trying to update the distribution names cache' ) err_msg2 = GT( u'The cache file exists but may not have been updated') if cache_exists: err_msg = u'{}\n\n{}'.format(err_msg, err_msg2) ShowErrorDialog(err_msg, traceback.format_exc(), self) self.btn_preview.Enable(cache_exists) return False
def BuildPrep(self): # Declare these here in case of error before dialogs created save_dia = None prebuild_progress = None try: # List of tasks for build process # 'stage' should be very first task task_list = {} # Control page pg_control = GetPage(pgid.CONTROL) fld_package = GetField(pg_control, inputid.PACKAGE) fld_version = GetField(pg_control, inputid.VERSION) fld_maint = GetField(pg_control, inputid.MAINTAINER) fld_email = GetField(pg_control, inputid.EMAIL) fields_control = ( fld_package, fld_version, fld_maint, fld_email, ) # Menu launcher page pg_launcher = GetPage(pgid.MENU) # Check to make sure that all required fields have values required = list(fields_control) if pg_launcher.IsOkay(): task_list[u'launcher'] = pg_launcher.Get() required.append(GetField(pg_launcher, inputid.NAME)) if not GetField(pg_launcher, chkid.FNAME).GetValue(): required.append(GetField(pg_launcher, inputid.FNAME)) for item in required: if TextIsEmpty(item.GetValue()): field_name = GT(item.GetName().title()) page_name = pg_control.GetName() if item not in fields_control: page_name = pg_launcher.GetName() return (dbrerrno.FEMPTY, u'{} ➜ {}'.format(page_name, field_name)) # Get information from control page for default filename package = fld_package.GetValue() # Remove whitespace package = package.strip(u' \t') package = u'-'.join(package.split(u' ')) version = fld_version.GetValue() # Remove whitespace version = version.strip(u' \t') version = u''.join(version.split()) arch = GetField(pg_control, inputid.ARCH).GetStringSelection() # Dialog for save destination ttype = GT(u'Debian packages') save_dia = wx.FileDialog( self, GT(u'Save'), os.getcwd(), wx.EmptyString, u'{}|*.deb'.format(ttype), wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT | wx.FD_CHANGE_DIR) save_dia.SetFilename(u'{}_{}_{}.deb'.format( package, version, arch)) if not save_dia.ShowModal() == wx.ID_OK: return (dbrerrno.ECNCLD, None) build_path = os.path.split(save_dia.GetPath())[0] filename = os.path.split(save_dia.GetPath())[1].split(u'.deb')[0] # Control, menu, & build pages not added to this list page_checks = ( (pgid.FILES, u'files'), (pgid.SCRIPTS, u'scripts'), (pgid.CHANGELOG, u'changelog'), (pgid.COPYRIGHT, u'copyright'), ) # Install step is not added to this list # 'control' should be after 'md5sums' # 'build' should be after 'control' other_checks = ( (self.chk_md5, u'md5sums'), (self.chk_strip, u'strip'), (self.chk_rmstage, u'rmstage'), (self.chk_lint, u'lintian'), ) prep_task_count = len(page_checks) + len(other_checks) progress = 0 wx.Yield() prebuild_progress = ProgressDialog(GetMainWindow(), GT(u'Preparing to build'), maximum=prep_task_count) if wx.MAJOR_VERSION < 3: # Resize dialog for better fit pb_size = prebuild_progress.GetSizeTuple() pb_size = (pb_size[0] + 200, pb_size[1]) prebuild_progress.SetSize(pb_size) prebuild_progress.CenterOnParent() for PID, id_string in page_checks: wx.Yield() prebuild_progress.Update(progress, GT(u'Checking {}').format(id_string)) wizard_page = GetPage(PID) if wizard_page.IsOkay(): task_list[id_string] = wizard_page.Get() progress += 1 for task_check, id_string in other_checks: wx.Yield() prebuild_progress.Update( progress, GT(u'Testing for: {}').format(task_check.GetLabel())) if task_check.GetValue(): task_list[id_string] = None progress += 1 # Close progress dialog wx.Yield() prebuild_progress.Update(progress) prebuild_progress.Destroy() return (dbrerrno.SUCCESS, (task_list, build_path, filename)) except: if save_dia: save_dia.Destroy() if prebuild_progress: prebuild_progress.Destroy() return (dbrerrno.EUNKNOWN, traceback.format_exc())
def Build(self, task_list, build_path, filename): # Declare this here in case of error before progress dialog created build_progress = None try: # Other mandatory tasks that will be processed mandatory_tasks = ( u'stage', u'install_size', u'control', u'build', ) # Add other mandatory tasks for T in mandatory_tasks: task_list[T] = None task_count = len(task_list) # Add each file for updating progress dialog if u'files' in task_list: task_count += len(task_list[u'files']) # Add each script for updating progress dialog if u'scripts' in task_list: task_count += len(task_list[u'scripts']) if DebugEnabled(): task_msg = GT(u'Total tasks: {}').format(task_count) print(u'DEBUG: [{}] {}'.format(__name__, task_msg)) for T in task_list: print(u'\t{}'.format(T)) create_changelog = u'changelog' in task_list create_copyright = u'copyright' in task_list pg_control = GetPage(pgid.CONTROL) pg_menu = GetPage(pgid.MENU) stage_dir = u'{}/{}__dbp__'.format(build_path, filename) if os.path.isdir(u'{}/DEBIAN'.format(stage_dir)): try: shutil.rmtree(stage_dir) except OSError: ShowErrorDialog( GT(u'Could not free stage directory: {}').format( stage_dir), title=GT(u'Cannot Continue')) return (dbrerrno.EEXIST, None) # Actual path to new .deb deb = u'"{}/{}.deb"'.format(build_path, filename) progress = 0 task_msg = GT(u'Preparing build tree') Logger.Debug(__name__, task_msg) wx.Yield() build_progress = ProgressDialog( GetMainWindow(), GT(u'Building'), task_msg, maximum=task_count, style=PD_DEFAULT_STYLE | wx.PD_ELAPSED_TIME | wx.PD_ESTIMATED_TIME | wx.PD_CAN_ABORT) DIR_debian = ConcatPaths((stage_dir, u'DEBIAN')) # Make a fresh build tree os.makedirs(DIR_debian) progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) def UpdateProgress(current_task, message=None): task_eval = u'{} / {}'.format(current_task, task_count) if message: Logger.Debug(__name__, u'{} ({})'.format(message, task_eval)) wx.Yield() build_progress.Update(current_task, message) return wx.Yield() build_progress.Update(current_task) # *** Files *** # if u'files' in task_list: UpdateProgress(progress, GT(u'Copying files')) no_follow_link = GetField(GetPage(pgid.FILES), chkid.SYMLINK).IsChecked() # TODO: move this into a file functions module def _copy(f_src, f_tgt, exe=False): # NOTE: Python 3 appears to have follow_symlinks option for shutil.copy # FIXME: copying nested symbolic link may not work if os.path.isdir(f_src): if os.path.islink(f_src) and no_follow_link: Logger.Debug( __name__, u'Adding directory symbolic link to stage: {}'. format(f_tgt)) os.symlink(os.readlink(f_src), f_tgt) else: Logger.Debug( __name__, u'Adding directory to stage: {}'.format(f_tgt)) shutil.copytree(f_src, f_tgt) os.chmod(f_tgt, 0o0755) elif os.path.isfile(f_src): if os.path.islink(f_src) and no_follow_link: Logger.Debug( __name__, u'Adding file symbolic link to stage: {}'. format(f_tgt)) os.symlink(os.readlink(f_src), f_tgt) else: if exe: Logger.Debug( __name__, u'Adding executable to stage: {}'.format( f_tgt)) else: Logger.Debug( __name__, u'Adding file to stage: {}'.format(f_tgt)) shutil.copy(f_src, f_tgt) # Set FILE permissions if exe: os.chmod(f_tgt, 0o0755) else: os.chmod(f_tgt, 0o0644) files_data = task_list[u'files'] for FILE in files_data: file_defs = FILE.split(u' -> ') source_file = file_defs[0] target_file = u'{}{}/{}'.format(stage_dir, file_defs[2], file_defs[1]) target_dir = os.path.dirname(target_file) if not os.path.isdir(target_dir): os.makedirs(target_dir) # Remove asteriks from exectuables exe = False if source_file[-1] == u'*': exe = True source_file = source_file[:-1] _copy( source_file, u'{}/{}'.format(target_dir, os.path.basename(source_file)), exe) # Individual files progress += 1 UpdateProgress(progress) # Entire file task progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # *** Strip files ***# # FIXME: Needs only be run if 'files' step is used if u'strip' in task_list: UpdateProgress(progress, GT(u'Stripping binaries')) for ROOT, DIRS, FILES in os.walk(stage_dir): #@UnusedVariable for F in FILES: # Don't check files in DEBIAN directory if ROOT != DIR_debian: F = ConcatPaths((ROOT, F)) if FileUnstripped(F): Logger.Debug(__name__, u'Unstripped file: {}'.format(F)) # FIXME: Strip command should be set as class member? ExecuteCommand(GetExecutable(u'strip'), F) progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) package = GetField(pg_control, inputid.PACKAGE).GetValue() # Make sure that the directory is available in which to place documentation if create_changelog or create_copyright: doc_dir = u'{}/usr/share/doc/{}'.format(stage_dir, package) if not os.path.isdir(doc_dir): os.makedirs(doc_dir) # *** Changelog *** # if create_changelog: UpdateProgress(progress, GT(u'Creating changelog')) # If changelog will be installed to default directory changelog_target = task_list[u'changelog'][0] if changelog_target == u'STANDARD': changelog_target = ConcatPaths( (u'{}/usr/share/doc'.format(stage_dir), package)) else: changelog_target = ConcatPaths( (stage_dir, changelog_target)) if not os.path.isdir(changelog_target): os.makedirs(changelog_target) WriteFile(u'{}/changelog'.format(changelog_target), task_list[u'changelog'][1]) CMD_gzip = GetExecutable(u'gzip') if CMD_gzip: UpdateProgress(progress, GT(u'Compressing changelog')) c = u'{} -n --best "{}/changelog"'.format( CMD_gzip, changelog_target) clog_status = commands.getstatusoutput(c.encode(u'utf-8')) if clog_status[0]: ShowErrorDialog(GT(u'Could not compress changelog'), clog_status[1], warn=True, title=GT(u'Warning')) progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # *** Copyright *** # if create_copyright: UpdateProgress(progress, GT(u'Creating copyright')) WriteFile( u'{}/usr/share/doc/{}/copyright'.format( stage_dir, package), task_list[u'copyright']) progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # Characters that should not be in filenames invalid_chars = (u' ', u'/') # *** Menu launcher *** # if u'launcher' in task_list: UpdateProgress(progress, GT(u'Creating menu launcher')) # This might be changed later to set a custom directory menu_dir = u'{}/usr/share/applications'.format(stage_dir) menu_filename = pg_menu.GetOutputFilename() # Remove invalid characters from filename for char in invalid_chars: menu_filename = menu_filename.replace(char, u'_') if not os.path.isdir(menu_dir): os.makedirs(menu_dir) WriteFile(u'{}/{}.desktop'.format(menu_dir, menu_filename), task_list[u'launcher']) progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # *** md5sums file *** # # Good practice to create hashes before populating DEBIAN directory if u'md5sums' in task_list: UpdateProgress(progress, GT(u'Creating md5sums')) if not WriteMD5(stage_dir, parent=build_progress): # Couldn't call md5sum command build_progress.Cancel() progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # *** Scripts *** # if u'scripts' in task_list: UpdateProgress(progress, GT(u'Creating scripts')) scripts = task_list[u'scripts'] for SCRIPT in scripts: script_name = SCRIPT script_text = scripts[SCRIPT] script_filename = ConcatPaths( (stage_dir, u'DEBIAN', script_name)) WriteFile(script_filename, script_text) # Make sure scipt path is wrapped in quotes to avoid whitespace errors os.chmod(script_filename, 0755) os.system((u'chmod +x "{}"'.format(script_filename))) # Individual scripts progress += 1 UpdateProgress(progress) # Entire script task progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # *** Control file *** # UpdateProgress(progress, GT(u'Getting installed size')) # Get installed-size installed_size = os.popen( (u'du -hsk "{}"'.format(stage_dir))).readlines() installed_size = installed_size[0].split(u'\t') installed_size = installed_size[0] # Insert Installed-Size into control file control_data = pg_control.Get().split(u'\n') control_data.insert(2, u'Installed-Size: {}'.format(installed_size)) progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # Create final control file UpdateProgress(progress, GT(u'Creating control file')) # dpkg fails if there is no newline at end of file control_data = u'\n'.join(control_data).strip(u'\n') # Ensure there is only one empty trailing newline # Two '\n' to show physical empty line, but not required # Perhaps because string is not null terminated??? control_data = u'{}\n\n'.format(control_data) WriteFile(u'{}/DEBIAN/control'.format(stage_dir), control_data, noStrip=u'\n') progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # *** Final build *** # UpdateProgress(progress, GT(u'Running dpkg')) working_dir = os.path.split(stage_dir)[0] c_tree = os.path.split(stage_dir)[1] deb_package = u'{}.deb'.format(filename) # Move the working directory becuase dpkg seems to have problems with spaces in path os.chdir(working_dir) # HACK to fix file/dir permissions for ROOT, DIRS, FILES in os.walk(stage_dir): for D in DIRS: D = u'{}/{}'.format(ROOT, D) os.chmod(D, 0o0755) for F in FILES: F = u'{}/{}'.format(ROOT, F) if os.access(F, os.X_OK): os.chmod(F, 0o0755) else: os.chmod(F, 0o0644) # FIXME: Should check for working fakeroot & dpkg-deb executables build_status = commands.getstatusoutput( (u'{} {} -b "{}" "{}"'.format(GetExecutable(u'fakeroot'), GetExecutable(u'dpkg-deb'), c_tree, deb_package))) progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # *** Delete staged directory *** # if u'rmstage' in task_list: UpdateProgress(progress, GT(u'Removing temp directory')) try: shutil.rmtree(stage_dir) except OSError: ShowErrorDialog(GT( u'An error occurred when trying to delete the build tree' ), parent=build_progress) progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # *** ERROR CHECK if u'lintian' in task_list: UpdateProgress(progress, GT(u'Checking package for errors')) # FIXME: Should be set as class memeber? CMD_lintian = GetExecutable(u'lintian') errors = commands.getoutput((u'{} {}'.format(CMD_lintian, deb))) if errors != wx.EmptyString: e1 = GT(u'Lintian found some issues with the package.') e2 = GT(u'Details saved to {}').format(filename) WriteFile(u'{}/{}.lintian'.format(build_path, filename), errors) DetailedMessageDialog(build_progress, GT(u'Lintian Errors'), ICON_INFORMATION, u'{}\n{}.lintian'.format(e1, e2), errors).ShowModal() progress += 1 # Close progress dialog wx.Yield() build_progress.Update(progress) build_progress.Destroy() # Build completed successfullly if not build_status[0]: return (dbrerrno.SUCCESS, deb_package) if PY_VER_MAJ <= 2: # Unicode decoder has trouble with certain characters. Replace any # non-decodable characters with � (0xFFFD). build_output = list(build_status[1]) # String & unicode string incompatibilities index = 0 for C in build_output: try: GS(C) except UnicodeDecodeError: build_output[index] = u'�' index += 1 build_status = (build_status[0], u''.join(build_output)) # Build failed return (build_status[0], build_status[1]) except: if build_progress: build_progress.Destroy() return (dbrerrno.EUNKNOWN, traceback.format_exc())