diff --git a/livesupport/src/modules/storageClient/include/LiveSupport/Storage/StorageClientInterface.h b/livesupport/src/modules/storageClient/include/LiveSupport/Storage/StorageClientInterface.h index 1fabd7767..4edb66dea 100644 --- a/livesupport/src/modules/storageClient/include/LiveSupport/Storage/StorageClientInterface.h +++ b/livesupport/src/modules/storageClient/include/LiveSupport/Storage/StorageClientInterface.h @@ -588,6 +588,28 @@ class StorageClientInterface throw (XmlRpcException) = 0; + /** + * Import a playlist archive to the local storage. + * This must be a tar file, in the LS Archive format, as produced + * by exportPlaylistOpen/Close() when called with the internalFormat + * parameter. + * + * The size of the tar file must be less than 2 GB, because + * the storage server can not deal with larger files. + * + * @param sessionId the session ID from the authentication client + * @param path the path for the playlist archive file. + * @return on success, the unique ID of the imported playlist. + * + * @exception XmlRpcException if there is a problem with the XML-RPC + * call or we have not logged in yet. + */ + virtual Ptr::Ref + importPlaylist(Ptr::Ref sessionId, + Ptr::Ref path) const + throw (XmlRpcException) + = 0; + /** * The possible states of an asynchronous transport process. */ diff --git a/livesupport/src/modules/storageClient/src/TestStorageClient.cxx b/livesupport/src/modules/storageClient/src/TestStorageClient.cxx index b529751f5..88520841e 100644 --- a/livesupport/src/modules/storageClient/src/TestStorageClient.cxx +++ b/livesupport/src/modules/storageClient/src/TestStorageClient.cxx @@ -1079,6 +1079,19 @@ TestStorageClient :: exportPlaylistClose( } +/*------------------------------------------------------------------------------ + * Import a playlist archive to the local storage. + *----------------------------------------------------------------------------*/ +Ptr::Ref +TestStorageClient :: importPlaylist( + Ptr::Ref sessionId, + Ptr::Ref path) const + throw (XmlRpcException) +{ + throw XmlRpcException("Method not implemented."); +} + + /*------------------------------------------------------------------------------ * Check the status of the asynchronous network transport operation. *----------------------------------------------------------------------------*/ diff --git a/livesupport/src/modules/storageClient/src/TestStorageClient.h b/livesupport/src/modules/storageClient/src/TestStorageClient.h index 1f457ffc3..b4e0d9710 100644 --- a/livesupport/src/modules/storageClient/src/TestStorageClient.h +++ b/livesupport/src/modules/storageClient/src/TestStorageClient.h @@ -712,6 +712,27 @@ class TestStorageClient : exportPlaylistClose(Ptr::Ref token) const throw (XmlRpcException); + /** + * Import a playlist archive to the local storage. + * This must be a tar file, in the LS Archive format, as produced + * by exportPlaylistOpen/Close() when called with the internalFormat + * parameter. + * + * The size of the tar file must be less than 2 GB, because + * the storage server can not deal with larger files. + * + * @param sessionId the session ID from the authentication client + * @param path the path for the playlist archive file. + * @return on success, the unique ID of the imported playlist. + * + * @exception XmlRpcException if there is a problem with the XML-RPC + * call or we have not logged in yet. + */ + virtual Ptr::Ref + importPlaylist(Ptr::Ref sessionId, + Ptr::Ref path) const + throw (XmlRpcException); + /** * Check the status of the asynchronous network transport operation. * diff --git a/livesupport/src/modules/storageClient/src/WebStorageClient.cxx b/livesupport/src/modules/storageClient/src/WebStorageClient.cxx index 940f6e446..47bf02fb3 100644 --- a/livesupport/src/modules/storageClient/src/WebStorageClient.cxx +++ b/livesupport/src/modules/storageClient/src/WebStorageClient.cxx @@ -754,6 +754,46 @@ const std::string exportPlaylistUrlParamName = "url"; const std::string exportPlaylistTokenParamName = "token"; +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ storage server constants: importPlaylist */ + +/*------------------------------------------------------------------------------ + * The name of the opening 'import playlist' method on the storage server + *----------------------------------------------------------------------------*/ +const std::string importPlaylistOpenMethodName + = "locstor.importPlaylistOpen"; + +/*------------------------------------------------------------------------------ + * The name of the closing 'import playlist' method on the storage server + *----------------------------------------------------------------------------*/ +const std::string importPlaylistCloseMethodName + = "locstor.importPlaylistClose"; + +/*------------------------------------------------------------------------------ + * The name of the session ID parameter in the input structure + *----------------------------------------------------------------------------*/ +const std::string importPlaylistSessionIdParamName = "sessid"; + +/*------------------------------------------------------------------------------ + * The name of the checksum parameter in the input structure + *----------------------------------------------------------------------------*/ +const std::string importPlaylistChecksumParamName = "chsum"; + +/*------------------------------------------------------------------------------ + * The name of the writable URL parameter in the output structure + *----------------------------------------------------------------------------*/ +const std::string importPlaylistUrlParamName = "url"; + +/*------------------------------------------------------------------------------ + * The name of the token parameter for both 'open' and 'close' + *----------------------------------------------------------------------------*/ +const std::string importPlaylistTokenParamName = "token"; + +/*------------------------------------------------------------------------------ + * The name of the unique ID parameter in the output structure + *----------------------------------------------------------------------------*/ +const std::string importPlaylistUniqueIdParamName = "gunid"; + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ storage server constants: remoteSearchXXXX */ /*------------------------------------------------------------------------------ @@ -2490,6 +2530,96 @@ WebStorageClient :: exportPlaylistClose( } +/*------------------------------------------------------------------------------ + * Import a playlist archive to the local storage. + *----------------------------------------------------------------------------*/ +Ptr::Ref +WebStorageClient :: importPlaylist( + Ptr::Ref sessionId, + Ptr::Ref path) const + throw (XmlRpcException) +{ + std::ifstream ifs(path->c_str()); + if (!ifs) { + ifs.close(); + throw XmlRpcIOException("Could not read the playlist archive file."); + } + std::string md5string = Md5(ifs); + ifs.close(); + + XmlRpcValue parameters; + XmlRpcValue result; + + parameters.clear(); + parameters[importPlaylistSessionIdParamName] + = sessionId->getId(); + parameters[importPlaylistChecksumParamName] + = md5string; + + execute(importPlaylistOpenMethodName, parameters, result); + + checkStruct(importPlaylistOpenMethodName, + result, + importPlaylistUrlParamName, + XmlRpcValue::TypeString); + + checkStruct(importPlaylistOpenMethodName, + result, + importPlaylistTokenParamName, + XmlRpcValue::TypeString); + + std::string url = std::string(result[importPlaylistUrlParamName]); + std::string token = std::string(result[importPlaylistTokenParamName]); + + FILE* binaryFile = fopen(path->c_str(), "rb"); + if (!binaryFile) { + throw XmlRpcIOException("The playlist archive file disappeared."); + } + fseek(binaryFile, 0, SEEK_END); + long binaryFileSize = ftell(binaryFile); + rewind(binaryFile); + + CURL* handle = curl_easy_init(); + if (!handle) { + throw XmlRpcCommunicationException("Could not obtain curl handle."); + } + + int status = curl_easy_setopt(handle, CURLOPT_READDATA, binaryFile); + status |= curl_easy_setopt(handle, CURLOPT_INFILESIZE, binaryFileSize); + // works for files of size up to 2 GB + status |= curl_easy_setopt(handle, CURLOPT_PUT, 1); + status |= curl_easy_setopt(handle, CURLOPT_URL, url.c_str()); + + if (status) { + throw XmlRpcCommunicationException("Could not set curl options."); + } + + status = curl_easy_perform(handle); + + if (status) { + throw XmlRpcCommunicationException("Error uploading file."); + } + + curl_easy_cleanup(handle); + fclose(binaryFile); + + parameters.clear(); + parameters[importPlaylistTokenParamName] + = token; + + execute(importPlaylistCloseMethodName, parameters, result); + + checkStruct(importPlaylistCloseMethodName, + result, + importPlaylistUniqueIdParamName, + XmlRpcValue::TypeString); + + Ptr::Ref id(new UniqueId(std::string( + result[importPlaylistUniqueIdParamName] ))); + return id; +} + + /*------------------------------------------------------------------------------ * Check the status of the asynchronous network transport operation. *----------------------------------------------------------------------------*/ diff --git a/livesupport/src/modules/storageClient/src/WebStorageClient.h b/livesupport/src/modules/storageClient/src/WebStorageClient.h index 89da5a103..b4d93c71a 100644 --- a/livesupport/src/modules/storageClient/src/WebStorageClient.h +++ b/livesupport/src/modules/storageClient/src/WebStorageClient.h @@ -804,6 +804,27 @@ class WebStorageClient : exportPlaylistClose(Ptr::Ref token) const throw (XmlRpcException); + /** + * Import a playlist archive to the local storage. + * This must be a tar file, in the LS Archive format, as produced + * by exportPlaylistOpen/Close() when called with the internalFormat + * parameter. + * + * The size of the tar file must be less than 2 GB, because + * the storage server can not deal with larger files. + * + * @param sessionId the session ID from the authentication client + * @param path the path for the playlist archive file. + * @return on success, the unique ID of the imported playlist. + * + * @exception XmlRpcException if there is a problem with the XML-RPC + * call or we have not logged in yet. + */ + virtual Ptr::Ref + importPlaylist(Ptr::Ref sessionId, + Ptr::Ref path) const + throw (XmlRpcException); + /** * Check the status of the asynchronous network transport operation. * diff --git a/livesupport/src/products/gLiveSupport/src/GLiveSupport.cxx b/livesupport/src/products/gLiveSupport/src/GLiveSupport.cxx index f31d8567e..4dfc0ad88 100644 --- a/livesupport/src/products/gLiveSupport/src/GLiveSupport.cxx +++ b/livesupport/src/products/gLiveSupport/src/GLiveSupport.cxx @@ -873,11 +873,11 @@ GLiveSupport :: releaseOpennedPlaylists(void) throw () /*------------------------------------------------------------------------------ - * Upload a file to the server. + * Upload an audio clip to the local storage. *----------------------------------------------------------------------------*/ void LiveSupport :: GLiveSupport :: -GLiveSupport :: uploadFile(Ptr::Ref audioClip) +GLiveSupport :: uploadAudioClip(Ptr::Ref audioClip) throw (XmlRpcException) { storage->storeAudioClip(sessionId, audioClip); @@ -887,6 +887,24 @@ GLiveSupport :: uploadFile(Ptr::Ref audioClip) } +/*------------------------------------------------------------------------------ + * Upload a playlist archive to the local storage. + *----------------------------------------------------------------------------*/ +Ptr::Ref +LiveSupport :: GLiveSupport :: +GLiveSupport :: uploadPlaylistArchive(Ptr::Ref path) + throw (XmlRpcException) +{ + Ptr::Ref id = storage->importPlaylist(sessionId, path); + Ptr::Ref playlist = storage->getPlaylist(sessionId, id); + + // this will also add it to the local cache + addToScratchpad(playlist); + + return playlist; +} + + /*------------------------------------------------------------------------------ * Add a file to the Scratchpad, and update it. *----------------------------------------------------------------------------*/ diff --git a/livesupport/src/products/gLiveSupport/src/GLiveSupport.h b/livesupport/src/products/gLiveSupport/src/GLiveSupport.h index e1d08a502..c50f7b67e 100644 --- a/livesupport/src/products/gLiveSupport/src/GLiveSupport.h +++ b/livesupport/src/products/gLiveSupport/src/GLiveSupport.h @@ -532,13 +532,23 @@ class GLiveSupport : public LocalizedConfigurable, showLoggedInUI(void) throw (); /** - * Upload a file to the storage. + * Upload an audio clip to the storage. * - * @param audioClip the file to upload + * @param audioClip the audio clip to upload. * @exception XmlRpcException on upload failures. */ void - uploadFile(Ptr::Ref audioClip) + uploadAudioClip(Ptr::Ref audioClip) + throw (XmlRpcException); + + /** + * Upload a playlist archive to the storage. + * + * @param path the path of the file to upload. + * @exception XmlRpcException on upload failures. + */ + Ptr::Ref + uploadPlaylistArchive(Ptr::Ref path) throw (XmlRpcException); /** diff --git a/livesupport/src/products/gLiveSupport/src/UploadFileWindow.cxx b/livesupport/src/products/gLiveSupport/src/UploadFileWindow.cxx index 99763bb8b..5d96582ca 100644 --- a/livesupport/src/products/gLiveSupport/src/UploadFileWindow.cxx +++ b/livesupport/src/products/gLiveSupport/src/UploadFileWindow.cxx @@ -72,10 +72,9 @@ UploadFileWindow :: UploadFileWindow ( : GuiWindow(gLiveSupport, bundle, "", - windowOpenerButton) + windowOpenerButton), + fileType(invalidType) { - isAudioClipValid = false; - Ptr::Ref wf = WidgetFactory::getInstance(); try { @@ -302,19 +301,43 @@ void UploadFileWindow :: updateFileInfo(void) throw () { std::string fileName = fileNameEntry->get_text().raw(); - Ptr::Ref newUri(new std::string("file://")); - newUri->append(fileName); // see if the file exists, and is readable std::ifstream file(fileName.c_str()); if (!file.good()) { file.close(); statusBar->set_text(*getResourceUstring("couldNotOpenFileMsg")); - isAudioClipValid = false; + fileType = invalidType; return; } file.close(); + + fileType = determineFileType(fileName); + + switch (fileType) { + case audioClipType: readAudioClipInfo(fileName); + break; + + case playlistArchiveType: statusBar->set_text(""); + break; + + case invalidType: statusBar->set_text(*getResourceUstring( + "unsupportedFileTypeMsg")); + break; + } +} + +/*------------------------------------------------------------------------------ + * Read the playlength and metadata info from the binary audio file. + *----------------------------------------------------------------------------*/ +void +UploadFileWindow :: readAudioClipInfo(const std::string & fileName) + throw () +{ + Ptr::Ref newUri(new std::string("file://")); + newUri->append(fileName); + Ptr::Ref playlength; try { playlength = readPlaylength(fileName); @@ -341,7 +364,7 @@ UploadFileWindow :: updateFileInfo(void) throw () audioClip->readTag(gLiveSupport->getMetadataTypeContainer()); } catch (std::invalid_argument &e) { statusBar->set_text(e.what()); - isAudioClipValid = false; + fileType = invalidType; return; } @@ -357,7 +380,6 @@ UploadFileWindow :: updateFileInfo(void) throw () } statusBar->set_text(""); - isAudioClipValid = true; } @@ -379,10 +401,24 @@ UploadFileWindow :: onFileNameEntryLeave(GdkEventFocus * event) void UploadFileWindow :: onUploadButtonClicked(void) throw () { - if (!isAudioClipValid) { - return; + switch (fileType) { + case audioClipType: uploadAudioClip(); + break; + + case playlistArchiveType: uploadPlaylistArchive(); + break; + + case invalidType: break; } +} + +/*------------------------------------------------------------------------------ + * Upload an audio clip to the storage. + *----------------------------------------------------------------------------*/ +void +UploadFileWindow :: uploadAudioClip(void) throw () +{ for (unsigned int i=0; i < metadataKeys.size(); ++i) { Ptr::Ref metadataKey = metadataKeys[i]; Gtk::Entry * metadataEntry = metadataEntries[i]; @@ -401,22 +437,38 @@ UploadFileWindow :: onUploadButtonClicked(void) throw () } try { - gLiveSupport->uploadFile(audioClip); + gLiveSupport->uploadAudioClip(audioClip); } catch (XmlRpcException &e) { statusBar->set_text(e.what()); return; } - statusBar->set_text(*formatMessage("clipUploadedMsg", + clearEverything(); + statusBar->set_text(*formatMessage("fileUploadedMsg", *audioClip->getTitle() )); +} - fileNameEntry->set_text(""); - for (unsigned int i=0; i < metadataEntries.size(); ++i) { - Gtk::Entry * metadataEntry = metadataEntries[i]; - metadataEntry->set_text(""); + +/*------------------------------------------------------------------------------ + * Upload a playlist archive to the storage. + *----------------------------------------------------------------------------*/ +void +UploadFileWindow :: uploadPlaylistArchive(void) throw () +{ + Ptr::Ref path(new const Glib::ustring( + fileNameEntry->get_text() )); + + Ptr::Ref playlist; + try { + playlist = gLiveSupport->uploadPlaylistArchive(path); + } catch (XmlRpcException &e) { + statusBar->set_text(e.what()); + return; } - - isAudioClipValid = false; + + clearEverything(); + statusBar->set_text(*formatMessage("fileUploadedMsg", + *playlist->getTitle() )); } @@ -426,14 +478,7 @@ UploadFileWindow :: onUploadButtonClicked(void) throw () void UploadFileWindow :: onCloseButtonClicked(void) throw () { - fileNameEntry->set_text(""); - for (unsigned int i=0; i < metadataEntries.size(); ++i) { - Gtk::Entry * metadataEntry = metadataEntries[i]; - metadataEntry->set_text(""); - } - statusBar->set_text(""); - isAudioClipValid = false; - + clearEverything(); hide(); } @@ -445,8 +490,8 @@ Ptr::Ref UploadFileWindow :: readPlaylength(const std::string & fileName) throw (std::invalid_argument) { - // TODO: replace this with mime-type detection (gnomevfs?) and - // the appropriate TagLib::X::File subclass constructors + // TODO: use the appropriate TagLib::X::File subclass constructors, + // once we find some way of determining the MIME type. TagLib::FileRef fileRef(fileName.c_str()); if (fileRef.isNull()) { throw std::invalid_argument("unsupported file type"); @@ -462,3 +507,44 @@ UploadFileWindow :: readPlaylength(const std::string & fileName) return length; } + +/*------------------------------------------------------------------------------ + * Determine the type of the given file. + *----------------------------------------------------------------------------*/ +UploadFileWindow::FileType +UploadFileWindow :: determineFileType(const std::string & fileName) + throw () +{ + unsigned int dotPosition = fileName.find('.'); + if (dotPosition == std::string::npos) { + return invalidType; + } + + std::string extension = fileName.substr(dotPosition); + if (extension == ".mp3" || extension == ".ogg") { + return audioClipType; + + } else if (extension == ".tar") { + return playlistArchiveType; + + } else { + return invalidType; + } +} + + +/*------------------------------------------------------------------------------ + * Clear all the input fields and set the fileType to 'invalidType'. + *----------------------------------------------------------------------------*/ +void +UploadFileWindow :: clearEverything(void) throw () +{ + fileNameEntry->set_text(""); + for (unsigned int i=0; i < metadataEntries.size(); ++i) { + Gtk::Entry * metadataEntry = metadataEntries[i]; + metadataEntry->set_text(""); + } + statusBar->set_text(""); + fileType = invalidType; +} + diff --git a/livesupport/src/products/gLiveSupport/src/UploadFileWindow.h b/livesupport/src/products/gLiveSupport/src/UploadFileWindow.h index 90a2f54dc..93c75eecc 100644 --- a/livesupport/src/products/gLiveSupport/src/UploadFileWindow.h +++ b/livesupport/src/products/gLiveSupport/src/UploadFileWindow.h @@ -178,15 +178,83 @@ class UploadFileWindow : public GuiWindow Gtk::Label * statusBar; /** - * The name of the file to upload. + * The audio clip to be uploaded. */ Ptr::Ref audioClip; /** - * Signals if the audio clip is valid. + * The possible file types. */ - bool isAudioClipValid; + typedef enum { audioClipType, playlistArchiveType, invalidType } + FileType; + /** + * The type of the currently selected file. + */ + FileType fileType; + + /** + * Update the information for the file to upload, based on the + * value of the fileNameEntry text entry field. + */ + void + updateFileInfo(void) throw (); + + /** + * Read the playlength and metadata info from the binary audio file. + * + * @param fileName the local file name (with path) for the + * binary audio file. + */ + void + readAudioClipInfo(const std::string & fileName) throw (); + + /** + * Determine the length of an audio file on disk. + * + * @param fileName a binary audio file (e.g., /tmp/some_clip.mp3) + * @return the length of the file; a null pointer if the + * length could not be read (see bug #1426) + * @exception std::invalid_argument if the file is not found, or its + * format is not supported by TagLib + */ + Ptr::Ref + readPlaylength(const std::string & fileName) + throw (std::invalid_argument); + + /** + * Upload an audio clip to the storage. + */ + void + uploadAudioClip(void) throw (); + + /** + * Upload a playlist archive to the storage. + */ + void + uploadPlaylistArchive(void) throw (); + + /** + * Determine the type of the given file. + * + * This method looks at the extension only. + * TODO: replace this with proper mime-type detection + * (gnomevfs, system("file fileName"), or ...?) + * + * @param fileName the name (with path) of the local file. + * @return the type of the file. + */ + FileType + determineFileType(const std::string & fileName) throw (); + + /** + * Clear all the input fields and set the fileType to 'invalidType'. + */ + void + clearEverything(void) throw (); + + + protected: /** * Function to catch the event of the choose file button being * pressed. @@ -208,7 +276,7 @@ class UploadFileWindow : public GuiWindow * @param event the event recieved. * @return true if the event has been processed, false otherwise. */ - bool + virtual bool onFileNameEntryLeave(GdkEventFocus * event) throw (); /** @@ -217,26 +285,6 @@ class UploadFileWindow : public GuiWindow virtual void onCloseButtonClicked(void) throw (); - /** - * Update the information for the file to upload, based on the - * value of the fileNameEntry text entry field. - */ - void - updateFileInfo(void) throw (); - - /** - * Determine the length of an audio file on disk. - * - * @param fileName a binary audio file (e.g., /tmp/some_clip.mp3) - * @return the length of the file; a null pointer if the - * length could not be read (see bug #1426) - * @exception std::invalid_argument if the file is not found, or its - * format is not supported by TagLib - */ - Ptr::Ref - readPlaylength(const std::string & fileName) - throw (std::invalid_argument); - public: /** diff --git a/livesupport/src/products/gLiveSupport/var/root.txt b/livesupport/src/products/gLiveSupport/var/root.txt index 0b8c29650..4be5e4d0d 100644 --- a/livesupport/src/products/gLiveSupport/var/root.txt +++ b/livesupport/src/products/gLiveSupport/var/root.txt @@ -116,7 +116,7 @@ root:table fileChooserDialogTitle:string { "Open File" } - clipUploadedMsg:string { "Uploaded clip ''{0}''." } + fileUploadedMsg:string { "Uploaded ''{0}''." } couldNotOpenFileMsg:string { "The file could not be opened." } couldNotReadLengthMsg:string { "Could not determine audio clip length." }