Backstory
I collect books. A lot of books. Whitepapers, (programming) cookbooks, transcripts, important letters, overviews, you name it. Usually in PDF, otherwise my house would be too small.
To keep track of those books, I use an arcane collection system based on notes referring to notes. This obviously has some scalability problems, so for the past year I've been trying to build a proper, relational database for it.
It still isn't finished...
However, it's a project in parts and the first part has been ready many moons ago. It also hasn't changed much over the time, so I guess it's ready for review.
The problem
To have a database of books, you want at least 3 things:
- Generate a list from a set of books. A directory goes in, a text file containing all books comes out.
- Eventually, a program will have to read it to extract keywords from it. I'll save that for a later version. The pre-loader.
- Insert or update the list into a database. Create the database if it doesn't exist yet. The CRUDder.
- Produce a view into the database for the user. The viewer.
So, I decided to split those 3 things and give each their own program. Eventually it may end up in one, but with explicit separation. Later on it may also need an automated maintenance program as a 4th module.
The database (3NF) should at least contain the name, authors and publishing year of each book. Later versions may include the size in pages, ISBN and keywords. This program does not yet support this, but is should've been written with those features in mind.
The pre-loader
The pre-loader will take a directory of PDFs. The test set is 222 items large, the final dataset much larger.
For now, only PDFs are supported. If I'd ever want to change that, all I'd have to do is change the following:
if file.lower().endswith(FILE_EXTENSION):
into:
for file_extension in FILE_EXTENSIONS:
if file.lower().endswith(FILE_EXTENSIONS):
and break
out of the for
after handling the first successful hit, but since I'm not sure I'll ever do that the current, simple version will suffice.
All PDFs should be in the following format:
String_String_4digits.pdf
An example book would look like this:
SomeLongBookTitle_AuthorName1&AuthorName2_2017.pdf
Where first there's the title of the book, second there's the authors of the book and last there's the publishing year. Author names should be seperated by ampersand, any other character except an underscore will be considered part of an author's name.
If the author of a book is unknown, it will be UNK
. If a publishing year is unknown, it will be 0000
. Books published before the year 1 anno Domini are not supported.
If the file is not a PDF, it will be unidentified
. Such files may or may not be a problem, but a list is kept. For example, a link may have been created instead of a copy, resulting in a .lnk
instead of a .pdf
.
File names can be very long. Example:
FullyIntegratedMultipurposeLow-CostK-BandFMCWRadarModuleWithSub-milimeterMeasurementPrecision_PiotrKaminski&IzabelaSlomian&KrzysztofWincza&SlawomirGruszczynski_2015.pdf
Book name: 93 characters. File name (incl. extension): 168 characters.
EvaluationOfBatteryAndBatteryChargerShort-CircuitCurrentContributionsToAFaultOnTheDcDistributionSystemAtANuclearPowerPlant_BrookhavenNationalLaboratory_2015.pdf
Book name: 122 characters. File name (incl. extension): 160 characters.
There can be a lot of authors:
TROPOMIATBDOfTheTotalAndTroposphericNO2DataProducts_JHGMVanGefffen&KFBoersma&HJEskes&JDMaasakkers&JPVeefkind_2016.pdf
5 authors.
(That, by the way, is an unfortunate example of improper naming. An artefact of being inconsistent and lacking a thorough naming scheme, but irrelevant for the database.)
The current idea is the program will deliver a list formatted like this:
EffectiveSTL ScottMeyers 0000
ElectricalEngineersReferenceBook MALaughton&DFWarne 2003
ElectroMagneticComplianceWhatIsEFT Microchip 2004
A better approach would perhaps include splitting the authors already so the next step doesn't have to do it. Seems the right thing to do. Perhaps even turning the whole list into a JSON file instead of a text file.
Code
import os
TARGET_DIRECTORY = "Books"
FILE_EXTENSION = ".pdf"
PRINT_UNIDENTIFIED_LIST = True
def print_result(good_count, bad_count, unidentified_list):
length_unidentified_list = len(unidentified_list)
print("Succesful books: {0}".format(good_count))
print("Bad books: {0}".format(bad_count))
print("Unidentified files: {0}".format(length_unidentified_list))
if PRINT_UNIDENTIFIED_LIST and (length_unidentified_list > 0):
print(length_unidentified_list)
print(unidentified_list)
def make_list():
with open("list.txt", 'w') as output_file:
good_count, bad_count = 0, 0
unidentified_list = []
for file in os.listdir(TARGET_DIRECTORY):
# Check against .lower() to also catch
# accidental uppercased file extension
if file.lower().endswith(FILE_EXTENSION):
try:
book_name, author_name, publishing_year = \
file[:-len(FILE_EXTENSION)].split("_")
#print(book_name, author_name, publishing_year)
output_file.write(
"{0} {1} {2}\n".format(
book_name, author_name, publishing_year
)
)
good_count += 1
except ValueError:
# Added indentation for improved UX
print(" Could not handle the following file: {0}".format(file))
print(" Check whether it's name is properly formatted.")
bad_count += 1
else:
unidentified_list += [file]
print_result(good_count, bad_count, unidentified_list)
def main():
make_list()
if __name__ == '__main__':
main()
Yes, I know 1 of my lines is slightly too long according to PEP8. That's the result of the make_list
function failing the Single Responsibility Principle and lack of helper functions.
If that gets fixed, the offending line will probably be fixed in the process.
That's also the reason there are no docstrings yet. The function structure as-is is flawed, even though the current function names are very descriptive.
I've tried putting the results in a dictionary, like this:
result = {
'good_count': 0,
'bad_count': 0,
'unidentified_list': []
}
But it didn't improve readability a bit. I've also tried putting everything in a module and use more specialized functions. That got very messy, very fast.
Anything and everything is up for review, including the design of the whole process.
The current execution time of the program is low enough that I'm not worried, even for larger amounts of data.
python2.7
overpython3.x
? \$\endgroup\$