def DownloadConSeries(self, seriesname) -> bool: # MainConSeriesFrame # Clear out any old information self._grid.Datasource = ConSeries() if seriesname is None or len(seriesname) == 0: # Nothing to load. Just return. return False if self._basedirectoryFTP is None: assert False # Never take this branch. Delete when I'm sure. ProgressMessage(self).Show("Loading " + self.Seriesname + "/index.html from fanac.org") file = FTP().GetFileAsString("/" + self.Seriesname, "index.html") pathname = self.Seriesname + "/index.html" if len(self._basedirectoryFTP) > 0: pathname = self._basedirectoryFTP + "/" + pathname if file is not None: # Get the JSON from the file j = FindBracketedText(file, "fanac-json")[0] if j is None or j == "": Log("DownloadConSeries: Can't load convention information from " + pathname) wx.MessageBox("Can't load convention information from " + pathname) return False try: self.FromJson(j) except (json.decoder.JSONDecodeError): Log("DownloadConSeries: JSONDecodeError when loading convention information from " + pathname) wx.MessageBox( "JSONDecodeError when loading convention information from " + pathname) return False else: # Offer to download the data from Fancy 3 self.Seriesname = seriesname resp = wx.MessageBox( "Do you wish to download the convention series " + seriesname + " from Fancyclopedia 3?", 'Shortcut', wx.YES | wx.NO | wx.ICON_QUESTION) if resp == wx.YES: self.DownloadConSeriesFromFancy(seriesname) if self.TextFancyURL is None or len(self.TextFancyURL) == 0: self.TextFancyURL = "fancyclopedia.org/" + WikiPagenameToWikiUrlname( seriesname) self._grid.MakeTextLinesEditable() self.RefreshWindow() ProgressMessage(self).Show(self.Seriesname + " Loaded", close=True, delay=0.5) return True
def EditConInstancePage(self, instancename: str, irow: int) -> None: if len(instancename) == 0: dlg = wx.TextEntryDialog( None, "Please enter the name of the Convention Instance you wish to create.", "Enter Convention Instance name") if dlg.ShowModal() == wx.CANCEL or len(dlg.GetValue().strip( )) == 0: # Do nothing if the user returns an empty string as name return instancename = dlg.GetValue() if irow >= self._grid.NumRows: self._grid.ExpandDataSourceToInclude(irow, 0) # Add rows if needed with ModalDialogManager(ConInstanceDialogClass, self._basedirectoryFTP + "/" + self.Seriesname, self.Seriesname, instancename) as dlg: dlg.ConInstanceName = instancename # Construct a description of the convention from the information in the con series entry, if any. if irow < self._grid.Datasource.NumRows and len( dlg.ConInstanceTopText.strip()) == 0: row = self._grid.Datasource.Rows[irow] dates = None if row.Dates is not None and not row.Dates.IsEmpty(): dates = str(row.Dates) locale = None if row.Locale is not None and len(row.Locale) > 0: locale = row.Locale description = instancename if dates is not None and locale is not None: description += " was held " + dates + " in " + locale + "." elif dates is not None: description += " was held " + dates + "." elif locale is not None: description += " was held in " + locale + "." if row.GoHs is not None and len(row.GoHs) > 0: gohs = row.GoHs.replace("&", "&") if ("," in gohs and not ", jr" in gohs ) or "&" in gohs or " and " in gohs: description += " The GoHs were " + gohs + "." else: description += " The GoH was " + gohs + "." dlg.ConInstanceTopText = description dlg.ConInstanceName = instancename dlg.ConInstanceFancyURL = "fancyclopedia.org/" + WikiPagenameToWikiUrlname( instancename) dlg.MarkAsSaved() dlg.RefreshWindow() if dlg.ShowModal() == wx.ID_OK: if self._grid.Datasource.NumRows <= irow: for i in range(irow - self._grid.Datasource.NumRows + 1): self._grid.Datasource.Rows.append(Con()) self._grid.Datasource.Rows[irow].Name = dlg.ConInstanceName self._grid.Datasource.Rows[irow].URL = dlg.ConInstanceName self.RefreshWindow()
def ConTextConSeriesKeyUp(self, event): # MainConSeriesFrame self.TextFancyURL = "fancyclopedia.org/" + WikiPagenameToWikiUrlname( self.tConSeries.GetValue())
def FetchConSeriesFromFancy(self, name, retry: bool = False ) -> bool: # MainConSeriesFrame if name is None or name == "": return False wait = wx.BusyCursor( ) # The busy cursor will show until wait is destroyed pageurl = "https://fancyclopedia.org/" + WikiPagenameToWikiUrlname( name) try: response = urlopen(pageurl) except: del wait # End the wait cursor Log("FetchConSeriesFromFancy: Got exception when trying to open " + pageurl) if not retry: dlg = wx.TextEntryDialog( None, "Load failed. Enter a different name and press OK to retry.", "Try a different name?", value=name) if dlg.ShowModal() == wx.CANCEL or len( dlg.GetValue().strip()) == 0: return False response = dlg.GetValue() return self.FetchConSeriesFromFancy(response) self._fancydownloadfailed = True return False html = response.read() soup = BeautifulSoup(html, 'html.parser') del wait # End the wait cursor tables = soup.find_all("table", class_="wikitable mw-collapsible") if tables == None or len(tables) == 0: msg = "Can't find a table in Fancy 3 page " + pageurl + ". Is it possible that its name on Fancy 3 is different?" Log(msg) self._fancydownloadfailed = True wx.MessageBox(msg) return False bsrows = tables[0].find_all("tr") headers = [] rows = [] for bsrow in bsrows: if len(headers) == 0: #Save the header row separately heads = bsrow.find_all("th") if len(heads) > 0: for head in heads: headers.append(head.contents[0]) headers = [ RemoveAllHTMLTags(UnformatLinks(str(h))).strip() for h in headers ] continue # Ordinary row cols = bsrow.find_all("td") row = [] print("") if len(cols) > 0: for col in cols: row.append( RemoveAllHTMLTags(UnformatLinks(str(col))).strip()) if len(row) > 0: rows.append(row) # Did we find anything? if len(headers) == 0 or len(rows) == 0: Log("FetchConSeriesFromFancy: Can't interpret Fancy 3 page '" + pageurl + "'") self._fancydownloadfailed = True wx.MessageBox("Can't interpret Fancy 3 page '" + pageurl + "'") return False # OK. We have the data. Now fill in the ConSeries object # First, figure out which columns are which nname = FindIndexOfStringInList(headers, "Convention") if nname is None: nname = FindIndexOfStringInList(headers, "Name") ndate = FindIndexOfStringInList(headers, "Dates") if ndate is None: ndate = FindIndexOfStringInList(headers, "Date") nloc = FindIndexOfStringInList(headers, "Location") if nloc is None: nloc = FindIndexOfStringInList(headers, "Site, Location") if nloc is None: nloc = FindIndexOfStringInList(headers, "Site, City") if nloc is None: nloc = FindIndexOfStringInList(headers, "Site") ngoh = FindIndexOfStringInList(headers, "GoHs") if ngoh is None: ngoh = FindIndexOfStringInList(headers, "GoH") if ngoh is None: ngoh = FindIndexOfStringInList(headers, "Guests of Honor") if ngoh is None: ngoh = FindIndexOfStringInList(headers, "Guests") for row in rows: if len(row) != len( headers ): # Merged cells which usually signal a skipped convention. Ignore them. continue con = Con() if nname is not None: con.Name = row[nname] if ndate is not None: con.Dates = FanzineDateRange().Match(row[ndate]) if nloc is not None: con.Locale = row[nloc] if ngoh is not None: con.GoHs = row[ngoh] self._grid.Datasource.Rows.append(con) self.Seriesname = name self._fancydownloadfailed = False self.RefreshWindow() return True
def UploadConSeries(self) -> bool: # MainConSeriesFrame # First read in the template try: with open(PyiResourcePath("Template-ConSeries.html")) as f: file = f.read() except: wx.MessageBox("Can't read 'Template-ConSeries.html'") return False # Delete any trailing blank rows. (Blank rows anywhere are as error, but we only silently drop trailing blank rows.) # Find the last non-blank row. last = None for i, row in enumerate(self._grid.Datasource.Rows): if len((row.GoHs + row.Locale + row.Name + row.URL).strip()) > 0 or not row.Dates.IsEmpty: last = i # Delete the row or rows following it if last is not None and last < self._grid.Datasource.NumRows - 1: del self._grid.Datasource.Rows[last + 1:] # Determine if we're missing 100% of the data for the Dates, Location, or GoH columns so we can drop them from the listing ProgressMessage(self).Show("Uploading /" + self.Seriesname + "/index.html") # We want to do substitutions, replacing whatever is there now with the new data # The con's name is tagged with <fanac-instance>, the random text with "fanac-headertext" link = FormatLink( "https://fancyclopedia.org/" + WikiPagenameToWikiUrlname(self.Seriesname), self.Seriesname) file = SubstituteHTML(file, "title", self.Seriesname) file = SubstituteHTML(file, "fanac-instance", link) file = SubstituteHTML(file, "fanac-headertext", self.TextComments) showempty = self.m_radioBoxShowEmpty.GetSelection( ) == 0 # Radio button: Show Empty cons? hasdates = len([ d.Dates for d in self._grid.Datasource.Rows if d.Dates is not None and not d.Dates.IsEmpty() ]) > 0 haslocations = len([ d.Locale for d in self._grid.Datasource.Rows if d.Locale is not None and len(d.Locale) > 0 ]) > 0 hasgohs = len([ d.GoHs for d in self._grid.Datasource.Rows if d.GoHs is not None and len(d.GoHs) > 0 ]) > 0 # Now construct the table which we'll then substitute. newtable = '<table class="table" id="conseriestable">\n' newtable += " <thead>\n" newtable += ' <tr id="conseriestable">\n' newtable += ' <th scope="col">Convention</th>\n' if hasdates: newtable += ' <th scope="col">Dates</th>\n' if haslocations: newtable += ' <th scope="col">Location</th>\n' if hasgohs: newtable += ' <th scope="col">GoHs</th>\n' newtable += ' </tr>\n' newtable += ' </thead>\n' newtable += ' <tbody>\n' for row in self._grid.Datasource.Rows: if (row.URL is None or row.URL == "") and not showempty: # Skip empty cons? continue newtable += " <tr>\n" if row.URL is None or row.URL == "": newtable += ' <td>' + row.Name + '</td>\n' else: newtable += ' <td>' + FormatLink( RemoveAccents(row.URL) + "/index.html", row.Name) + '</td>\n' if hasdates: newtable += ' <td>' + str( row.Dates) if not None else "" + '</td>\n' if haslocations: newtable += ' <td>' + row.Locale + '</td>\n' if hasgohs: newtable += ' <td>' + row.GoHs + '</td>\n' newtable += " </tr>\n" newtable += " </tbody>\n" newtable += " </table>\n" file = SubstituteHTML(file, "fanac-table", newtable) file = SubstituteHTML(file, "fanac-json", self.ToJson()) file = SubstituteHTML( file, "fanac-date", datetime.now().strftime("%A %B %d, %Y %I:%M:%S %p") + " EST") # Now try to FTP the data up to fanac.org if self.Seriesname is None or len(self.Seriesname) == 0: Log("UploadConSeries: No series name provided") return False if not FTP().PutFileAsString( "/" + self.Seriesname, "index.html", file, create=True): wx.MessageBox("Upload failed") return False UpdateLog().LogText("Uploaded ConSeries: " + self.Seriesname) ProgressMessage(self).Show("Upload succeeded: /" + self.Seriesname + "/index.html", close=True, delay=0.5) self.MarkAsSaved( ) # It was just saved, so unless it's updated again, the dialog can exit without uploading self._uploaded = True # Something's been uploaded self.RefreshWindow() return True
def OnTextConInstanceNameKeyUp(self, event): self.ConInstanceFancyURL = "fancyclopedia.org/" + WikiPagenameToWikiUrlname( self.tConInstanceName.GetValue().strip()) self.RefreshWindow()
def OnUploadConInstancePage(self) -> None: # Delete any trailing blank rows. (Blank rows anywhere are as error, but we only silently drop trailing blank rows.) # Find the last non-blank row. last = None for i, row in enumerate(self._grid.Datasource.Rows): if len((row.SourceFilename + row.SiteFilename + row.DisplayTitle + row.Notes).strip()) > 0: last = i # Delete the row or rows following it if last is not None and last < self._grid.Datasource.NumRows - 1: del self._grid.Datasource.Rows[last + 1:] # Check to see if the data is valid error = False for i, row in enumerate(self._grid.Datasource.Rows): # Valid data requires # If a text row, that some text exists # If an external link row, that text and a properly formed URL exists (but does not check to see target exists) # For a file, that there is an entry in the "Source File Name", "Site Name", and "Display Name" columns if row.IsText: if len((row.SourceFilename + row.SiteFilename + row.DisplayTitle + row.Notes).strip()) == 0: error = True Log("Malformed text row: #" + str(i) + " " + str(row)) for j in range(self._grid.NumCols): self._grid.SetCellBackgroundColor(i, j, Color.Pink) elif row.IsLink: if len(row.URL.strip()) == 0 or len( row.DisplayTitle.strip()) == 0: error = True Log("Malformed link row: #" + str(i) + " " + str(row)) for j in range(self._grid.NumCols): self._grid.SetCellBackgroundColor(i, j, Color.Pink) else: if len(row.SourceFilename.strip()) == 0 or len( row.SiteFilename.strip()) == 0 or len( row.DisplayTitle.strip()) == 0: error = True Log("Malformed file row: #" + str(i) + " " + str(row)) for j in range(self._grid.NumCols): self._grid.SetCellBackgroundColor(i, j, Color.Pink) if error: self._grid.Grid.ForceRefresh() wx.MessageBox("Malformed row found") return # Read in the template file = None try: Log("sys.path[0]= " + sys.path[0]) Log("sys.argv[0]= " + sys.argv[0]) Log("os.path.join(sys.path[0], 'Template-ConPage.html')= " + os.path.join(sys.path[0], "Template-ConPage.html")) with open(PyiResourcePath("Template-ConPage.html")) as f: file = f.read() except: wx.MessageBox("Can't read 'Template-ConPage.html'") Log("Can't read 'Template-ConPage.html'") return ProgressMessage(self).Show("Uploading /" + self._seriesname + "/" + self._coninstancename + "/index.html") # We want to do substitutions, replacing whatever is there now with the new data # The con's name is tagged with <fanac-instance>, the random text with "fanac-headertext" fancylink = FormatLink( "https://fancyclopedia.org/" + WikiPagenameToWikiUrlname(self.ConInstanceName), self.ConInstanceName) file = SubstituteHTML(file, "title", self.ConInstanceName) file = SubstituteHTML(file, "fanac-instance", fancylink) file = SubstituteHTML(file, "fanac-stuff", self.ConInstanceTopText) # Fill in the top buttons s="<button onclick=\"window.location.href='https://fancyclopedia.org/"+WikiPagenameToWikiUrlname(self.ConInstanceName)+"'\"> Fancyclopedia 3 </button> "+ \ "<button onclick=\"window.location.href='..'\">All "+self._seriesname+"s</button>" file = SubstituteHTML(file, "fanac-topbuttons", s) # If there are missing page counts for pdfs, try to gett hem. (This can eventually be eliminated as there will be no pre-V7 files on the server.) self.FillInMissingPDFPageCounts() file = SubstituteHTML(file, "fanac-json", self.ToJson()) file = SubstituteHTML( file, "fanac-date", datetime.now().strftime("%A %B %d, %Y %I:%M:%S %p") + " EST") if len(self.Credits.strip()) > 0: file = SubstituteHTML( file, "fanac-credits", 'Credits: ' + self.Credits.strip() + "<br>") #<p id="randomtext"><small> +'</small></p>' def FormatSizes(row) -> str: info = "" if row.Size > 0 or (row.Pages is not None and row.Pages > 0): info = "<small>(" if row.Size > 0: info += "{:,.1f}".format(row.Size / (1024**2)) + ' MB' if row.Pages is not None and row.Pages > 0: if row.Size > 0: info += "; " info += str(row.Pages) + " pp" info += ")</small>" return info showExtensions = self.radioBoxShowExtensions.GetSelection() != 0 def MaybeSuppressPDFExtension(fn: str, suppress: bool) -> str: if suppress: parts = os.path.splitext(row.DisplayTitle) if parts[1].lower() in [ ".pdf", ".jpg", ".png", ".doc", ".docx" ]: fn = parts[0] return fn if self.radioBoxFileListFormat.GetSelection( ) == 0: # Are we to output a table? # Now construct the table which we'll then substitute. newtable = '<table class="table" id="conpagetable">\n' newtable += " <thead>\n" newtable += " <tr>\n" newtable += ' <th scope="col">Document</th>\n' newtable += ' <th scope="col">Size</th>\n' newtable += ' <th scope="col">Notes</th>\n' newtable += ' </tr>\n' newtable += ' </thead>\n' newtable += ' <tbody>\n' for i, row in enumerate(self._grid.Datasource.Rows): newtable += " <tr>\n" # Display title column if row.IsText: newtable += ' <td colspan="3">' + row.SourceFilename + " " + row.SiteFilename + " " + row.DisplayTitle + " " + row.Notes + '</td>\n' elif row.IsLink: newtable += ' <td colspan="3">' + FormatLink( row.URL, row.DisplayTitle) + '</td>\n' else: # The document title/link column s = MaybeSuppressPDFExtension(row.DisplayTitle, showExtensions) newtable += ' <td>' + FormatLink(row.SiteFilename, s) + '</td>\n' # This is the size & page count column newtable += ' <td>' + FormatSizes(row) + '</td>\n' # Notes column info = ' <td> </td>\n' if len(row.Notes) > 0: info = ' <td>' + str(row.Notes) + '</td>\n' newtable += info newtable += " </tr>\n" newtable += " </tbody>\n" newtable += " </table>\n" else: # Output a list # Construct a list which we'll then substitute. newtable = '<ul id="conpagetable">\n' for row in self._grid.Datasource.Rows: if row.IsText: text = row.SourceFilename + " " + row.SiteFilename + " " + row.DisplayTitle + " " + row.Notes newtable += ' </ul><b>' + text.strip( ) + '</b><ul id="conpagetable">\n' elif row.IsLink: newtable += ' <li id="conpagetable">' + FormatLink( row.URL, row.DisplayTitle) + "</li>\n" else: s = MaybeSuppressPDFExtension(row.DisplayTitle, showExtensions) newtable += ' <li id="conpagetable">' + FormatLink( row.SiteFilename, s) val = FormatSizes(row) if len(val) > 0: newtable += ' ' + val + '\n' else: newtable += ' (--)\n' # Notes if len(row.Notes) > 0: newtable += " (" + str(row.Notes) + ")" newtable += "</li>\n" newtable += " </ul>\n" file = SubstituteHTML(file, "fanac-table", newtable) if not FTP().PutFileAsString( "/" + self._seriesname + "/" + self._coninstancename, "index.html", file, create=True): Log("Upload failed: /" + self._seriesname + "/" + self._coninstancename + "/index.html") wx.MessageBox("OnUploadConInstancePage: Upload failed: /" + self._seriesname + "/" + self._coninstancename + "/index.html") ProgressMessage(self).Close() return wd = "/" + self._seriesname + "/" + self._coninstancename FTP().CWD(wd) for delta in self.conInstanceDeltaTracker.Deltas: if delta.Verb == "add": ProgressMessage(self).Show("Adding " + delta.Con.SourcePathname + " as " + delta.Con.SiteFilename) Log("delta-ADD: " + delta.Con.SourcePathname + " as " + delta.Con.SiteFilename) FTP().PutFile(delta.Con.SourcePathname, delta.Con.SiteFilename) elif delta.Verb == "rename": ProgressMessage(self).Show("Renaming " + delta.Oldname + " to " + delta.Con.SiteFilename) Log("delta-RENAME: " + delta.Oldname + " to " + delta.Con.SiteFilename) FTP().Rename(delta.Oldname, delta.Con.SiteFilename) elif delta.Verb == "delete": if not delta.Con.IsText and not delta.Con.IsLink: ProgressMessage(self).Show("Deleting " + delta.Con.SiteFilename) Log("delta-DELETE: " + delta.Con.SiteFilename) if len(delta.Con.SiteFilename.strip()) > 0: FTP().DeleteFile(delta.Con.SiteFilename) elif delta.Verb == "replace": ProgressMessage(self).Show("Replacing " + delta.Oldname + " with new/updated file") Log("delta-REPLACE: " + delta.Con.SourcePathname + " <-- " + delta.Oldname) Log(" delta-DELETE: " + delta.Con.SiteFilename) if len(delta.Con.SiteFilename.strip()) > 0: FTP().DeleteFile(delta.Con.SiteFilename) Log(" delta-ADD: " + delta.Con.SourcePathname + " as " + delta.Con.SiteFilename) FTP().PutFile(delta.Con.SourcePathname, delta.Con.SiteFilename) else: Log("delta-UNRECOGNIZED: " + str(delta)) UpdateLog().Log(self._seriesname, self._coninstancename, self.conInstanceDeltaTracker) self.conInstanceDeltaTracker = ConInstanceDeltaTracker( ) # The upload is complete. Start tracking changes afresh ProgressMessage(self).Show("Upload succeeded: /" + self._seriesname + "/" + self._coninstancename + "/index.html", close=True, delay=0.5) self.MarkAsSaved() self.Uploaded = True self.RefreshWindow()