nixplay

package module
v0.0.0-...-c436d6b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 13, 2023 License: MIT Imports: 26 Imported by: 0

README

go-nixplay

Go Reference GitHub release (latest SemVer)) CI Go Report Card

go-nixplay is an unofficial library for uploading and downloading photos to/from the Nixplay cloud based picture frame service. Note that since Nixplay does not publicly document any of their APIs they could change APIs with no warning potentially breaking this library.

Usage

This project is intended to simply be a library for communicating with Nixplay and not as a full application. My eventual goal is to integrate this library into rclone as a cloud backend in order to implement a flexible way of syncing photos from the local file system or virtually any cloud storage provider to Nixplay.

For info on using the library see the go doc reference page or see tests for an example.

Capabilities

  • List albums and playlists
  • Get basic info about albums and playlists such as name and photo count
  • Add and delete albums and playlists
  • List photos within an album or playlist
  • Get basic info about photos such as name, size, MD5 hash
  • Upload new photos
  • Delete existing photos

Caching

My experience has been that the HTTP calls to get data about albums and photos tends to be on the slow side. So this library will only make calls to get data about items when it is requested and will cache data that is received as part of the request in the event that the data is requested again. The cache of albums/playlists can be cleared by doing client.ResetCache() and the cache of photos within an individual album/playlist can be cleared by doing container.ResetCache(). Cached data such as name, size, MD5 hash for an individual item can not be cleared, to get updated data for that item reset that parents cache and re-request that item and associated data.

Limitations

Nixplay Meta Model

Before we get into real limitations it is useful to discuss the meta model (or at least my interpretation of it) of how photos are stored in Nixplay. To start with every photo is contained/owned by an album. Nixplay does not allow photos with duplicate content within the same album. However nixplay does allow photos with duplicate content to exist in different albums. There is also no constraint of name uniqueness for photos within an album.

Photos within a playlist are not contained/owned by the playlist but these are rather just associations to photos that are actually contained/owned by an album. Nixplay unfortunately allows a playlist to contain multiple associations to the same photo within an album, ie in a playlist you could have 10 copies the same photo in the same album.

Photo Addition/Delete is not "Atomic"

The biggest fallout from the above meta model is that addition and deletion of photos is not "atomic", as in adding or deleting the photo may result in it getting added or deleted from another container.

For example:

  • If you add a photo to a playlist it will automatically upload the photo to the "My Uploads" album and then associate that photo to the playlist you added the photo to.
  • If you delete a photo from a album it will also remove the photo from any playlists that the photo was associated to.
  • When deleting a photo from a playlist we will use APIs to only remove photos from that playlist in order to avoid affecting other playlists the photo may be a part of. This is probably expected based on everything mentioned above, but it is worth explicitly mentioning because this can result in "leaking" photos. If I add a photo to a playlist and then immediately delete the photo it results in the photo being "leaked" in the "My Uploads" folder. This probably isn't a big issue, but if you were to use these APIs to frequently add/remove mass number of photos you could leak enough photos to hit 10GB free storage quota.

Note that the caching mentioned in the Caching does not take this into account. If you add or delete a photo from one container you need to reset the cache in other containers if you want to guarantee that you have the correct state for those containers.

Multiple Copies of Photos in Playlist

One of the goals of this library was to provide a reference to the photo that is uploaded to Nixplay. The difficulty with this is that Nixplay does not return any such reference to describe the ID of the photo upload, only that if upload succeeded or failed. So to achieve this goal when a photo is upload this library stores the MD5 hash of the photo and can use this information to differentiate the photo from others within the container. This works well for photos within albums as Nixplay doesn't allow photos with duplicate content in the same album (as mentioned above).

However as I mentioned above Nixplay DOES allow duplicate copies of photos with in a playlist. For now it is recommended that you avoid uploading duplicate copies of photos to a playlist as this will likely result in unexpected behavior. At some point I may look into resolving this issue but I doubt many people will run into this issue/limitation.

Name Encoding

Nixplay does not document any sort of API so we really don't have any guarantee of what sort of characters it supports for names of containers or files. I did some experimentation it seems like Nixplay has pretty good support but some non-ASCII characters like emoji don't work correctly. So in an effort to make sure things work correctly and will continue to work correctly we will be pretty aggressive with the encoding names of containers and files before uploading to Nixplay. Any non-ASCII or non-printable characters, along with backslashes () and double quotes ("), will be encoded using Go escape sequences.

Testing

This library contains tests to ensure that all APIs are working correctly. To make this possible a test Nixplay account needs to be used. At the start of testing this account needs to have the default empty configuration, it should have two empty playlists ${username}@mynixplay.com and Favorites, it should have two empty albums ${username}@mynixplay.com and My Uploads. DO NOT use a real nixplay account you care about for testing as this may remove photos you care about.

The credentials for test account to be used for testing should be specified by using the GO_NIXPLAY_TEST_ACCOUNT_USERNAME and GO_NIXPLAY_TEST_ACCOUNT_PASSWORD environment variables.

When running tests the -p 1 flag must be passed to go test to disable running tests in parallel as having multiple tests attempting to add/remove albums/playlists/photos at the same time as some tests look at all albums/playlists to lock down the APIs use to add/remove containers.

For example to run all tests

export GO_NIXPLAY_TEST_ACCOUNT_USERNAME="YOUR_USERNAME_HERE"
export GO_NIXPLAY_TEST_ACCOUNT_PASSWORD="YOUR_PASSWORD_HERE"
go test -p 1 -v ./...

This library runs these tests via GitHub Actions to ensure there are no bugs introduced in PRs. To do this the above mentioned environment variables are injected in to the testing environment by using encrypted secrets that are tied to the test environment. There is a good writeup of this workflow here.

Acknowledgements

Thanks to andrewjjenkins for doing the initial reverse engineering of Nixplay's APIs as part of andrewjjenkins/picsync.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AddPhotoOptions

type AddPhotoOptions struct {
	// MIMEType of the photo to be uploaded.
	//
	// Specifying the MIME Type is optional. However Nixplay does require that
	// the MIME Type is provided, so if a MIME Type is not specified then one
	// will be inferred from the file extension.
	//
	// According to Nixplay documentation  JPEG, PNG, TIFF, HEIC, MP4 are all
	// supported see the following for more details:
	// https://web.archive.org/web/20230328184513/https://support.nixplay.com/hc/en-us/articles/900002393886-What-photo-and-video-formats-does-Nixplay-support-
	//
	// If you try to upload an unsupported file type you will get a 400 Bad
	// Request error from the server.
	MIMEType string

	// FileSize in bytes of the photo to be uploaded to Nixplay.
	//
	// Specifying the MIME Type is optional. However Nixplay does require that
	// the file size is provided, so if the the size is not specified then it
	// will be computed based on the io.Reader provided. An attempt will be made
	// to efficiently compute the size without buffering the entire photo into
	// memory however in some cases it may be necessary to buffer the full photo
	// into memory.
	FileSize int64
}

AddPhotoOptions are optional arguments may be specified when adding photos to Nixplay.

type Client

type Client interface {

	// Containers gets all containers of the specified ContainerType
	Containers(ctx context.Context, containerType types.ContainerType) ([]Container, error)

	// ContainersWithName gets a containers based on type and name.
	//
	// If no containers with the specified name could be found then an empty
	// slice of containers will be returned.
	ContainersWithName(ctx context.Context, containerType types.ContainerType, name string) ([]Container, error)

	// ContainerWithName gets the container based on type and unique name as
	// returned by Container.NameUnique.
	//
	// If no container with the specified unique name could be found then a nil
	// Container will be returned.
	ContainerWithUniqueName(ctx context.Context, containerType types.ContainerType, name string) (Container, error)

	// CreateContainer creates a container of the specified type and name.
	//
	// Note that the name of the container will be encoded before passing the
	// name to Nixplay. See [README.md name-encoding](./README.md#name-encoding)
	// for more details.
	CreateContainer(ctx context.Context, containerType types.ContainerType, name string) (Container, error)

	// Reset cache resets the internal cache of containers
	//
	// For more details see https://github.com/anitschke/go-nixplay/#caching
	ResetCache()
}

Client is the interface that is essentially the entrypoint into communicating with Nixplay. It provides the ability to query containers (albums or playlists) or create new containers.

type Container

type Container interface {
	// ID is a unique identifier for the container. This identifier is
	// guaranteed to stable across go-nixplay sessions although the identifier
	// for a given container may change with upgrades to go-nixplay. Note that
	// this identifier may be different than the internal identifier used by
	// Nixplay to identifier an album or playlist.
	ID() types.ID

	ContainerType() types.ContainerType

	Name(ctx context.Context) (string, error)
	// NameUnique returns a name that has an additional unique ID appended to
	// the end of the name if there are containers of the same type. If there
	// are no containers with the same name and of the same type then NameUnique
	// returns the same thing as Name.
	NameUnique(ctx context.Context) (string, error)

	// PhotoCount gets the number of photos within the container.
	//
	// Note that this API is often times more efficient than len(c.Photos)
	PhotoCount(ctx context.Context) (int64, error)

	// Photos gets all photos in the container
	Photos(ctx context.Context) ([]Photo, error)

	// PhotosWithName gets all photos in the container with the specified name.
	PhotosWithName(ctx context.Context, name string) ([]Photo, error)

	// PhotoWithUniqueName gets the photo in the container with the unique name
	// as returned by Photo.NameUnique
	PhotoWithUniqueName(ctx context.Context, name string) (Photo, error)

	// PhotoWithID gets the photo in the container with the specified ID.
	//
	// If no photo with the specified ID can be found in the container nil is
	// returned.
	PhotoWithID(ctx context.Context, id types.ID) (Photo, error)

	// Delete deletes the container.
	//
	// See
	// https://github.com/anitschke/go-nixplay/#photo-additiondelete-is-not-atomic
	// for further discussion of delete behavior.
	Delete(ctx context.Context) error

	// AddPhoto uploads a photo into the container.
	//
	// Note that the name of the container will be encoded before passing the
	// name to Nixplay. See [README.md name-encoding](./README.md#name-encoding)
	// for more details.
	AddPhoto(ctx context.Context, name string, r io.Reader, opts AddPhotoOptions) (Photo, error)

	// Reset cache resets the internal cache of photos
	//
	// For more details see https://github.com/anitschke/go-nixplay/#caching
	ResetCache()
}

Container is the interface for an object that contains photos, either an album or playlist.

type DefaultClient

type DefaultClient struct {
	// contains filtered or unexported fields
}

func (*DefaultClient) ContainerWithUniqueName

func (c *DefaultClient) ContainerWithUniqueName(ctx context.Context, containerType types.ContainerType, name string) (Container, error)

func (*DefaultClient) Containers

func (c *DefaultClient) Containers(ctx context.Context, containerType types.ContainerType) ([]Container, error)

func (*DefaultClient) ContainersWithName

func (c *DefaultClient) ContainersWithName(ctx context.Context, containerType types.ContainerType, name string) ([]Container, error)

func (*DefaultClient) CreateContainer

func (c *DefaultClient) CreateContainer(ctx context.Context, containerType types.ContainerType, name string) (Container, error)

func (*DefaultClient) ResetCache

func (c *DefaultClient) ResetCache()

type DefaultClientOptions

type DefaultClientOptions struct {
	// HTTPClient is the HTTP Client that will be used to communicate with the
	// Nixplay servers.
	//
	// If no client is specified then the default http.Client will be used.
	HTTPClient httpx.Client
}

DefaultClientOptions are optional inputs that may be specified for creating a DefaultClient

type Photo

type Photo interface {
	// ID is a unique identifier for the photo. This identifier is guaranteed to
	// stable across go-nixplay sessions although the identifier for a given
	// container may change with upgrades to go-nixplay. Note that this
	// identifier may be different than the internal identifier used by Nixplay
	// to identifier a photo.
	//
	// Note copies of the same photo in different containers will have a unique
	// identifier.
	//
	// Note that duplicate copies of the same photo within the same playlist
	// will have the same identifier. See further discussion of this issue in
	// https://github.com/anitschke/go-nixplay/#multiple-copies-of-photos-in-playlist
	ID() types.ID

	Name(ctx context.Context) (string, error)

	// NameUnique returns a name that has an additional unique ID appended to
	// the end of the name if there are photos with the same name in the
	// container that this photo resides in. If there are no photos with the
	// same name in the container then NameUnique returns the same thing as
	// Name.
	NameUnique(ctx context.Context) (string, error)

	Size(ctx context.Context) (int64, error)
	MD5Hash(ctx context.Context) (types.MD5Hash, error)

	// URL returns the URL for the original photo that was uploaded to Nixplay.
	URL(ctx context.Context) (string, error)

	// Open opens the photo for reading the contents of the photo.
	Open(ctx context.Context) (io.ReadCloser, error)

	// Delete deletes the photo from the parent container that this photo object
	// was obtained from.
	//
	// See
	// https://github.com/anitschke/go-nixplay/#photo-additiondelete-is-not-atomic
	// for further discussion of delete behavior.
	Delete(ctx context.Context) error
}

Photo is an interface for an object that represents a photo. Even though a photo may exist in one album and multiple playlists the Photo object represents a photo within the specific Container object that it was obtained from.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL