ocarina/playlist/ was the last subdirectory remaining in the ocarina
code, and I can finally remove it. Thank you GtkBuilder! At this
point, there is only one ocarina header file, so I move it to the main
ocarina directory.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This lets me remove the "if treeview is focused" special cases that keep
popping up in the window keypress handler.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Disabled playlists are ignored when picking the next song. I grey-out
the widgets on the UI when this notification is received. I keep
filtering enabled this time (the entry was disabled too in Ocarina 5.9)
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I put it on the tab page this time, instead of in the tab label. I
don't want wide tab labels, so I should eventually come up with a way of
closing playlists without having to change tabs. Maybe a right-click
menu?
I also noticed that the libsaria delete_playlist() code didn't work, so
I simplified it a bit.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
It's easier to refilter automatically after setting the filter text,
rather than going through a bunch of function pointers to change the filter.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
The first time Escape is pressed, rows are unselected. The second time
Escape is pressed, the toplevel window is selected.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Instead of using a "on_new_playlist()" function, I now use the
notification system to tell the gui that a new playlist has been
created. For now I just put it on the front of the tab list.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Dynamic playlists are going to need to run the same code to generate
playlist tabs as the static tabs. Since I don't know how to use
GtkBuilder fragments, instead I write the code for generating each page.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
One for managing the notebook tabs (tabs.cpp) and one for managing a
specific playlist page (playlist.cpp).
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Since I don't have dynamic playlists re-implemented yet, I just swtch
between the library, recent, and banned playlists.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I have to track if a filter-text entry has focus to do this properly,
otherwise using a shortcut key while searching a playlist will trigger
that shortcut.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This was in body/playlist.cpp, but I eventually plan on removing this
file so the code I'm using from there needs to be moved somewhere else.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This notification is sent when a track in the playlist is updated, such
as when the play count is incremented. I also remove the
track_updated() functions from the library renderer and ocarina::Playlist.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This class is slowly going away, and these functions have already been
reimplemented using my GtkBuilder interface.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Dynamic playlists are disabled right now, so I'm making use of this time
to clear out old code. This patch removes library renderer functions
that have already been reimplemented.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
My old code was doing this by looking for widgets getting mapped or
unmapped. This was stupid, so now I'm using the GtkNotebook switch-page
signal.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Used to notify the UI that the playlist size has changed. Other options
include: Set the size automatically when responding to PLAYLIST_ADD (and
evenutally "PLAYLIST_RM")
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This tab is appended at the end of the notebook, so it looks as if there
are two Library tabs. I'm going to use notifications and GtkBuilder on
the new tab to remove the PlaylistRenderer and to hopefully make adding
new features easier. Right now only inserting tracks into the
LibraryPlist works, and I eventually want to remove the LibraryPlist
liststore in favor of a custom treemodel.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I initially made this class so that multiple front ends could be used at
once and all receive the same notifications. I see now that this was a
stupid idea, since I only need to keep one list of the library. My
notify() function does the same stuff without the need for a driver
list.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Any void * will do, or even NULL! Try to keep notify.h up-to-date with
what argument is passed and please don't send out the same notification
with multiple argument types.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Right now it only notifies that an event happened. I think I should
eventually pass in an additional argument related to the event that
happened. This will allow me to send out notifications for preferences
or library paths changing.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Clicking them doesn't do anything yet, and the buttons are
non-functional as well. But paths are displayed as they're configured!
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I modified the code slightly from what I used to have. The biggest
difference is changing the counter will always enable automatic pausing,
so I no longer need the extra click to enable this feature.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I check the size of the idle queue during the timeout poll and then
enable the progress bar. When the queue size reaches zero I hide the
bar again.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
The shortcuts were hiding more than they probably should have, and I
didn't have much using them. The only function in window.h that I'm
going to use is init_window(), so I'll just declare it in ocarina.h.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I wasn't doing anything with these, so they're just getting in the way
and increasing complexity. This change also allows me to remove the
"enabled" variable from the idle queue.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This makes more sense than pushing a single track into a list and then
iterating over the single item...
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I was using multiple boolean fields, but I think it'll be easier to set
up a single bit-flag field instead.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I kept around the old list while I was converting everything over to the
new list. Now that I support all the needed features, I can remove the
old variable.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
- The erase() function returns the previous item
- Implement a push_front() function
- Implement a pop_front() function
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Not really C++ iterators, but close enough. I can get the first and
last values values to use in for loops, I also made a next() function
for list items.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This function takes a function pointer argument that should return
"true" if the current item is the item we're looking for and "false"
otherwise.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Return the "count" variable and hope we don't have more than
MAX_UNSIGNED_INT objects in the list...
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I free the memory in the destructor, too! Be careful, though, the copy
constructor isn't actually a copy constructor.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I'm going to gradually replace the stl list with my own implementation.
This should make everything more efficient and I won't have to deal with
those pesky iterators.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I copy images and other files to lib/ocarina/ during the build to make
installing easier. The lib directory should always be relative to the
bin (executable) path, so I can easily find the lib path based on
executable path (/proc/self/exe).
I can also use this to find bugs in the buld process, such as adding a
file to the images/ directory but forgetting to add it as an install
target.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This lets me pull code out of the generic Sconstruct, so it can't be
bad! I also removed the test directory since I never made unit tests.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Having tracks add themselves to the recent playlist means that it also
adds itself when going backwards through the playlist, resetting the
cur iterator and causing the same 2 songs to be played. To get around
this, I gave tracks a new load_unlisted() function to load without
adding themselves to the recently played list.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I created a new renderer function for updating tracks when they change.
Using this, I can show the new playcount of tracks as it is incremented.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This allows me to restore it when restarting ocarina. I also need to
notify the renderer that the playlist status has changed so I can change
the button state.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Right now, this just toggles the "banned" field in the Track class and
then skips to the next track.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I plan on using this to track if the user has banned a track or not.
This patch introduces the new field and handles the library version
upgrade.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Scripts should write commands to a temporary file and then write the
path to that file to the application pipe. I can then write results of
the command to this file before exiting.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I'm going to use the pipe with shell scripts for remote-controlling
ocarina (possibly through a web interface).
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Passing individual variables through works for a small number of
variables, but passing a struct makes it easier to add new arguments.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
On the libsaria side, I created a function for finding and deleting a
playlist pointer. On the ocarina side, I added a button to each
playlist tab to remove the tab.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Used for passing extra data through the button clicked callback. I also
use this patch to remove the extra GdkEvent argument passed to buttons,
since this argument isn't actually part of the callback...
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I do this after choosing the next song and after tracks are removed from
each playlist. This allows me to remove playlists as they empty.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Before I was only adding tracks that were picked through the
deck::next() function. Now I have the track add itself to the recent
list (except when I'm playing an outside track).
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This should set the gst pipeline to the correct state when the song is
loaded, rather than pausing after telling it to play.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Derived classes need to implement a do_add_tracks() function instead so
I have a single place to notify the renderer and save the playlist.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
- The old header functions
- The old callback header file
- The old shortcut registering system
- The old main() code
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I've been wanting to do this for a long time. I don't think I need it
anymore, so it can all be safely removed.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
The "S" or "Q" shortcut will create a new playlist on the front of the
deck, "s" or "q" creates a shortcut on the back of the deck.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I don't want to keep creating functions for doing things to playlists.
Instead, I'd rather just get the playlist directly.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I create directories with numbered files for the library and playlist,
this patch creates generic code for reading them during startup.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I do this in another idle task, I also had to give the library a
function to find tracks based on (libid, trackid).
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I do this whenever the playlist changes (tracks added, removed or
playlist renumbered). When playlists are deleted I remove the file. I
also remove the file when new_number < cur_number.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
From index.cpp to filter.cpp. I also added in an "is_visible()"
function for testing visibility of tracks.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Right now I just add and remove songs from the index as they are added
and removed from the playlist.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Right now I just pause after the current song, but I eventually plan on
adding controls to pause after N songs.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I keep a counter that is decremented after every call to next(). When
it reaches zero, pause after loading the next song.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
- Delete the library file
- Remove tracks from each playlist
- Notify the renderer that tracks have been removed
- Notify library drivers that the path has been removed
- Remove the path from the list
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
- Renumber when new playlists are either added or removed
- Finding nth playlist is much easier now
- Insert new playlists right before the library
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I'm going to make it less stack-ish because I was getting confused.
Turns out people don't think people have a hard time thinking about song
order starting from the end... :-(
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
0 - 9: Switch to playlist N
L / l: Switch to library
R / r: Switch to recent songs
N / n: Play next song
P / p: Play previous song
Escape: Give main window focus
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This way people can see what has played recently and directly select
from a list. Generic playlist classes are awesome!
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
- Only works for new sets
- Set a callback function when created
- Add a right-click menu to the ocarina treeview
- Remove songs from playlist when picking next song
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I now only have a single instance of the control buttons, so the old
code can be removed now. The footer has been completely replaced by now.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
- Change WriteTask() to take an extra void pointer argument
- Pass library path pointer through WriteTask
- Store tracks to file named after library id.
- Remove newline from tags
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I plan on using (library, track) ids as a way of storing playlists. I
made both counters unsigned ints, since I'm willing to bet that people
won't have 4,294,967,295 songs or library paths...
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I'll eventually allow more playlists to be stacked, but for now picking
from the library is easy. I may eventually add in a way to pick
sequentially from a set.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I now have direct access to the track that was selected, so I created a
dummy function to play it (to be filled in soon).
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
For sets, I keep a sorted list and merge-sort the new songs. I also
created insert_prepare() and insert_done() functions to tell the UI that
we are about to insert (this gives me a chance to call the
freeze_child_notify() and thaw_child_notify() in GTK).
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Right now this must be set when a new playlist is constructed. I can
eventually change this to use a default name based on what type of
playlist has been created.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
The renderer doesn't actually render anything yet, but it is statically
defined in ocarina/playlist.cpp and it will eventually display the
library.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This makes more sense to me conceptually than having a class called
PlaylistRenderer. I also commented out a lot of code, so the renderer
doesn't do anything right now.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I'm going to re-implement my old SongList class as a PlaylistRenderer,
so for now I create a new directory and a new header file.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I sort the subset of tracks to be added, and then use insertion sort to
add them to the playlist object.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I don't pull out the audio properties yet, but I do find artist, album,
title and everything else that I can.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This will eventually be used to determine if the track is visible based
on library path visibility.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This gives the UI a chance to set the new path or new size. I also
updated Ocarina to show the library path size in its liststore row.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This also removed the other `typedef sid_t` left in the header file.
I'll re-add in functions and variables as I need them, and hopefully
keep things working more efficiently.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I was using the sid_t to lookups for tracks and library paths. I think
I can simplify things by storing pointers in the UI rather than using
id numbers. This will give me direct access to whatever it is I want to
manipulate.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I created a new "list_dir()" function to recursively list directory
contents. I plan on using this to find songs to add to the library, but
it could also be modified to read playlists and library paths in the
appdir.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Whenever a task is queued, I increment a counter. When it is run, I
increment a different counter. This allows me to track the idle queue
completion percentage. I also created a progress bar for Ocarina to
display in the toes of the footer.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This allows other tools to be written to modify the library of a
currently running program without having to create a library driver
instance.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I'm going to need a polling loop for the progress bar. I might as well
only create one poll() function that is used to do everything...
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I want to eventually use this to create a library file for each library
path. The "visible" field will be used to enable and disable different
paths during run time.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
The UI calls the library::add_path() function to create a new
LibraryPath structure to be managed by the library.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I want to gradually re-add everything and clean it up as I go along. To
help with this, I comment out the old code so I can use it as a
reference while writing new code.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I'm going to use a ribbon-ish interface for the footer and work the
settings pages in with the now playing widgets to make things easier to
find. Right now, I created everything with placeholders.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I'm not using a notebook as the main widget anymore. I'm also setting
the content directly, rather than getting the widget from the body code
and setting it in the window code.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This is one of the first things that happens when initializing libsaria.
This also allows me to remove the remaining commented out code from the
preferences file.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I made a WriteTask class to use for saving files. This allows me to
create a file in the appdir and write out the new preferences if they've
changed.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I do this from the window init function using default values, rather
than pass it as a parameter to the init function.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I combined the ocarina.h and gtk.h header files into one single file. I
also began writing new window managing code under a window namespace for
code separation.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I'm going to replace my old path lookup code with something designed
around accessing files in a pre-set directory. I'm also moving back to
putting files in the home diretory, rather than the XDG_CONFIG directory.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Instead, I pass a string with the name of the application being run.
This will be used to have separate applications using libsaria.
This commit also comments out most of the libsaria and ocarina init
functions until I can revise and clean up everything.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I eventually plan on creating more cpp binaries that link with libsaria
for pipe actions and other things. It's helpful to only initialize the
parts of libsaria that I intend to use.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
The UI shortcuts may remove songs from the queue with no way of
notifying the stack layer. The UI should call the stack_top_pop()
function to check if the top playlist is empty and ready for removal.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Now renderers don't need to track this on their own, and renderers will
update as tracks are added.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Not all renderers are created dynamically, so I made a way of telling
the playlist system if renderers should be deleted.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I only allow one queue instance right now, but this allows me to remove
the queue directory since it is no longer needed. I also removed the
QUEUE_REFRESH callback since all this is handled through the libsaria
playlist code. All that's left is reloading saved playlists on startup.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Right now it's just for a single queue, but I should be able to expand
this once I enable more stacking in the backend.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This allows for bookkeeping and other cleanup (schedule playlist
removal, remove from UI, and so on).
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This patch does two things. First, I remove the Page class and let each
notebook page manage itself. Second, I created functions to add the
header and footer to a GtkWidget and then prepend the page in the
notebook (so it adds pages as if it was a stack).
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
My goal is to eventually pick songs from whatever is on top of the
playlist stack. Right now, only the library is added and no songs are
picked.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This replaces the "remove id" ability and replaces it with a "remove
index" ability. Removing an index will remove the correct song in the
case that the same song has been added multiple times. I also modified
the UI to remove rows at a specific index.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I can get everything I need using playlists and renderers, so I can
safely remove this bit of code (I never really liked it anyway...). I
have to keep around the queue::size() function for a little bit longer,
since choosing the next song still uses it.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I don't need these now that I have playlist renderers, so I might as
well rip out the code while I'm thinking about it.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Rather than exposing iterator functions, I now have a function to walk
the playlist and call the renderer insert() function for each track.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This class will be given a Playlist and will be in charge of calling
Playlist functions. I eventually plan on expanding this to allow the
playlist to call renderer functions instead of using my current callback
system.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I don't think I'll need this once I implement dynamic, stackable
playlists, but for now I need a way of scheduling the idle task.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I put in a generic IOTask function. I'm sure this could eventually be
expanded to open and pass the appropriate stream to functions using this
task.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I do this if PL_RANDOM is set before checking PL_SEQUENTIAL. I still
need to respond to the case that both flags are set...
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
The improved next() function will increment the current iterator to the
next visible song. If no songs are visible, then it moves to the next
song on the list. The PL_DRAIN flags is then checked, and if draining
is enabled then the current track is removed from the list before
returning.
This patch also switches over the library sequential next function to
use the lib_playlist version.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I can do this easier with library::lookup() since this avoids having to
loop on the entire track_list.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
get_info() performed a loop over all library paths and over each track
in each path to find the requested sid_t. I now keep a map sid_t ->
Track * to make finding the track easier and faster.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
This allows more direct access to each item in the list. It also speeds
up sorting, since I don't need to keep looking up the same tracks over
and over again in the library.
Siged-off-by: Bryan Schumaker <bjschuma@gmail.com>
With this change, my old queue code no longer saves itself. Instead, it
only sends out QUEUE_REFRESH notifications.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
Note that this follows the broken implementation used by the queue. It
will remove all ids with the given values rather than the ones at a
specifix index. This will be fixed later when I update the UI to make
it Playlist-aware.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>
I replace the old queue iterator functions with calls to the queue
playlist object that way I don't need to modify the UI. This creates a
common iterator that can be used by all playlists.
Signed-off-by: Bryan Schumaker <bjschuma@gmail.com>