Editing Documentation/Maemo 5 Developer Guide/Using Data Sharing/Sharing Plug-in
Warning: You are not logged in.
Your IP address will be recorded in this page's edit history.
The edit can be undone.
Please check the comparison below to verify that this is what you want to do, and then save the changes below to finish undoing the edit.
Latest revision | Your text | ||
Line 1: | Line 1: | ||
- | + | =Sharing Plug-in Creation= | |
+ | Maemo 5 introduces a new library for handling Sharing related information and services. This chapter will walk you through the process of creating a sharing plug-in using a template plug-in as an example. Basic knowledge of Debian development and working in a Scratchbox environment is needed in order to follow these guides. | ||
- | + | The example plugin can be browsed via the [[Subversion]] [https://vcs.maemo.org/svn/maemoexamples/trunk/data-sharing-plugin/ web interface] | |
- | + | Sharing API reference documentation is available in the [http://maemo.org/packages/view/libsharing-plugin-doc/ libsharing-plugin-doc] package, or [http://maemo.org/api_refs/5.0/5.0-final/libsharing-plugin/ on maemo.org]. | |
- | + | Before starting to create your own plugin, it is a good idea to check the default services OVI and Flickr. From those you can see two different UI flows for creating a sharing account. | |
- | + | In OVI the UI flow is pretty simple and clear, you just enter the user name and password and then validate the account. In Flickr the flow is more complex, as before validation the user must login to the Flickr website to get authentication and continue the validation after that. | |
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | Sharing account creation can be accessed from the ‘Settings’ application: | |
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
Settings -> Sharing accounts -> New -> Select service ... | Settings -> Sharing accounts -> New -> Select service ... | ||
- | + | ==Sharing User Interfaces== | |
- | + | The Sharing application implements common UI functionality needed for sharing files, and abstracts away much of the other functionality required for a file-sharing application. Sharing Application service support can be extended by using Sharing Plug-ins. These plug-ins can be created by any 3rd party developer. The plug-ins should implement the [http://maemo.org/api_refs/5.0/5.0-final/libsharing-plugin/libsharing-plugin-SharingPluginInterface.html Sharing plug-in API functions listed in the API reference] and define some parameters in a service definition XML file. <!-- Which parameters, where is the documentation regarding the XML format? --> | |
- | Sharing application | + | |
- | Figure 1 below shows Sharing accounts dialog opened from the | + | Figure 1 below shows the Sharing accounts dialog opened from the Settings application. You can create a new Sharing account or edit an existing one from the Sharing dialog. If you install a custom Sharing plug-in, you can see the service it provides in the "Select service list" when creating a new Sharing account. |
Line 124: | Line 23: | ||
- | Figure 2 shows the Sharing Dialog user interface that is used to share images | + | Figure 2 shows the Sharing Dialog user interface that is used to share images to the selected account on some service. The images displayed can come for example from the Photos application or from the device camera. You can choose the account created for your service from the "Account" combo box which holds all existing Sharing accounts. |
[[Image:SharingDialogInterface.png]] | [[Image:SharingDialogInterface.png]] | ||
Line 130: | Line 29: | ||
''Figure 2: Sharing Dialog User Interface'' | ''Figure 2: Sharing Dialog User Interface'' | ||
- | + | ==Getting Familiar With Target Service API== | |
+ | Many web services provide APIs that are available for 3rd party developers. In this tutorial, we focus on services that provide APIs for image and video uploads. It is possible to give the title, description and tags for the images using the common service API. | ||
+ | Sharing supports image scaling and metadata filtering as common options for any service. Sharing plug-ins can have service specific options, like privacy. These settings can be accessed through the Options dialog (as shown in Figure 2.) | ||
- | + | ==Adding Your Dummy Plugin To The Sharing Menus== | |
- | + | As a prerequisite, you will need a working Maemo 5 SDK to continue further. | |
- | == | + | We will first install the Sharing plug-in template: |
- | + | *Obtain the template source code from [https://vcs.maemo.org/svn/maemoexamples/trunk/data-sharing-plugin/ the Maemo examples repository]. It contains a good template file structure for a Sharing plug-in. The source code can be checked out with the command: | |
- | As a prerequisite, you will need a working Maemo 5 SDK to continue further. We will first install the Sharing | + | svn checkout https://vcs.maemo.org/svn/maemoexamples/trunk/data-sharing-plugin/ |
- | + | *Build a Debian package from the folder using the command: | |
- | * Obtain the template source code | + | |
- | * | + | |
./autogen.sh; dpkg-buildpackage -rfakeroot -d | ./autogen.sh; dpkg-buildpackage -rfakeroot -d | ||
- | * Install the | + | *Install the built package to your Scratchbox environment or to the device if it was built with the ARM target. |
- | *The template dummy service should be now visible in the Sharing | + | *The template dummy service should be now visible in the Sharing application when creating new accounts. |
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | <div style="border: 2px solid rgb(255, 215, 0); background-color: rgb(252, 233, 79); margin-left: 25px; margin-right: 25px; padding: 2px"> N.B: Note that this template plug-in does not send any data before you write the implementation for it.</div> | |
- | == | + | =Editing The Template Plug-in= |
+ | In this section, we peek under the hood by opening the files in the template plug-in and get familiar with the Sharing classes that are used in the Sharing plug-in API. | ||
- | Figure 3 shows the general overview of how Sharing | + | ==Sharing Internals== |
+ | Figure 3 shows the general overview of how a Sharing plug-in connects to the Sharing application using the Sharing plug-in API. The basic plug-in components are the service definition file and the plug-in library. The Sharing Application dialog is used to create ''SharingEntries'' that are shared by ''SharingManager''. Sharing Account Manager implements the Sharing Accounts. Libsharing is a library for all common Sharing functionality. | ||
[[Image:SharingPluginAPI.jpg]] | [[Image:SharingPluginAPI.jpg]] | ||
Line 161: | Line 56: | ||
- | Figure 4 shows the common ''Sharing'' classes found in ''libsharing'' that you would use | + | Figure 4 shows the common ''Sharing'' classes found in ''libsharing'' that you would use when creating the plug-in. |
- | + | ''SharingTransfer'' is the object that contains the data of a sharing task (a ''SharingEntry''), including the overall status of the transfer process for a set of shared files. | |
- | + | ''SharingEntry'' contains the ''SharingEntryMedia'', which are the individual files to be shared. It also knows the ''SharingAccount'' that is the target of sharing. | |
- | + | ''SharingAccount'' contains the username and password along with other parameters that you have to save for your service's account. It also has the information about ''SharingService'' which is registered to the ''SharingAccount''. | |
[[Image:LibSharingClasses.jpg]] | [[Image:LibSharingClasses.jpg]] | ||
Line 170: | Line 65: | ||
''Figure 4: Libsharing classes'' | ''Figure 4: Libsharing classes'' | ||
- | To see | + | To see the sharing classes in more detail you can browse to the [https://vcs.maemo.org/svn/maemoexamples/trunk/data-sharing-plugin/ Sharing plug-in example] |
- | + | ==Service XML File==<!-- This needs a link to some reference documentation --> | |
+ | The service definition file, located in the example at data/template.service.xml.in, is the starting point for plug-in loading. It defines the library that implements the Sharing plug-in API functions, the sign-up URL for new account creation with a web browser, the service name, icon file names and some basic information about the plug-in. | ||
- | + | <?xml version="1.0" encoding="UTF-8"?> | |
- | + | <service plugin="libtemplate.so" provider="Me"> | |
- | + | ||
- | <?xml version="1.0" encoding="UTF-8"?> | + | <accounts plugInSetup="0" plugInEdit="0"> |
- | <service plugin="libtemplate.so" provider="Me"> | + | <signup>www.maemo.org</signup> |
- | + | <password maxlen="32"/> | |
- | + | </accounts> | |
- | + | ||
- | + | <ui> | |
- | + | <name>Template</name> | |
- | + | <icon type="post">@servicesdir@/template-post.png</icon> | |
- | + | <icon type="setup">@servicesdir@/template-setup.png</icon> | |
- | + | <options> | |
- | + | <option id="privacy" type="enumeration" default="private"> | |
- | + | <caption domain="osso-sharing-ui" key="share_bd_options_privacy"/> | |
- | + | <value id="private" domain="osso-sharing-ui" key="share_fi_options_privacy_private"/> | |
- | + | <value id="public" domain="osso-sharing-ui" key="share_fi_options_privacy_public"/> | |
- | + | </option> | |
- | + | </option> | |
- | + | </ui> | |
- | + | </service> | |
- | + | ||
- | + | ||
- | </service | + | |
- | + | ||
''File 1: Example service definition XML file'' | ''File 1: Example service definition XML file'' | ||
- | + | The root of the service XML file name "template.service.xml.in", excluding any extensions , "template" in this case, defines the id of the plugin. The "plugin" value in the service definition defines the library that implements the Sharing plug-in API. In this example file the library is libtemplate.so. "Provider" is the developer name, nick, etc. In "accounts", you define the plug-in account setup and edit flows described more in detail in the coming sections. Signup is the URL that is opened in the browser when a new account is created for a service. | |
- | + | ||
- | The | + | |
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | . | + | |
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | Sharing accounts can either create a default flow where | + | ==Account Setup User Interface Flow== |
+ | Sharing accounts can either create a default UI flow where "username" and "password" parameters are set for the account in a standard dialog, or an optional custom flow. In File 1, the "accounts" tag has a parameter "plugInSetup". If it is set to "1", Sharing Accounts will call the Sharing plug-in API function "sharing_plugin_interface_account_setup" to create UI flow. The example in File 1 will use the default flow. | ||
+ | You can see the difference between UI flows when creating a Flickr and an Ovi account. Ovi uses the default flow and Flickr uses its own UI flow. | ||
- | + | SharingPluginInterfaceAccountSetupResult sharing_plugin_interface_account_setup (GtkWindow* parent, SharingService* service, SharingAccount** worked_on, osso_context_t* osso) | |
- | SharingPluginInterfaceAccountSetupResult sharing_plugin_interface_account_setup (GtkWindow* parent, SharingService* service, SharingAccount** worked_on, osso_context_t* osso) | + | |
- | + | ||
If you decide to create your own account setup flow, please try to keep the same UI look as in other Sharing dialogs. | If you decide to create your own account setup flow, please try to keep the same UI look as in other Sharing dialogs. | ||
- | + | ==Account Validation== | |
+ | Account validation is recommended so that errors due to incorrect login details are reduced. It is possible to use the dummy function of the template plug-in<!-- link? -->, but for a better user experience this function should be implemented so that Sharing Account information is validated against the service when a new account is created, not when a Sharing request is initiated. | ||
- | + | The next function is called after the "sharing_plugin_interface_account_setup" call ends, or when the default account setup flow is complete (when the "Validate" button is pressed). | |
- | + | SharingPluginInterfaceAccountValidateResult sharing_plugin_interface_account_validate (SharingAccount* account, ConIcConnection* con, gboolean *cont, gboolean* dead_mans_switch) | |
- | + | In the last phase of account creation, the account must be validated. Sharing plug-in API ''sharing_plugin_interface_test_account'' is the function called in the validation phase of the account creation flow. | |
- | + | Usually web services have a phase in account creation where you have input the needed information from your account, and only then do you get the actual credentials to upload images if your account information is valid. This is the phase that is implemented in the Sharing plug-in API function. | |
- | + | ||
- | + | ==Account Editing User Interface Flow== | |
- | + | Sharing Accounts support here too either default flow where “username” and “password” parameters are edited or optional custom edit UI flow. The wanted flow can be set by setting the parameter "plugInEdit" from the service definition file either to "0" or to "1" where "0" means the default flow and "1" plug-in flow. | |
- | + | ||
- | Sharing Accounts support here too either default flow where | + | |
The default flow can be used when you need only username and password to get needed information for sending. The plug-in flow is used when you need more than this or customised account validation flow. You can see the difference between UI flows here too when editing Flickr and Ovi accounts. | The default flow can be used when you need only username and password to get needed information for sending. The plug-in flow is used when you need more than this or customised account validation flow. You can see the difference between UI flows here too when editing Flickr and Ovi accounts. | ||
- | Next function must be implemented only when plug-in account setup is used (when | + | Next function must be implemented only when plug-in account setup is used (when "plugInSetup" is set to "1"): |
- | + | SharingPluginInterfaceEditAccountResult sharing_plugin_interface_edit_account (GtkWindow* parent, SharingAccount* account, ConIcConnection* con, gboolean* dead_mans_switch) | |
- | + | ||
- | SharingPluginInterfaceEditAccountResult sharing_plugin_interface_edit_account (GtkWindow* parent, SharingAccount* account, ConIcConnection* con, gboolean* dead_mans_switch) | + | |
- | + | ||
- | + | ||
- | + | ||
- | + | ==Sending Functionality== | |
- | + | ||
- | + | ||
- | + | <tt>SharingPluginInterfaceSendResult sharing_plugin_interface_send (SharingTransfer* transfer, ConIcConnection* con, gboolean* dead_mans_switch)</tt> | |
- | * Set progress of | + | After pressing the 'Share' button in the Sharing dialog (Figure 2.), the data is put into the Sharing outbox (can be seen under /home/user/MyDocs/.sharing/outbox/). The Sharing manager process is started and an icon is added to the status menu to process the new Sharing Entry. |
- | * | + | ''SharingHTTP'' provides an API to create common HTTP requests. |
- | * Poll cancel flag time to time, for example in | + | In order to create a better user experience, the following points should be implemented after you get the basic functionality working in your plug-in: |
- | * If you are using ''libcurl'' instead of | + | *Set the progress of the transfer with [http://maemo.org/api_refs/5.0/5.0-final/libsharing-plugin/libsharing-plugin-SharingTransfer.html#sharing-transfer-set-progress sharing_transfer_set_progress], which accepts a gdouble argument of between 0 and 1, to estimate the current or total transfer time. |
+ | *Mark each ''SharingEntryMedia'' (file) as sent with [http://maemo.org/api_refs/5.0/5.0-final/libsharing-plugin/libsharing-plugin-SharingEntryMedia.html#sharing-entry-media-set-sent sharing_entry_media_set_sent] when file sending is complete. Check the sent value with [http://maemo.org/api_refs/5.0/5.0-final/libsharing-plugin/libsharing-plugin-SharingEntryMedia.html#sharing-entry-media-get-sent sharing_entry_media_get_sent] to prevent sending the same file multiple times, for example in reboot scenarios. | ||
+ | *Poll the cancel flag from time to time, for example in the ''libcurl'' or ''SharingHTTP'' progress function to end transferring when needed. Use [http://maemo.org/api_refs/5.0/5.0-final/libsharing-plugin/libsharing-plugin-SharingTransfer.html#sharing-transfer-continue sharing_transfer_continue] to get the continue flag bit. | ||
+ | *If you are using ''libcurl'' instead of ''SharingHTTP'', please listen to ''conic'' events to disconnect transfer when no connection available. It returns with 'no connection' return value in this case. | ||
Next some example source for common tasks found in usual sending functionality: | Next some example source for common tasks found in usual sending functionality: | ||
- | |||
==== Example sending loop ==== | ==== Example sending loop ==== | ||
- | When you process | + | When you process ''SharingEntryMedia'' from the ''SharingEntry'' you propably end up with a loop where you iterate over the list of ''SharingEntryMedia''. Here is a bare example, where some example lines commented out with "//". |
+ | <tt> | ||
- | + | for (GSList* p = sharing_entry_get_media (entry); p != NULL; p = g_slist_next(p)) { | |
- | for (GSList* p = sharing_entry_get_media (entry); p != NULL; p = g_slist_next(p)) { | + | SharingEntryMedia* media = p->data; |
- | + | /* Process media */ | |
- | + | if (!sharing_entry_media_get_sent (media)) { | |
- | + | /* Post media */ | |
- | + | //guint result = my_send_task_post_function (my_send_task, media); | |
- | + | /* Process post result */ | |
- | + | if (result == 0 /* EXAMPLE: MY_SEND_RESULT_SUCCESS */) { | |
- | + | /* If success mark media as sent */ | |
- | + | sharing_entry_media_set_sent (media, TRUE); | |
- | + | /* And mark process to your internal data structure */ | |
- | + | //my_send_task->upload_done += sharing_entry_media_get_size (media); | |
- | + | } else { | |
- | + | /* We have sent the file in last sharing-manager call */ | |
- | + | //my_send_task->upload_done += sharing_entry_media_get_size (media); | |
- | + | } | |
- | + | } | |
- | + | } | |
- | } | + | |
- | </ | + | </tt> |
==== Example tags string ==== | ==== Example tags string ==== | ||
+ | Usually, services provide API for adding tags to images. Here is example source code to create a string containing tag information, with ", " string used as a separator. If the service supports also geotagging you could develop this source further by checking the tag type also. | ||
+ | <tt> | ||
- | + | static gchar* create_tags_str (const GSList* tags) | |
- | + | { | |
- | + | gchar* ret = NULL; | |
- | static gchar* create_tags_str (const GSList* tags) | + | for (const GSList* p = tags; p != NULL; p = g_slist_next (p)) { |
- | { | + | SharingTag* tag = (SharingTag*)(p->data); |
- | + | const gchar* tmp = sharing_tag_get_word (tag); | |
- | + | if (tmp != NULL) { | |
- | + | gchar* new_ret = NULL; | |
- | + | if (ret != NULL) { | |
- | + | new_ret = g_strdup_printf ("%s, %s", ret, tmp); | |
- | + | g_free (ret); /* old return is freed */ | |
- | + | } else { | |
- | + | new_ret = g_strdup (tmp); | |
- | + | } | |
- | + | ret = new_ret; | |
- | + | } | |
- | + | } | |
- | + | return ret; | |
- | + | } | |
- | + | gchar* tags = create_tags_str (sharing_entry_media_get_tags (media)); | |
- | + | </tt> | |
- | } | + | |
- | gchar* tags = create_tags_str (sharing_entry_media_get_tags (media)); | + | |
- | </ | + | |
==== SharingHTTP example ==== | ==== SharingHTTP example ==== | ||
+ | SharingHTTP is intended for HTTP transfers. It is probably easier to use this than libcurl and libconic directly in most cases. Give it a try, at least if you are not familiar with libcurl! | ||
- | < | + | <tt> |
+ | SharingHTTP * http = sharing_http_new (); | ||
+ | SharingHTTPRunResponse res; | ||
+ | res = sharing_http_run (http, "http://example.com/post"); | ||
+ | if (res == SHARING_HTTP_RUNRES_SUCCESS) { | ||
+ | g_print ("Got response (%d): %s\n", sharing_http_get_res_code (http), | ||
+ | sharing_http_get_res_body (http, NULL)); | ||
+ | } else { | ||
+ | g_printerr ("Couldn't get stuff from service\n"); | ||
+ | } | ||
+ | sharing_http_unref (http); | ||
+ | </tt> | ||
- | + | ==Uninstallation== | |
- | + | Libsharing provides ''sharing-account-remover'' binary that can be used to clean Sharing Accounts created for your plug-in. This binary is run by the Debian package's prerm script. Prerm scripts are run just before the package is uninstalled. Change the plug-in id from sharingplugintemplate to match your plug-in id in the script. Your plug-in's id is the service definitions files prefix. For template.service.xml, the prefix is "template". | |
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | Libsharing provides | + | |
Example prerm script: | Example prerm script: | ||
- | < | + | <tt> |
- | #!/bin/sh | + | #!/bin/sh |
- | # You can use sharing-account-remover to remove the accounts at plugin | + | # You can use sharing-account-remover to remove the accounts at plugin |
- | # uninstallation | + | # uninstallation |
- | if [ "$1" = "remove" ]; then | + | if [ "$1" = "remove" ]; then |
- | + | /usr/bin/sharing-account-remover template | |
- | fi | + | fi |
- | </ | + | </tt> |
- | + | ||
- | + | ||
- | After setting up your | + | =Testing your plugin= |
+ | After setting up your scratchbox environment you can start using and testing your own plugin. | ||
Sharing framework consists following packages: | Sharing framework consists following packages: | ||
Line 461: | Line 244: | ||
Select your service and create new account. After one account is created you can use ImageViewer for sharing images and MediaPlayer for sharing video files. | Select your service and create new account. After one account is created you can use ImageViewer for sharing images and MediaPlayer for sharing video files. | ||
- | + | =Sharing your plugin with others= | |
+ | Maemo Extras repository is the best place for your plug-in if you want to get users for it. More information on how you can upload packages to Extras repository can be found [[http://wiki.maemo.org/Uploading_to_Extras here]]. | ||
- | + | Before uploading plugin to public repositories make sure you have updated the debian configuration files to match your information under ./debian folder. | |
- | + | Also make sure that the section is set to [[Documentation/Maemo 5 Developer Guide/Packaging, Deploying and Distributing#Sections | one of the valid sections listed in the packaging guide]]. For data sharing, use "user/multimedia". This way the package will be installable by application manager. Below example version of debian control file. | |
- | + | ||
- | Also make sure that the section is set to [[Documentation/Maemo 5 Developer Guide/Packaging, Deploying and Distributing#Sections|one of the valid sections listed in the packaging guide]]. For data sharing, use | + | |
Source: sharing-plugin-template | Source: sharing-plugin-template | ||
Line 478: | Line 260: | ||
libsharing-plugin-dev | libsharing-plugin-dev | ||
Standards-Version: 3.8.0 | Standards-Version: 3.8.0 | ||
- | |||
- | |||
- | |||
- |
Learn more about Contributing to the wiki.