DogMelon

Lesson 4: Creating Books

filename: lesson04-create_book.py
getName() Lesson 04 - Create Book
getDescription() Lesson 04 - Learn how to create a book


Up until now, we've just been reading our Note Studio data. We haven't modified the Note Studio data in any way. Pretty harmless, really. Consider - if there was something wrong with our plugin, no big deal, we simply won't see the expected results in the output file. Until now, that is.


Now we're going to see how to modify the contents of our library.


It's time to learn more about books. In Lesson 2 we covered how to iterate through your books, and get each book's name. Now we're going to learn how to create a book.

A Word About Collections

Before we get any further, we have to learn a little about how collections work in Note Studio.


Collections are what Note Studio uses to group books together. Each collection is a separate repository of books, with its own location, name and properties. We need to learn about collections, because if you have multiple collections, and you want to create or import a book, you have to decide which collection to add it to.

Collection == DataSource

In the Note Studio Plugin API, a collection is called a 'DataSource'. Whenever you see the word 'DataSource' in the API, just think to yourself 'oooh - I know what that is - that's a collection!'

Iterating Through Your Collections

Here's how you get the number of collections in your library:

	numCollections = theAppData.library.getNumDataSources()

You can discover the name of each collection by iterating through them as follows:

	for i in range( numCollections ):
		name = theAppData.library.getDataSourceFriendlyName( i )

Look familiar? It might - it's the same approach as we used to iterate through both the book names and the page names.


Now, before we go ahead and create a book, we check that you actually have at least one collection. Because you need add a book to an existing collection, there is no point trying to create a book before any collections exist. Here's how we cover ourselves:

	if numCollections == 0:
		outputFile.close()
		return

The 'return' statement exits the 'execute' function, meaning no further actions are taken. Notice that we close the output file first. It's impolite to leave a file open when the plugin exits.

Creating the Book

Again, we defer the actual job of creating the book to a separate function called 'lesson04_createBrandNewBook':

	newBook = lesson04_createBrandNewBook( outputFile )

Interestingly, this is the first time we have written a function which returns a parameter. This function returns the book which gets created, so that we can continue to use that book. Let's look at the entire creation process, step-by-step. First, we arbitratily decide to add the book to the first collection. We don't have to do this, we could look that the names of the collections before choosing one, but for simplicity we'll just use the first:

	collection = theAppData.library.getDataSource( 0 )

You see, when we create a book, we need to tell it which collection it is part of. Next, we create the book:

	book = WikiBook( collection )

Creating a book is easy, but you must remember to pass in the collection as shown. Now, although we've created a book, it isn't part of our library yet. And we haven't really finished setting it up yet. There are some more things we must do before it is an official, ready-to-use book. The order you do these things is important, so in future, you'll have to always do the following steps in the following order. Firstly, you have to give the book a name. Here's how you do it:

	book.setDisplayName( newBookName )

Now, add it to the library. Note that you have to pass two parameters: the WikiBook object and the DataSource object to the 'addBook' function.

	ret = theAppData.library.addBook( book, collection )

the function returns a Boolean value of True if successful, False otherwise. This function might fail if there was already a book of that name in the library. Note that book names must be unique across the entire library, not simply in a collection. It's a strange limitation, but there you have it.


Now, you've added a book, but it's still not valid yet. You need to add a page - specifically, a home page. A book without pages is not a valid object - Note Studio normally prevents you from creating a book without any pages, but with plugins, Note Studio doesn't get to check what you're doing, and you can get yourself into trouble by creating invalid objects.


So, here's how to add and set up a home page for the book:

	homePageName = "My Home Page"
	homePageText = "This is my home page. It worked!"
	book.addPageWithNameAndContents( homePageName, homePageText )
	book.setHomePage(homePageName)

First, we added the page, then, we told the book that the new page was in fact the book's home page. This is the page you'll come back to whenever you press the 'Home' button while browsing this book.


Finally, we return the newly created book:

	return book

Reading and Writing Book Properties

Now we have created a book, let's look at a few more things we can do with a book object. Previously, we've learnt how to read a book's name. Now, we'll learn some more useful information we can retrieve. Here are the first 5 lines of the 'lesson04_getAndSetProperties()' function:

	displayName = book.getDisplayName()
	description = book.getDescription()
	publisher = book.getPublisher()
	isReadOnly = book.getReadOnly()
	isLocked = book.isLocked()

OK, that's a lot of new functions, but they're all pretty simple:

Function Description
getDisplayName() The book's name, as seen in the Library Panel (we've seen this already)
getDescription() The book's description, as seen in the Info Panel
getPublisher() The book's publisher, as see in the Info Panel
getReadOnly() an integer: '1' indicating the book is read-only, '0' meaning it is read-write (the default)
isLocked() an integer: '1' indicating that the book is encrypted, '0' meaning the book is not encrypted.


Notice that we encryption status is checked with a call to 'isLocked', rather than a call to 'isEncrypted'. In the plugin API, what you have thought of as encryption is now called 'locking'. In the plugin API, encryption means something different. We don't have time to examine that further in this Lesson - maybe a future lesson will cover this.


Some of these functions have an equivalent 'set...' method. We now modify some of the book's properties as follows:

	book.setDisplayName( "Renamed: Lesson 4 Book" )
	book.setDescription( "A book created and modified by Plugin Lesson 04" )
	book.setPublisher( "Dogmelon Plugin Tutorial" )

No description should be needed - it's pretty obvious what these routines do! In the downloadable plugin code you can see that we then query the properties, to observe that they have indeed been modified.

Refreshing the Display

Now, if we ended the plugin here, it would be confusing. Why? Because you can't actually see the new book in the Note Studio library panel. It's there, but because you've added it via a plugin, Note Studio doesn't know about it yet. You need to let Note Studio know that it needs to refresh the display. Here's how you do it:

	theAppData.appFrame.onRefresh()

We've just met our second member of 'theAppData'. If you recall that theAppData is the base object which is used to access most Note Studio data. We are familiar with

theAppData.library

which is a WikiLibrary object. Now we introduce

theAppData.appFrame

which is an AppFrame object. We will learn more about this object in later tutorials - here, we just see how to refresh the display.

Important Note: onRefresh() does more than simply refresh the display. It causes all datasources (collections) to be re-read. This means, that if you had modified Note Studio data external to Note Studio, calling onRefresh() would cause the data to be re-read.


Now, here's an important point to remember. In the important note above, we pointed out that refresh caused all the collections to be re-read from the disk. To get technical for a moment, all collection objects are destroyed and re-created. This means that any data-related variable becomes invalided through a refresh.


Now, this might sound complicated, but ordinarily it won't affect things too much. Normally, you would only call onRefresh() at the very end of a plugin, not part-way through. We've done it deliberately to illustrate a point. Therefore, if you look at the full refresh code:

	# Note: newBook is valid
	theAppData.appFrame.onRefresh()
	# Note: newBook is no longer valid - trying to use it
	# would cause a crash

Prior to calling onRefresh(), the variable 'newBook' is valid - it's the book we created earlier. But you can't use it after calling onRefresh(). If you want to do something with that book again, you need to obtain it again. Fortunately, there's an extremely useful routine exactly for that:

	book = theAppData.library.findBook( "Renamed: Lesson 4 Book" 

This returns either a book object, or 'None' if the book could not be found.

Conclusion:

This has been the biggest lesson so far. We've seen a lot more of what you can do with a WikiBook object. We've also had our first encounter with the AppFrame object, using it to refresh our data and the display.

API Functions:

WikiLibrary.addBook( book, collection )

WikiLibrary.findBook( bookName )

WikiLibrary.getNumDataSources()

WikiLibrary.getDataSource( index )

WikiLibrary.getDataSourceFriendlyName( index )

WikiBook( datasource )

WikiBook.addPageWithNameAndContents( pageName, pageContents )

WikiBook.getDescription()

WikiBook.getPublisher()

WikiBook.getReadOnly()

WikiBook.isLocked()

WikiBook.setDisplayName( bookName )

WikiBook.setHomePage( homePageName )

WikiBook.setDescription( description )

WikiBook.setPublisher( publisher )

AppFrame.onRefresh()


View Plugin Source