| Index: tools/telemetry/third_party/gsutilz/third_party/protorpc/demos/tunes_db/server/tunes_db.py
|
| diff --git a/tools/telemetry/third_party/gsutilz/third_party/protorpc/demos/tunes_db/server/tunes_db.py b/tools/telemetry/third_party/gsutilz/third_party/protorpc/demos/tunes_db/server/tunes_db.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..0d62cb7ce9a7bdbc69bc629b946168854b6713de
|
| --- /dev/null
|
| +++ b/tools/telemetry/third_party/gsutilz/third_party/protorpc/demos/tunes_db/server/tunes_db.py
|
| @@ -0,0 +1,539 @@
|
| +#!/usr/bin/env python
|
| +#
|
| +# Copyright 2010 Google Inc.
|
| +#
|
| +# Licensed under the Apache License, Version 2.0 (the "License");
|
| +# you may not use this file except in compliance with the License.
|
| +# You may obtain a copy of the License at
|
| +#
|
| +# http://www.apache.org/licenses/LICENSE-2.0
|
| +#
|
| +# Unless required by applicable law or agreed to in writing, software
|
| +# distributed under the License is distributed on an "AS IS" BASIS,
|
| +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| +# See the License for the specific language governing permissions and
|
| +# limitations under the License.
|
| +#
|
| +
|
| +"""Tunes DB service implementation.
|
| +
|
| +This module contains all the protocol buffer and service definitions
|
| +necessary for the Tunes DB service.
|
| +"""
|
| +
|
| +import base64
|
| +import sys
|
| +
|
| +from google.appengine.ext import db
|
| +
|
| +from protorpc import descriptor
|
| +from protorpc import message_types
|
| +from protorpc import messages
|
| +from protorpc import protobuf
|
| +from protorpc import remote
|
| +
|
| +import model
|
| +
|
| +
|
| +class Artist(messages.Message):
|
| + """Musician or music group responsible for music production.
|
| +
|
| + Fields:
|
| + artist_id: Unique opaque identifier for artist.
|
| + name: User friendly name of artist.
|
| + album_count: Number of albums produced by artist.
|
| + """
|
| +
|
| + artist_id = messages.StringField(1, required=True)
|
| + name = messages.StringField(2, required=True)
|
| +
|
| + album_count = messages.IntegerField(3)
|
| +
|
| +
|
| +class Album(messages.Message):
|
| + """Album produced by a musician or music group.
|
| +
|
| + Fields:
|
| + album_id: Unique opaque identifier for artist.
|
| + artist_id: Artist id of musician or music group that produced album.
|
| + name: Name of album.
|
| + released: Year when album was released.
|
| + """
|
| +
|
| + album_id = messages.StringField(1, required=True)
|
| + artist_id = messages.StringField(2, required=True)
|
| + name = messages.StringField(3, required=True)
|
| + released = messages.IntegerField(4)
|
| +
|
| +
|
| +class AddArtistRequest(messages.Message):
|
| + """Request to add a new Artist to library.
|
| +
|
| + Fields:
|
| + name: User friendly name of artist.
|
| + """
|
| +
|
| + name = messages.StringField(1, required=True)
|
| +
|
| +
|
| +class AddArtistResponse(messages.Message):
|
| + """Response sent after creation of new artist in library.
|
| +
|
| + Fields:
|
| + artist_id: Unique opaque ID of new artist.
|
| + """
|
| +
|
| + artist_id = messages.StringField(1, required=True)
|
| +
|
| +
|
| +class UpdateArtistRequest(messages.Message):
|
| + """Update an existing artist.
|
| +
|
| + Fields:
|
| + artist: Complete information about artist to update.
|
| + """
|
| +
|
| + artist = messages.MessageField(Artist, 1, required=True)
|
| +
|
| +
|
| +class UpdateArtistResponse(messages.Message):
|
| + """Artist update response.
|
| +
|
| + Fields:
|
| + artist_updated: Artist was found and updated.
|
| + """
|
| +
|
| + artist_updated = messages.BooleanField(1, required=True)
|
| +
|
| +
|
| +class DeleteArtistRequest(messages.Message):
|
| + """Delete artist from library.
|
| +
|
| + Fields:
|
| + artist_id: Unique opaque ID of artist to delete.
|
| + """
|
| +
|
| + artist_id = messages.StringField(1, required=True)
|
| +
|
| +
|
| +class DeleteArtistResponse(messages.Message):
|
| + """Artist deletion response.
|
| +
|
| + Fields:
|
| + artist_deleted: Artist was found and deleted.
|
| + """
|
| +
|
| + artist_deleted = messages.BooleanField(1, default=True)
|
| +
|
| +
|
| +class FetchArtistRequest(messages.Message):
|
| + """Fetch an artist from the library.
|
| +
|
| + Fields:
|
| + artist_id: Unique opaque ID of artist to fetch.
|
| + """
|
| +
|
| + artist_id = messages.StringField(1, required=True)
|
| +
|
| +
|
| +class FetchArtistResponse(messages.Message):
|
| + """Fetched artist from library.
|
| +
|
| + Fields:
|
| + artist: Artist found in library.
|
| + """
|
| +
|
| + artist = messages.MessageField(Artist, 1)
|
| +
|
| +
|
| +class SearchArtistsRequest(messages.Message):
|
| + """Artist search request.
|
| +
|
| + Fields:
|
| + continuation: Continuation from the response of a previous call to
|
| + search_artists remote method.
|
| + fetch_size: Maximum number of records to retrieve.
|
| + name_prefix: Name prefix of artists to search. If none provided and
|
| + no continuation provided, search will be of all artists in library.
|
| + If continuation is provided, name_prefix should be empty, if not, value
|
| + is ignored.
|
| + """
|
| +
|
| + continuation = messages.StringField(1)
|
| + fetch_size = messages.IntegerField(2, default=10)
|
| + name_prefix = messages.StringField(3, default=u'')
|
| +
|
| +
|
| +class SearchArtistsResponse(messages.Message):
|
| + """Response from searching artists.
|
| +
|
| + Fields:
|
| + artists: Artists found from search up to fetch_size.
|
| + continuation: Opaque string that can be used with a new search request
|
| + that will continue finding new artists where this response left off.
|
| + Will not be set if there were no results from the search or fewer
|
| + artists were returned in the response than requested, indicating the end
|
| + of the query.
|
| + """
|
| +
|
| + artists = messages.MessageField(Artist, 1, repeated=True)
|
| + continuation = messages.StringField(2)
|
| +
|
| +
|
| +class AddAlbumRequest(messages.Message):
|
| + """Request to add a new album to library.
|
| +
|
| + Fields:
|
| + name: User friendly name of album.
|
| + artist_id: Artist id of artist that produced record.
|
| + released: Year album was released.
|
| + """
|
| +
|
| + name = messages.StringField(1, required=True)
|
| + artist_id = messages.StringField(2, required=True)
|
| + released = messages.IntegerField(3)
|
| +
|
| +
|
| +class AddAlbumResponse(messages.Message):
|
| + """Response sent after creation of new album in library.
|
| +
|
| + Fields:
|
| + album_id: Unique opaque ID of new album.
|
| + """
|
| +
|
| + album_id = messages.StringField(1, required=True)
|
| +
|
| +
|
| +class UpdateAlbumRequest(messages.Message):
|
| + """Update an existing album.
|
| +
|
| + Fields:
|
| + album: Complete information about album to update.
|
| + """
|
| +
|
| + album = messages.MessageField(Album, 1, required=True)
|
| +
|
| +
|
| +class UpdateAlbumResponse(messages.Message):
|
| + """Album update response.
|
| +
|
| + Fields:
|
| + album_updated: Album was found and updated.
|
| + """
|
| +
|
| + album_updated = messages.BooleanField(1, required=True)
|
| +
|
| +
|
| +class DeleteAlbumRequest(messages.Message):
|
| + """Delete album from library.
|
| +
|
| + Fields:
|
| + album_id: Unique opaque ID of album to delete.
|
| + """
|
| +
|
| + album_id = messages.StringField(1, required=True)
|
| +
|
| +
|
| +class DeleteAlbumResponse(messages.Message):
|
| + """Album deletion response.
|
| +
|
| + Fields:
|
| + album_deleted: Album was found and deleted.
|
| + """
|
| +
|
| + album_deleted = messages.BooleanField(1, default=True)
|
| +
|
| +
|
| +class FetchAlbumRequest(messages.Message):
|
| + """Fetch an album from the library.
|
| +
|
| + Fields:
|
| + album_id: Unique opaque ID of album to fetch.
|
| + """
|
| +
|
| + album_id = messages.StringField(1, required=True)
|
| +
|
| +
|
| +class FetchAlbumResponse(messages.Message):
|
| + """Fetched album from library.
|
| +
|
| + Fields:
|
| + album: Album found in library.
|
| + """
|
| +
|
| + album = messages.MessageField(Album, 1)
|
| +
|
| +
|
| +class SearchAlbumsRequest(messages.Message):
|
| + """Album search request.
|
| +
|
| + Fields:
|
| + continuation: Continuation from the response of a previous call to
|
| + search_albums remote method.
|
| + fetch_size: Maximum number of records to retrieve.
|
| + name_prefix: Name prefix of albms to search. If none provided and
|
| + no continuation provided, search will be of all albums in library.
|
| + If continuation is provided, name_prefix should be empty, if not, value
|
| + is ignored.
|
| + artist_id: Restrict search to albums of single artist.
|
| + """
|
| +
|
| + continuation = messages.StringField(1)
|
| + fetch_size = messages.IntegerField(2, default=10)
|
| + name_prefix = messages.StringField(3, default=u'')
|
| + artist_id = messages.StringField(4)
|
| +
|
| +
|
| +class SearchAlbumsResponse(messages.Message):
|
| + """Response from searching artists.
|
| +
|
| + Fields:
|
| + albums: Albums found from search up to fetch_size.
|
| + continuation: Opaque string that can be used with a new search request
|
| + that will continue finding new albums where this response left off.
|
| + Will not be set if there were no results from the search or fewer
|
| + albums were returned in the response than requested, indicating the end
|
| + of the query.
|
| + """
|
| +
|
| + albums = messages.MessageField(Album, 1, repeated=True)
|
| + continuation = messages.StringField(2)
|
| +
|
| +
|
| +class MusicLibraryService(remote.Service):
|
| + """Music library service."""
|
| +
|
| + __file_set = None
|
| +
|
| + def __artist_from_model(self, artist_model):
|
| + """Helper that copies an Artist model to an Artist message.
|
| +
|
| + Args:
|
| + artist_model: model.ArtistInfo instance to convert in to an Artist
|
| + message.
|
| +
|
| + Returns:
|
| + New Artist message with contents of artist_model copied in to it.
|
| + """
|
| + return Artist(artist_id=unicode(artist_model.key()),
|
| + name=artist_model.name,
|
| + album_count=artist_model.album_count)
|
| +
|
| + def __album_from_model(self, album_model):
|
| + """Helper that copies an Album model to an Album message.
|
| +
|
| + Args:
|
| + album_model: model.AlbumInfo instance to convert in to an Album
|
| + message.
|
| +
|
| + Returns:
|
| + New Album message with contents of album_model copied in to it.
|
| + """
|
| + artist_id = model.AlbumInfo.artist.get_value_for_datastore(album_model)
|
| +
|
| + return Album(album_id=unicode(album_model.key()),
|
| + artist_id=unicode(artist_id),
|
| + name=album_model.name,
|
| + released=album_model.released or None)
|
| +
|
| + @classmethod
|
| + def __search_info(cls,
|
| + request,
|
| + info_class,
|
| + model_to_message,
|
| + customize_query=None):
|
| + """Search over an Info subclass.
|
| +
|
| + Since all search request classes are very similar, it's possible to
|
| + generalize how to do searches over them.
|
| +
|
| + Args:
|
| + request: Search request received from client.
|
| + info_class: The model.Info subclass to search.
|
| + model_to_method: Function (model) -> message that transforms an instance
|
| + of info_class in to the appropriate messages.Message subclass.
|
| + customize_query: Function (request, query) -> None that adds additional
|
| + filters to Datastore query based on specifics of that search message.
|
| +
|
| + Returns:
|
| + Tuple (results, continuation):
|
| + results: A list of messages satisfying the parameters of the request.
|
| + None if there are no results.
|
| + continuation: Continuation string for response if there are more
|
| + results available. None if there are no more results available.
|
| + """
|
| + # TODO(rafek): fetch_size from this request should take priority
|
| + # over what is stored in continuation.
|
| + if request.continuation:
|
| + encoded_search, continuation = request.continuation.split(':', 1)
|
| + decoded_search = base64.urlsafe_b64decode(encoded_search.encode('utf-8'))
|
| + request = protobuf.decode_message(type(request), decoded_search)
|
| + else:
|
| + continuation = None
|
| + encoded_search = unicode(base64.urlsafe_b64encode(
|
| + protobuf.encode_message(request)))
|
| +
|
| + name_prefix = request.name_prefix
|
| +
|
| + query = info_class.search(name_prefix)
|
| + query.order('name')
|
| + if customize_query:
|
| + customize_query(request, query)
|
| +
|
| + if continuation:
|
| + # TODO(rafek): Pure query cursors are not safe for model with
|
| + # query restrictions. Would technically need to be encrypted.
|
| + query.with_cursor(continuation)
|
| +
|
| + fetch_size = request.fetch_size
|
| +
|
| + model_instance = query.fetch(fetch_size)
|
| + results = None
|
| + continuation = None
|
| + if model_instance:
|
| + results = [model_to_message(i) for i in model_instance]
|
| + if len(model_instance) == fetch_size:
|
| + cursor = query.cursor()
|
| + continuation = u'%s:%s' % (encoded_search, query.cursor())
|
| +
|
| + return results, continuation
|
| +
|
| +
|
| + @remote.method(AddArtistRequest, AddArtistResponse)
|
| + def add_artist(self, request):
|
| + """Add artist to library."""
|
| + artist_name = request.name
|
| + def do_add():
|
| + artist = model.ArtistInfo(name=artist_name)
|
| + artist.put()
|
| + return artist
|
| + artist = db.run_in_transaction(do_add)
|
| +
|
| + return AddArtistResponse(artist_id = unicode(artist.key()))
|
| +
|
| + @remote.method(UpdateArtistRequest, UpdateArtistResponse)
|
| + def update_artist(self, request):
|
| + """Update artist from library."""
|
| + def do_deletion():
|
| + artist = model.ArtistInfo.get(request.artist.artist_id)
|
| + if artist:
|
| + artist.name = request.artist.name
|
| + artist.put()
|
| + return True
|
| + else:
|
| + return False
|
| + return UpdateArtistResponse(
|
| + artist_updated=db.run_in_transaction(do_deletion))
|
| +
|
| + @remote.method(DeleteArtistRequest, DeleteArtistResponse)
|
| + def delete_artist(self, request):
|
| + """Delete artist from library."""
|
| + def do_deletion():
|
| + artist = model.ArtistInfo.get(request.artist_id)
|
| + if artist:
|
| + db.delete(model.Info.all(keys_only=True).ancestor(artist))
|
| + return True
|
| + else:
|
| + return False
|
| + return DeleteArtistResponse(
|
| + artist_deleted = db.run_in_transaction(do_deletion))
|
| +
|
| + @remote.method(FetchArtistRequest, FetchArtistResponse)
|
| + def fetch_artist(self, request):
|
| + """Fetch artist from library."""
|
| + artist_model = model.ArtistInfo.get(request.artist_id)
|
| + if isinstance(artist_model, model.ArtistInfo):
|
| + artist = self.__artist_from_model(artist_model)
|
| + else:
|
| + artist = None
|
| + return FetchArtistResponse(artist=artist)
|
| +
|
| +
|
| + @remote.method(SearchArtistsRequest, SearchArtistsResponse)
|
| + def search_artists(self, request):
|
| + """Search library for artists."""
|
| + results, continuation = self.__search_info(request,
|
| + model.ArtistInfo,
|
| + self.__artist_from_model)
|
| + return SearchArtistsResponse(artists=results or [],
|
| + continuation=continuation or None)
|
| +
|
| + @remote.method(AddAlbumRequest, AddAlbumResponse)
|
| + def add_album(self, request):
|
| + """Add album to library."""
|
| + def create_album():
|
| + if not request.artist_id:
|
| + raise ValueError('Request does not have artist-id.')
|
| + artist = model.ArtistInfo.get(request.artist_id)
|
| + if not artist:
|
| + raise ValueError('No artist found for %s.' % request.artist_id)
|
| + artist.album_count += 1
|
| + artist.put()
|
| +
|
| + album = model.AlbumInfo(name=request.name,
|
| + released=request.released,
|
| + artist=artist,
|
| + parent=artist)
|
| + album.put()
|
| +
|
| + return album
|
| + album = db.run_in_transaction(create_album)
|
| +
|
| + return AddAlbumResponse(album_id=unicode(album.key()))
|
| +
|
| + @remote.method(UpdateAlbumRequest, UpdateAlbumResponse)
|
| + def update_album(self, request):
|
| + """Update album from library."""
|
| + def do_deletion():
|
| + album = model.AlbumInfo.get(request.album.album_id)
|
| + if album:
|
| + album.name = request.album.name
|
| + album.released = request.album.released
|
| + album.put()
|
| + return True
|
| + else:
|
| + return False
|
| + return UpdateAlbumResponse(album_updated=db.run_in_transaction(do_deletion))
|
| +
|
| + @remote.method(DeleteAlbumRequest, DeleteAlbumResponse)
|
| + def delete_album(self, request):
|
| + """Delete album from library."""
|
| + def do_deletion():
|
| + album = model.AlbumInfo.get(request.album_id)
|
| +
|
| + artist = album.artist
|
| + artist.album_count -= 1
|
| + artist.put()
|
| +
|
| + if album:
|
| + db.delete(model.Info.all(keys_only=True).ancestor(album))
|
| + return True
|
| + else:
|
| + return False
|
| +
|
| + return DeleteAlbumResponse(album_deleted=db.run_in_transaction(do_deletion))
|
| +
|
| + @remote.method(FetchAlbumRequest, FetchAlbumResponse)
|
| + def fetch_album(self, request):
|
| + """Fetch album from library."""
|
| + album_model = model.AlbumInfo.get(request.album_id)
|
| + if isinstance(album_model, model.AlbumInfo):
|
| + album = self.__album_from_model(album_model)
|
| + else:
|
| + album = None
|
| + return FetchAlbumResponse(album=album)
|
| +
|
| + @remote.method(SearchAlbumsRequest, SearchAlbumsResponse)
|
| + def search_albums(self, request):
|
| + """Search library for albums."""
|
| + def customize_query(request, query):
|
| + if request.artist_id:
|
| + query.filter('artist', db.Key(request.artist_id))
|
| +
|
| + response = SearchAlbumsResponse()
|
| + results, continuation = self.__search_info(request,
|
| + model.AlbumInfo,
|
| + self.__album_from_model,
|
| + customize_query)
|
| + return SearchAlbumsResponse(albums=results or [],
|
| + continuation=continuation or None)
|
|
|