=== uchobby_afk is now known as uchobby | ||
=== tenach_ is now known as tenach | ||
sKuarecircle_ | Hello | 12:40 |
---|---|---|
serfus | sKuarecircle_, hey there, what are you looking for? | 12:42 |
=== harry is now known as Guest1771 | ||
=== Mohan_chml is now known as IAmNotThatGuy | ||
=== ApOgEE__ is now known as ApOgEE | ||
=== IAmNotThatGuy is now known as Mohan_chml | ||
=== MAfifi is now known as MSK61 | ||
=== alecu_ is now known as alecu | ||
=== ChanServ changed the topic of #ubuntu-classroom to: Welcome to the Ubuntu Classroom - https://wiki.ubuntu.com/Classroom || Support in #ubuntu || Upcoming Schedule: http://is.gd/8rtIi || Questions in #ubuntu-classroom-chat || Event: Ubuntu App Developer Week - Current Session: Manage your source code history! - Version Control with Bazaar - Instructors: MSK61 | ||
MSK61 | Welcome, everybody. | 15:01 |
MSK61 | May I know who's there by saying hi? | 15:02 |
MSK61 | OK. Seems good. Let's tatart. | 15:02 |
MSK61 | Start*. | 15:02 |
MSK61 | Today's session is about source control(aka other names like revision control, version control, ...etc). | 15:03 |
MSK61 | So what's source control? It's the ability to keep the history of how a software project evolved through different stages. | 15:03 |
MSK61 | It maintains information about who contributed to each feature, when different releases were available, ...etc. | 15:04 |
MSK61 | As if someone were able to take snapshots for a software project across different instants of time. | 15:04 |
MSK61 | Historically, there were two models for version control: centralized and distributed. | 15:05 |
MSK61 | Centralized version control works in a client-server fashion, with the server having all the meta-data for the project, while different contributors check out a working tree for the trunk(or a specific version). | 15:06 |
MSK61 | And every time someone wants to commit, he/she must be connected to the server. | 15:06 |
MSK61 | Distributed, on the other hand, worked on peer-to-peer like fashion. | 15:07 |
MSK61 | Each contributor had the full source tree(with all revisions and history), and any contributor can pull from/push to other contributors. | 15:07 |
MSK61 | Clear so far? | 15:07 |
MSK61 | So which model does bzr follow? | 15:08 |
MSK61 | Fine, it follows the latter model, distributed. | 15:08 |
MSK61 | And this has the benenfit that you don't have to be connected to a server to commit; you can just commit as many versions as you can offline, and then push to another repository when you get connected. | 15:09 |
MSK61 | OK. To start, we first need to install bzr. | 15:09 |
MSK61 | In a terminal, type "yum install bzr". | 15:10 |
MSK61 | Should be pretty fast. | 15:10 |
MSK61 | We'll base our example on plain-text files, but of course it should work with any type of files. | 15:12 |
MSK61 | Now create a directory called "myProj" for example. | 15:12 |
MSK61 | mkdir myProj. | 15:12 |
MSK61 | Now, cd myProj. | 15:13 |
MSK61 | mkdir trunk. | 15:14 |
MSK61 | To crearte a trunk branch(; it may be called anythin). | 15:14 |
MSK61 | Now, in the terminal type echo "Thi's the first line" > hello.txt | 15:15 |
ClassBot | maheshmohan asked: What is a trunk? | 15:15 |
MSK61 | A trunk is conventionally the branch which holds the development(read most recent) snapshot of the software project. | 15:16 |
MSK61 | You can give it any name, but trunk is a famous convention. | 15:16 |
MSK61 | Other SCM(source control management) tools might call it tip, or so,. | 15:17 |
MSK61 | Now inside trunk when you list the files you should see hello.txt | 15:17 |
MSK61 | OK for all? | 15:18 |
MSK61 | So everyone is now inside trunk the trunk folder and sees hello.txt file? | 15:19 |
MSK61 | OK. Now we've created a source file(hello.txt in our case). | 15:20 |
MSK61 | ls -AR should produce something like this http://paste.ubuntu.com/502649/ | 15:21 |
MSK61 | Now issue the command bzr stat | 15:21 |
MSK61 | Get out of the trunk folder. | 15:23 |
MSK61 | Remove it with "rm -r trunk". | 15:23 |
MSK61 | OK? | 15:24 |
MSK61 | Now, being inside myProj, issue the command "bzr init-repo ." | 15:24 |
MSK61 | This's needed to tell bzr to create a repository(a place where it'll maintain a project history in). | 15:25 |
MSK61 | ls -AR should yield http://paste.ubuntu.com/502652/ | 15:25 |
MSK61 | OK? | 15:26 |
MSK61 | I'll illustrate what the steps we did so far were for. | 15:32 |
MSK61 | From scratch: | 15:33 |
MSK61 | mkdir myProj => Create a directory for the bzr repository. | 15:33 |
MSK61 | cd myProj => Enter the directory. | 15:33 |
MSK61 | bzr init-repo . => Tell bzr to initialize the repository with metadata. | 15:34 |
MSK61 | Clear so far? | 15:34 |
MSK61 | Anyone missing should be OK by executing these steps from scratch. | 15:34 |
MSK61 | Now, issue | 15:34 |
MSK61 | mkdir trunk => conventional trunk(development) branch | 15:35 |
MSK61 | A repository can hold several branches, for example branches for different releases, different platforms, ...etc. | 15:35 |
MSK61 | But a repository should contain at least one branch(trunk in our case). | 15:36 |
MSK61 | cd trunk => Enter the branch directory. | 15:36 |
MSK61 | bzr init => Initialize the branch metadata. | 15:36 |
MSK61 | You notice now that two different commands for metada are used, one previously for the repository, and one for each branch you create(just trunk in our case). | 15:37 |
MSK61 | And for the branch, if you're inside the branch directory, you don't have to specify the location(like we did with . in the repo). | 15:37 |
MSK61 | Now, create a source file. | 15:38 |
MSK61 | echo "This's the first line." >> hello.txt | 15:38 |
MSK61 | This command created a source file called hello.txt. | 15:38 |
MSK61 | OK? | 15:38 |
MSK61 | Now issue the long awaited command | 15:39 |
MSK61 | bzr stat => Show the status of changed files in the branch. | 15:39 |
MSK61 | You should get this http://paste.ubuntu.com/502661/ | 15:40 |
MSK61 | This means that bzr has found a strange file(a file that hasn't been added to the source control). | 15:41 |
MSK61 | Now to add it, issue | 15:41 |
MSK61 | bzr add => Add all unknown(new, strange) files to the source control. | 15:42 |
MSK61 | bzr stat => This time it'll show http://paste.ubuntu.com/502663/ | 15:42 |
MSK61 | Which means bzr shows that the file has been added. | 15:43 |
MSK61 | However, all these changes have been made into the working tree. | 15:43 |
MSK61 | Everybody is aware of the working tree concept? | 15:44 |
MSK61 | OK. A working tree is the copy of the source files you're locally using on your computer. | 15:45 |
MSK61 | When you change anything there, it doesn't mean it'll be retained by bzr history management. | 15:45 |
MSK61 | Until you tell bzr to do. | 15:45 |
MSK61 | Clear? | 15:45 |
MSK61 | Now we want to tell bzr to add it as a history point in the history log. | 15:46 |
MSK61 | Issue the command: | 15:47 |
MSK61 | bzr commit => Add the changes as a revision in the history. | 15:47 |
MSK61 | You'll be introuduced by an editor to write a description message for what's been done in this revision. | 15:48 |
MSK61 | Type in any message, like "first release"(without the quotes), and save and close. | 15:48 |
MSK61 | Before, at the very first line. | 15:50 |
ClassBot | rooligan_ asked: before or after the "--------" line? | 15:50 |
MSK61 | Before, at the very first line. | 15:50 |
MSK61 | Now issue the command | 15:50 |
MSK61 | bzr log => Brwose the history of our project. | 15:50 |
MSK61 | http://paste.ubuntu.com/502666/ | 15:51 |
MSK61 | Everyone got that? | 15:51 |
MSK61 | The committer would be different in your case of course. | 15:52 |
MSK61 | Congratulations, you've just created your first release. | 15:52 |
MSK61 | Fine, you don't have to check out because you already have all the history locally. | 15:53 |
MSK61 | If you want to make changes, just make changes and commit again. | 15:53 |
MSK61 | A new release, with a new description message. | 15:53 |
MSK61 | OK. I had many other things to talk about, but there isn't really much time left. | 15:54 |
MSK61 | So I just skim on them. | 15:54 |
MSK61 | For pushes, you can push to a remoate or local branch with bzr push command. | 15:54 |
ClassBot | maheshmohan asked: How can I make my code to colloborate with other developers? | 15:54 |
MSK61 | Basically, bzr push and bzr pull. | 15:55 |
MSK61 | bzr push to publish your changes to another branch, bzr pull to get the recent changes from another branch to your local one. | 15:55 |
MSK61 | Remember that you can always commit locally(as many times as you want), and then push later. | 15:56 |
MSK61 | Now to get help about available commands, just type bzr help. | 15:57 |
MSK61 | bzr help commands. | 15:57 |
MSK61 | The online documentation is also very useful and detailed. | 15:58 |
MSK61 | Other useful, widely used commands, are ls, info, remove, ignore, push, pull, annotate. | 15:59 |
=== ChanServ changed the topic of #ubuntu-classroom to: Welcome to the Ubuntu Classroom - https://wiki.ubuntu.com/Classroom || Support in #ubuntu || Upcoming Schedule: http://is.gd/8rtIi || Questions in #ubuntu-classroom-chat || Event: Ubuntu App Developer Week - Current Session: Integrate Ubuntu One in your applications - Instructors: rodrigo | ||
=== MSK61 is now known as MAfifi | ||
rodrigo_ | ah, hello! | 16:04 |
rodrigo_ | shall I start? | 16:04 |
Pendulum | rodrigo_: it's all yours! | 16:04 |
rodrigo_ | ok, so hi everyone | 16:04 |
rodrigo_ | my name is Rodrigo Moya, and I work for the online services team at Canonical | 16:05 |
rodrigo_ | working on GNOME integration of U1 | 16:05 |
rodrigo_ | I guess you all know what Ubuntu One is, but just in case, I'll do a small introduction | 16:05 |
rodrigo_ | U1 is your personal cloud, a place where you can store your files and other data that you can sync between machines | 16:06 |
rodrigo_ | apart from syncing your files, it has integration of syncing of other data, like Tomboy notes, Evolution contacts | 16:07 |
rodrigo_ | as well as syncing from mobile phones | 16:07 |
rodrigo_ | so, my talk is about a new library I wrote during Maverick cycle with 2 goals: | 16:07 |
rodrigo_ | * first, avoid duplication of lots of code in our projects (U1-related) | 16:08 |
rodrigo_ | * and 2nd, but most important, allow 3rd parties to integrate their apps with U1 | 16:08 |
rodrigo_ | so, a bit of background | 16:08 |
rodrigo_ | U1 has 2 components on the desktop, a daemon that takes care of syncing your files | 16:08 |
rodrigo_ | and a personal CouchDB server to sync notes, contacts, bookmarks, gwibber messages, etc | 16:09 |
rodrigo_ | for the CouchDB part, we already have good APIs, like couchdb-glib, which is a C GObject-based API with introspection (so has bindings automatically for several languages) | 16:09 |
rodrigo_ | and desktopcouch, which is a Python API, about which aquarius has already given talks at other developer weeks | 16:10 |
rodrigo_ | for the file syncing daemon, we already had an API, but it was a DBus API | 16:10 |
rodrigo_ | while it is great to be able to communicate with it via DBus | 16:10 |
rodrigo_ | time has proven that 3rd parties don't want to deal with low level details of a DBus API | 16:11 |
rodrigo_ | also, DBus needs you to take care of asynchronous calls, error reporting, data marshaling, etc | 16:11 |
rodrigo_ | that's the code we were duplicating in our U1-related projects | 16:11 |
rodrigo_ | so, the Nautilus plugin and the music store widget (yeah, I forgot to say U1 also has a music store!) and other parts of the system were using this DBus API | 16:12 |
rodrigo_ | but no 3rd party, till now that I know, has made use of it | 16:12 |
rodrigo_ | so, I started writing a library (libsyncdaemon) which is a layer on top of this DBus API | 16:12 |
rodrigo_ | it hides all the complexity | 16:13 |
rodrigo_ | like async calls, data marshalling, etc | 16:13 |
rodrigo_ | and offers a GObject-based C API, with introspection for automatic generation of bindings for several languages | 16:13 |
rodrigo_ | btw, if there are questions, feel free to interrupt me, ping me on #ubuntu-classroom-chat | 16:14 |
rodrigo_ | so, any question so far? | 16:14 |
rodrigo_ | ok, no questions :) | 16:14 |
rodrigo_ | btw, alecu is giving a talk about DBus later on :) | 16:15 |
rodrigo_ | ok, so this library, given the time constraints I had for maverick, only wraps the low level DBus API, so that's what we'll be looking at | 16:15 |
rodrigo_ | but I already started adding high level stuff to the API, so for Natty, there should be much easier ways of doing what we'll be showing during the talk | 16:16 |
rodrigo_ | so, what does wrapping the DBus API mean? | 16:17 |
rodrigo_ | it means that it provides a similar API than what the DBus thuing offers | 16:17 |
rodrigo_ | but with a few helpers | 16:17 |
rodrigo_ | for instance, you don't have to deal with data marshalling | 16:17 |
rodrigo_ | this is very important, since the DBus API returns a lot of data in dictionaries | 16:18 |
rodrigo_ | to read the data in the dictionaries, you need to understand what each field means, apart from knowing the names of the fields | 16:18 |
rodrigo_ | also, it deals with service lifecycle | 16:18 |
rodrigo_ | it shouldn't happen, but sometimes DBus services die | 16:18 |
rodrigo_ | there is a way in Dbus to detect that, but it's a bit lowlevel | 16:19 |
rodrigo_ | so libsyncdaemon does it for you | 16:19 |
rodrigo_ | so, if the dbus sertvice dies and is restarted, libsyncdaemon will keep working, you won't have to recreate the objects, connect to the signals again, etc | 16:19 |
rodrigo_ | so, this for some background, so if there are no questions, we can go to see actual code | 16:20 |
rodrigo_ | ok, seems no questions | 16:21 |
rodrigo_ | so, let's see some code | 16:21 |
rodrigo_ | I'll show the C code, and I'll try to guess the Python code equivalent to the C code | 16:22 |
rodrigo_ | so, in C: | 16:22 |
rodrigo_ | #include <libsyncdaemon/libsyncdaemon.h> | 16:22 |
rodrigo_ | in Python: from gi.repository import SyncDaemon | 16:22 |
rodrigo_ | btw, to see the actual code: bzr get lp:ubuntuone-client | 16:23 |
rodrigo_ | or if you just want the headers/introspection stuff: | 16:23 |
rodrigo_ | sudo apt-get install gir1.0-syncdaemon-1.0 libsyncdaemon-1.0-dev | 16:23 |
rodrigo_ | so, the main object you want to start with is, in C, SyncdaemonDaemon (yeah, redundant :-) | 16:24 |
rodrigo_ | in Python: SyncDaemon.Daemon() | 16:24 |
rodrigo_ | this is the main class, and has access to all the functionalities | 16:24 |
rodrigo_ | first, it provides some functions of its own, not very interesting: | 16:25 |
rodrigo_ | syncdaemon_daemon_connect (or daemon.connect() in python) | 16:25 |
rodrigo_ | syncdaemon_daemon_disconnect | 16:25 |
rodrigo_ | syncdaemon_daemon_quit | 16:25 |
rodrigo_ | it also deals with the startup problem in u1 syncdaemon itself | 16:25 |
rodrigo_ | it needs to do a lot of things before he can respond to the DBus calls, so SyncdaemonDaemon object has a "ready" signal | 16:26 |
rodrigo_ | which signals apps when they can start calling it | 16:26 |
rodrigo_ | so, for instance, you would do in your app: | 16:26 |
rodrigo_ | daemon = syncdaemon_daemon_new (); | 16:26 |
rodrigo_ | g_signal_connect (daemon, "ready", daemon_is_ready_cb, my_data); | 16:27 |
rodrigo_ | and when your callback is called, you can start doing whatever you want with syncdaemon | 16:27 |
rodrigo_ | SyncdaemonDaemon also has a lot of signals for notifying the app about status changes, transfers, etc | 16:27 |
rodrigo_ | so, this is the main object | 16:28 |
rodrigo_ | and since it wraps the dbus API completely, it provides access to the different interfaces implemented by syncdaemon (the dbus service) | 16:28 |
rodrigo_ | there are 6 of them, but only a few of them are of interest to us | 16:29 |
rodrigo_ | there is the config interface, which allows you to check/change the configuration of syncdaemon | 16:29 |
rodrigo_ | like how much bandwidth to use, etc | 16:29 |
rodrigo_ | this is not very interesting to apps, so I'll skip it | 16:29 |
rodrigo_ | ditto for the events interface, it provides notifications of different low level events, so, since we have some high-level ones, I'll skip this one also | 16:30 |
rodrigo_ | the next one is the filesystem interface | 16:30 |
rodrigo_ | you can get a handle to it with: | 16:30 |
rodrigo_ | interface = syncdaemon_daemon_get_filesystem_interface (daemon) <- C | 16:31 |
rodrigo_ | interface = daemon.get_filesystem_interface () <- python | 16:31 |
rodrigo_ | this interface allows you to retrieve metadata for a given file or folder | 16:32 |
rodrigo_ | to get information like whether the file is synced or not, for instance | 16:32 |
rodrigo_ | metadata = syncdaemon_filesystem_interface_get_metadata (interface, path, TRUE or FALSE) | 16:32 |
rodrigo_ | this returns a SyncdaemonMetadata object, which contains that information I was talking about | 16:33 |
rodrigo_ | the next interface is a bit more interesting, so if you're not asleep yet, please listen :) | 16:33 |
rodrigo_ | this next interface is the folders interface, which allows you to synchronize any folder on your $HOME with Ubuntu On | 16:34 |
rodrigo_ | that's interesting, because a photo app, for instance, could easily provide a way for synchronizing a photo album to your personal cloud | 16:35 |
rodrigo_ | (that's the kind of 3rd parties we are looking for, as an example) | 16:35 |
rodrigo_ | this interface, like all others, is retrieved with: | 16:35 |
rodrigo_ | interface = syncdaemon_daemon_get_folders_interface (daemon); | 16:35 |
rodrigo_ | with this interface, you can retrieve the list of folders set up for syncing already: | 16:36 |
rodrigo_ | GSList *syncdaemon_folders_interface_get_folders (SyncdaemonFoldersInterface *interface); | 16:36 |
rodrigo_ | or add new folders (your photo album): | 16:36 |
rodrigo_ | void syncdaemon_folders_interface_create (SyncdaemonFoldersInterface *interface, const gchar *path); | 16:37 |
rodrigo_ | or delete folders you don't want to be synced anymore: | 16:37 |
rodrigo_ | void syncdaemon_folders_interface_delete (SyncdaemonFoldersInterface *interface, const gchar *folder_id); | 16:37 |
rodrigo_ | you can also do things that are not available in the current GUI for maverick | 16:37 |
rodrigo_ | like subscribing/unsubscribing a folder | 16:37 |
rodrigo_ | unsubscribing means that you don't want a specific folder to be copied to this machine | 16:38 |
rodrigo_ | imagine a netbook with very little disk space, you might not want to sync your huge music collection to it | 16:38 |
rodrigo_ | now, to the most interesting one, since it allows to do a lot of nice things | 16:39 |
rodrigo_ | there is the notion of public files, whcih are files that you publish via U1 and that everyone can see, no matter if they have an U1 account or not | 16:40 |
rodrigo_ | this is very nice for, "I'll take a screenshot and show you' kind of things | 16:40 |
rodrigo_ | this is, again, retrived with: | 16:40 |
rodrigo_ | interface = syncdaemon_daemon_get_publicfiles_interface (daemon); | 16:40 |
rodrigo_ | and has a simple call that does everything: | 16:41 |
rodrigo_ | void syncdaemon_publicfiles_interface_change_public_access (SyncdaemonPublicfilesInterface *interface, | 16:41 |
rodrigo_ | const gchar *share_id, | 16:41 |
rodrigo_ | const gchar *node_id, | 16:41 |
rodrigo_ | gboolean is_public); | 16:41 |
rodrigo_ | as you can see, it is low level, since it includes stuff you shouldn't be dealing with | 16:41 |
rodrigo_ | I tried to add a simple publish_file API, where you pass a path only, but it's not as easy as that | 16:41 |
rodrigo_ | first, publishing a file only works on folders that are setup to be synced to U1 | 16:42 |
rodrigo_ | but I plan to add it to the high level API | 16:42 |
rodrigo_ | so, in a screenshot app, for instance, you could add a button that said 'Publish in U1' | 16:42 |
rodrigo_ | and just call that imaginary high level API (syncdaemon_daemon_publish_file) and the file will be published at a given url | 16:43 |
rodrigo_ | something like http://one.ubuntu.com/p/Df3dr | 16:43 |
rodrigo_ | a lot of examples about this raise, like publishing photos from the photo app | 16:44 |
rodrigo_ | and I'm sure you can find a lot | 16:44 |
rodrigo_ | the last interface we're going to see is the shares ones, which allows you to share a folder already in U1 with someone else | 16:44 |
rodrigo_ | as well as deal with shares offered by other users to you | 16:45 |
rodrigo_ | void syncdaemon_shares_interface_accept (SyncdaemonSharesInterface *interface, const gchar *share_id); | 16:45 |
rodrigo_ | void syncdaemon_shares_interface_reject (SyncdaemonSharesInterface *interface, const gchar *share_id); | 16:45 |
rodrigo_ | these 2 are for accepting or rejecting shares by other users | 16:45 |
rodrigo_ | void syncdaemon_shares_interface_create (SyncdaemonSharesInterface *interface, | 16:46 |
rodrigo_ | const gchar *path, | 16:46 |
rodrigo_ | GSList *usernames, | 16:46 |
rodrigo_ | const gchar *name, | 16:46 |
rodrigo_ | gboolean allow_modifications); | 16:46 |
rodrigo_ | void syncdaemon_shares_interface_delete (SyncdaemonSharesInterface *interface, const gchar *share_id); | 16:46 |
rodrigo_ | and these 2 for sharing with someone or deleting a share | 16:46 |
rodrigo_ | to finish, I'll talk a little bit about libubuntuone, which was supposed to be a widget library for easy use of U1 features in GTK apps | 16:47 |
rodrigo_ | there was a contacts picker (the one you see when you click on Ubuntu One->Share in Nautilus | 16:47 |
rodrigo_ | and a music store widget | 16:47 |
rodrigo_ | thanks to that music store widget, banshee plugin for the U1 music store was written by directhex in a few days | 16:48 |
rodrigo_ | so, I think that's all, I'll leave the rest for questions, if there are any | 16:48 |
rodrigo_ | hmm, no questions, is anyone listening :) | 16:49 |
rodrigo_ | ah, a question: | 16:49 |
rodrigo_ | <zinga49> QUESTION: so this will be available in maverick then? | 16:49 |
rodrigo_ | zinga49, yes, it already is | 16:49 |
rodrigo_ | what is not available in maverick is a better high level API, but it should probablt be (or part of it) for natty | 16:50 |
rodrigo_ | QUESTION: and to use it in pyhton i install the above 2 modules? | 16:50 |
rodrigo_ | well, for only python, you don't need the -dev package, so gir1.0-syncdaemon-1.0 should be enough | 16:51 |
rodrigo_ | rye (in #ubuntuone) had some code using the automatic Python bindings, so ask him for an example | 16:51 |
rodrigo_ | so, with this, it should be easy to provide high level stuff integrating with U1 | 16:52 |
rodrigo_ | I already mentioned some simple examples, but I'm sure you can find lots of apps in Ubuntu that would make sense to have something like that | 16:52 |
rodrigo_ | like publishing files, etc | 16:53 |
rodrigo_ | f-spot, gnome-screenshot, etc | 16:53 |
rodrigo_ | ok, so no more questions? | 16:54 |
rodrigo_ | <ara> QUESTION: rodrigo_, is the client code in maverick already using your library? | 16:55 |
rodrigo_ | ara, the C parts of it are, like nautilus plugin, tomboy (using a part of the API I didn't mention, which is for registering a user with U1) and the music store widget | 16:56 |
rodrigo_ | the rest of the client code in maverick is either the daemon itself, which doesn't need this api | 16:56 |
rodrigo_ | and u1-prefs, which doesn't use the DBus service | 16:57 |
rodrigo_ | <ara> QUESTION: rodrigo_: so, no Python example code yet ;-) | 16:57 |
rodrigo_ | ara, yes, ask rye in #ubuntuone, he wrote an app to automatically publish files, I think | 16:58 |
rodrigo_ | also, worthy to say that this has been added in maverick, so the only place that I changed to use it was the nautilus plugin | 16:59 |
rodrigo_ | which had a lots of complicated code to deal with the DBus stuff | 16:59 |
rodrigo_ | and the music store widget, which only needed a couple of calls (mainly for tracking transfers) | 16:59 |
rodrigo_ | so, the already existing code wasn't changed, but I would do asap if I had time :-) | 16:59 |
=== ChanServ changed the topic of #ubuntu-classroom to: Welcome to the Ubuntu Classroom - https://wiki.ubuntu.com/Classroom || Support in #ubuntu || Upcoming Schedule: http://is.gd/8rtIi || Questions in #ubuntu-classroom-chat || Event: Ubuntu App Developer Week - Current Session: Hacking on 'testdrive' - Instructors: andreserl | ||
rodrigo_ | ok, I think we're out of time | 17:00 |
=== RoAkSoAx is now known as andreserl | ||
=== andreserl is now known as RoAkSoAx | ||
RoAkSoAx | Alright :) | 17:01 |
RoAkSoAx | Hi everybody! Welcome to the Hacking on TestDrive Session. | 17:01 |
RoAkSoAx | My Name is Andres Rodriguez and I'm a community member. My areas of contribution are MOTU, the Server Team, and the Cluster Stack! (And now application | 17:01 |
RoAkSoAx | programming :)) | 17:01 |
RoAkSoAx | I want to know who's here for the session so that I can begin | 17:02 |
RoAkSoAx | Ok, let's get started then | 17:03 |
RoAkSoAx | Unfortunately, due to some unexpected situations (Fumigation at my place, and Ubuntu won't connect to Wireless-N Router where I'm at), I won't be able to do | 17:03 |
RoAkSoAx | all I wanted to do today. Anyways, today's session is going to be: | 17:03 |
RoAkSoAx | 1. Introduction | 17:03 |
RoAkSoAx | 2. My Experience | 17:03 |
RoAkSoAx | 3. How to Contribute | 17:03 |
RoAkSoAx | 4. Questions | 17:03 |
RoAkSoAx | NOTE: I'll reply any of your questions at the end of the session. | 17:04 |
RoAkSoAx | Just before I begin, please install "quickly" given that it may take some time. | 17:04 |
RoAkSoAx | sudo apt-get install quickly | 17:04 |
RoAkSoAx | let me know when you have quickly installing | 17:04 |
RoAkSoAx | rooligan_: Ok, for those who don't know what quickly is. Quickly is a tool that allows the creation of Ubuntu Apps easily you can read more about it here: https://wiki.ubuntu.com/Quickly | 17:06 |
RoAkSoAx | == Introduction == | 17:06 |
RoAkSoAx | TestDrive is a tool that was originally developed by Dustin Kirkland to test Ubuntu ISO images. He presented the tool at the Ubuntu Developer Summit in | 17:06 |
RoAkSoAx | Dallas last year. Back then, the tool was a shell script. Then, he decided to change it to a single file python script. Nowadays, and with the creation of | 17:06 |
RoAkSoAx | TestDrive PyGTK, it is a collection of classes/modules. It is not perfect, but it works. | 17:06 |
RoAkSoAx | Btw, I'm glad to say that TestDrive PyGTK was my GSoC2010 project for Ubuntu, and my first real experience with python and GTK. | 17:07 |
RoAkSoAx | , though I started working on | 17:07 |
RoAkSoAx | it way before the GSoC. | 17:07 |
RoAkSoAx | now, how many of you here have used or currently use TestDrive? | 17:07 |
RoAkSoAx | ok well, now we all know what TestDrive is: And App to test Ubuntu ISO images, but it is not limited to Ubuntu specific only | 17:09 |
RoAkSoAx | given that we can test other ISO's that are in our harddrive, or even in any other repository | 17:09 |
RoAkSoAx | So anywa,s I'd like now to tell you in a short summary my experience coding it :) | 17:09 |
RoAkSoAx | == My Experience with App Development for/in Ubuntu == | 17:09 |
RoAkSoAx | First of all, I'd like to say that my experience developing TestDrive for Ubuntu was really fun and satisfactory. | 17:09 |
RoAkSoAx | And well it all started when I grabbed the TestDrive python script. The objectives were: | 17:09 |
RoAkSoAx | 1. Modify TestDrive in such a way that it would be easy to attach different fron-ends. | 17:09 |
RoAkSoAx | 2. Add the PyGTK Front-end. | 17:10 |
RoAkSoAx | The first, so called modularization, was to separate the python script's UI code with the "back-end's" code. This would be what we all call MVC. By | 17:10 |
RoAkSoAx | separating this code, I was making sure that I could re-use most of it when Implementing the Front-end. | 17:10 |
RoAkSoAx | Modularization, step 1: So, a class TestDrive was created to handle all the "back-end" work of testdrive, having an UI that just instances an object of the | 17:10 |
RoAkSoAx | class, and access this class' methods to do most of the work. At this point, I had separated TestDrive code in two, a binary (testdrive) and a module | 17:10 |
RoAkSoAx | (testdrive.py). | 17:10 |
RoAkSoAx | Modularization, step 2: Now, given that we use different virtualization methods and that it would be nice to add new ones, we also decided to separate the | 17:11 |
RoAkSoAx | virt method from the testdrive module. For this, we created three virt modules (kvm.py, vbox.py, parallels.py). These modules, do all the configuration | 17:11 |
RoAkSoAx | necessary to run an ISO in the VM, and return a command ready to launch the VM. | 17:11 |
RoAkSoAx | So, so far, we are learning that TestDrive test's ISO by running them in a VM, either using KVM, VirtualBox or Parallellels | 17:12 |
RoAkSoAx | the preferred method is, of course, KVM | 17:12 |
RoAkSoAx | So, what about the PyGTK. Well, after the first step of the modularization, I started working on the Front-end. before starting with it, I figured that it | 17:13 |
RoAkSoAx | would be a good idea to use quickly to create the front-end, and so I did. Quickly is such an amazing tool!! Anyways, I started the front-end with a single | 17:13 |
RoAkSoAx | list of ISOs, then turned into separated categories of ISO's, and so on. | 17:13 |
RoAkSoAx | Now, up to this point, we not only had a command line tool to test ISO, but a simple PyGTK tool with the same purpose | 17:14 |
RoAkSoAx | I faced one problem though! Which was being able to Sync and Run Multiple ISO/VM's at the same time. For that I implemented python threading for Syncing | 17:14 |
RoAkSoAx | ISO's, and that's when I also decided to go for the modularization of Virt Methods, to provide better threading support. | 17:14 |
RoAkSoAx | After this, I also implemented the posibility to add Other ISO's not in the Ubuntu repository. Of course, along all this I worked on other stuff such as | 17:14 |
RoAkSoAx | implementeing the preferences, improving the UI, improving the preferences methods and changed the configuration to configparser, logging capabilities, bug | 17:14 |
RoAkSoAx | fixing, etc etc. | 17:14 |
RoAkSoAx | so, TestDrive PyGTK ended up being a cool tool where I could launch multiple ISO in a VM, or sync them. I also have the possiblity to change preference | 17:15 |
RoAkSoAx | such as how much disk size per VM image, how much RAM to assign, How many CPUs, at all sort of that stuff | 17:15 |
RoAkSoAx | SO, to do all this I pretty much had to learn python and GTK from scratch; and in only three months I learned a lot!! and it was Super FUN. A cool tool that helped me in the process was Acire!! | 17:16 |
=== JanC_ is now known as JanC | ||
RoAkSoAx | I'm sure that all of you know what Acire is, if not... it is a toold that puts together lots of python snippets that can help any developer by showing them how to implement these snippets | 17:16 |
RoAkSoAx | So yes, this a kinda confusing sum up of how TestDrive PyGTK came to live... anyone has any questions so far? | 17:17 |
RoAkSoAx | Ok then | 17:19 |
RoAkSoAx | OK so now, you might wonder... how can I contribute ?? | 17:20 |
RoAkSoAx | Well, there are many ways to contribute with TestDrive... In fact, couple people already contributed, and471_ is one, am I correct :) | 17:21 |
RoAkSoAx | anyways, to code testdrive I created a Blueprint to track all what was necessary. We had a session at UDS-M in Belgium where we talked about what were the features that ppl wanted to see in teh PyGTK | 17:22 |
RoAkSoAx | most of these features are detailed in: | 17:22 |
RoAkSoAx | https://blueprints.launchpad.net/ubuntu/+spec/desktop-maverick-testdrive-frontend-gsoc | 17:22 |
RoAkSoAx | If you can see in the section "Work items for ubuntu-later:" | 17:23 |
RoAkSoAx | all those work items are to be done | 17:23 |
RoAkSoAx | most of them is adding simple features or improvements | 17:23 |
RoAkSoAx | However, contributions are not only limited to do that | 17:24 |
RoAkSoAx | There're some other things to be done such as | 17:24 |
RoAkSoAx | - Maintainance and improvements of other Virtualization Methods (VBox and Parallels) - Add new Virtualization Methods (Such as VMWare) - Various other improvements - PyQT4 Front-end | 17:24 |
RoAkSoAx | As I mentioned earlier, TestDrive can either work with KVM, Vbox and Parallels, being KVM the preferred method | 17:24 |
RoAkSoAx | but, I'm not a VBox user and there are lots of VBox users out there | 17:25 |
RoAkSoAx | that use TestDrive with VBox, but since I'm not a user | 17:25 |
RoAkSoAx | I can't always maintain the VBox (or Parallels code) as I can with KVM | 17:26 |
RoAkSoAx | so, we are always looking for people to work on VBox or Parallels, or even, to implement other virtualizations, such as VMWare | 17:26 |
RoAkSoAx | We actually have a bug report asking us to do that, but we (Dustin and me), prefer having someone contribute on that part | 17:27 |
RoAkSoAx | so if you are really interes in crontributed on that,and use VBox, Parallels, or VMWare... jsut ping me sometime | 17:27 |
RoAkSoAx | and you can get started :) | 17:27 |
RoAkSoAx | Now, one things that would be cool too is to have a PyQT4 front-end for Kubuntu | 17:28 |
RoAkSoAx | that would be great front-end to have for those users | 17:28 |
RoAkSoAx | so, there's another place where people who works with PyQT can contribute | 17:28 |
RoAkSoAx | and well, I'll guide you through the development if you wish | 17:29 |
RoAkSoAx | anywas, we also need testers to test and report bugs agains testdrive | 17:29 |
RoAkSoAx | to report any bugs that I might have missed | 17:29 |
RoAkSoAx | I know there are a few, which I'm already planing to address | 17:30 |
RoAkSoAx | but if someone wants to chip in | 17:31 |
RoAkSoAx | you are more than welcom :) | 17:31 |
RoAkSoAx | most of these are mainly improvements to the current code | 17:31 |
RoAkSoAx | however, if you have more ideas that you think might be interesting to have in TestDrive, please bring them on | 17:32 |
RoAkSoAx | well now we;ve been over a few things that it's needed to be done | 17:32 |
RoAkSoAx | but, if you'd like to contribute, how can you get started??? | 17:32 |
RoAkSoAx | it is easy!! | 17:32 |
RoAkSoAx | I'll give you a guided tour through the code :) | 17:32 |
RoAkSoAx | first step is to get the source | 17:33 |
RoAkSoAx | so place yourself in whatever folder you like from your terminal | 17:33 |
RoAkSoAx | and get the latest branch: | 17:33 |
RoAkSoAx | bzr branch lp:testdrive | 17:33 |
RoAkSoAx | and then enter the created folder | 17:33 |
RoAkSoAx | cd testdrive | 17:33 |
RoAkSoAx | rooligan_: :) | 17:34 |
RoAkSoAx | anywas, do you all now have the branch? | 17:34 |
RoAkSoAx | ok while you get it, I'll continue with teh explanation | 17:35 |
RoAkSoAx | when I created the PyGTK with quickly, I did it as a separate project | 17:35 |
RoAkSoAx | and then I had to merge it altogether | 17:36 |
RoAkSoAx | the meging was quite easy | 17:36 |
RoAkSoAx | it was just a correct organization | 17:37 |
RoAkSoAx | of the code in the source directory | 17:37 |
RoAkSoAx | once that was done, everything is done quickly | 17:37 |
RoAkSoAx | :) | 17:37 |
RoAkSoAx | the code is somehow divided like this: | 17:38 |
RoAkSoAx | bin -> contains testdrive-gtk and testdrive binaries | 17:38 |
RoAkSoAx | testdrive -> contains the testdrive.py, and virt modules | 17:38 |
RoAkSoAx | testdrivegtk -> contains all the classes related to the gtk | 17:38 |
RoAkSoAx | data -> contains two directories, media and ui | 17:39 |
RoAkSoAx | media contains all the iamges | 17:39 |
RoAkSoAx | and ui contains the .glade and .xml files for the UI | 17:39 |
RoAkSoAx | so, how can I start working on them | 17:40 |
RoAkSoAx | it is really simple | 17:40 |
RoAkSoAx | first of all | 17:40 |
RoAkSoAx | lets set up our python path | 17:40 |
RoAkSoAx | we are placed somewhere here: '/home/<user>/<whatever>/testdrive' | 17:40 |
RoAkSoAx | so we will set it up like: export PYTHONPATH=/home/<user>/<whatever>/testdrive | 17:41 |
RoAkSoAx | or export PYTHONPATH=`pwd` | 17:41 |
RoAkSoAx | once that is done | 17:41 |
=== asd is now known as Guest84970 | ||
RoAkSoAx | do this "quickly run" | 17:41 |
RoAkSoAx | that should launch testdrive | 17:41 |
RoAkSoAx | does it? :) | 17:41 |
RoAkSoAx | ok so by doing quickly run from the source directory of testdrive's branch | 17:42 |
RoAkSoAx | it launched testdrive | 17:42 |
RoAkSoAx | how quick and easy is that | 17:42 |
RoAkSoAx | now, what if we wanna launch the command line app? | 17:43 |
RoAkSoAx | that';s more complicated: python bin/testdrive | 17:43 |
RoAkSoAx | but easy as well | 17:43 |
RoAkSoAx | so ok, now I want to do changes | 17:43 |
RoAkSoAx | lets do "quickly edit" | 17:43 |
RoAkSoAx | what this will do is open all related testdrive files in your default editor | 17:43 |
RoAkSoAx | should be gedit | 17:43 |
RoAkSoAx | but now, I want to do changes to the UI specifcally | 17:44 |
RoAkSoAx | well... "quickly design" | 17:44 |
RoAkSoAx | rooligan_: that means it does not support kvm... so probaly you'll need to isntall virtualbox | 17:45 |
RoAkSoAx | so well, I used 3 simple quickly commands | 17:45 |
RoAkSoAx | to get started | 17:45 |
RoAkSoAx | now, if I make any changes to any of the files | 17:45 |
RoAkSoAx | or UI | 17:45 |
RoAkSoAx | I simply do "quickly run" again to see the changes | 17:45 |
RoAkSoAx | but how can I see the diff | 17:45 |
RoAkSoAx | the way I do it is: bzr diff | 17:46 |
RoAkSoAx | or bzr status to see which files where modified | 17:46 |
RoAkSoAx | unfortunately I was planning to go a little bit more over this, but as per unexpected events I'm sshing into my VPS from a Windows machine | 17:46 |
RoAkSoAx | anywas, it ios really simple to start hacking on testdrive | 17:47 |
RoAkSoAx | now you have the code layout | 17:47 |
RoAkSoAx | how to start running it | 17:47 |
RoAkSoAx | how to open all editable files | 17:47 |
RoAkSoAx | how to open the UI | 17:47 |
RoAkSoAx | in an easy way | 17:47 |
RoAkSoAx | all using quickly | 17:47 |
RoAkSoAx | how fun is that?? :) | 17:47 |
RoAkSoAx | Now, to finalize | 17:47 |
RoAkSoAx | == Questions == | 17:48 |
RoAkSoAx | Anyone? | 17:48 |
RoAkSoAx | zinga49: shoot | 17:49 |
RoAkSoAx | zinga49: when I say "synced" I mean, obtain the Ubuntu ISO image from a given repository. For example, by default when ever I sync an ISO Image it will obtain, or download it from http://cdimage.ubuntu.com | 17:50 |
RoAkSoAx | and that's what the "Sync" button means at the main UI | 17:50 |
RoAkSoAx | In other words, download the ISO Image from the repository | 17:50 |
RoAkSoAx | zinga49: If it is already downloaded, it will update it, if there were any changes | 17:51 |
RoAkSoAx | off course, this is something that we have to decide to do | 17:51 |
RoAkSoAx | for example, in testdrive command line, what was done, was to synchronize (or download the changes) every time we were about to run an ISO | 17:52 |
RoAkSoAx | but it came to the point "What if I want to run yesterday's ISO and not Today's ISO" | 17:52 |
RoAkSoAx | so in the Front-end, we have the option to Sync, or to launch | 17:52 |
RoAkSoAx | Sync will obtain the latest ISO from the repo , which by default is the daily Ubuntu ISO (cdimage.ubuntu.com) | 17:53 |
RoAkSoAx | Anymore questions? | 17:54 |
RoAkSoAx | zinga49: not really. It is just peronal preference. In fact, quicly AFAIK defaults to gedit but if you set export EDITOR=vim, it should use vum as the editor | 17:55 |
RoAkSoAx | and so on | 17:55 |
RoAkSoAx | zinga49: and I do code using a combination of vim/gedit depending on how I feel like that day :) | 17:56 |
RoAkSoAx | ok then we have 3 more minutes | 17:57 |
RoAkSoAx | any other question? | 17:57 |
RoAkSoAx | Ok I guess not. thank you all :) Enjoy the App Dev Week, and ping me if you awnt to get involved with TestDrive | 17:58 |
=== ChanServ changed the topic of #ubuntu-classroom to: Welcome to the Ubuntu Classroom - https://wiki.ubuntu.com/Classroom || Support in #ubuntu || Upcoming Schedule: http://is.gd/8rtIi || Questions in #ubuntu-classroom-chat || Event: Ubuntu App Developer Week - Current Session: Take control of your desktop with DBus - Instructors: alecu | ||
alecu | hello all | 18:00 |
alecu | my name is alecu, but some people know me as Alejandro J. Cura as well | 18:01 |
alecu | I'm a Canonical developer working on Ubuntu One | 18:01 |
alecu | and also a Python Argentina member | 18:01 |
alecu | today I'll be giving some tips on how to get started using DBus | 18:01 |
alecu | DBus is a way so programs can talk to programs | 18:02 |
alecu | it's composed of a daemon and many program speaking to that daemon | 18:02 |
alecu | and the daemon will route what one program is saying to some other programs that want to listen to that | 18:03 |
alecu | this way all the apps that make up your desktop can talk to each other, independently of the language they are coded on, | 18:03 |
alecu | and no matter if they are kde apps or gnome apps or system daemons. | 18:04 |
=== Goomba is now known as Guest678 | ||
=== alecu__ is now known as alecu | ||
alecu | so, regarding what apps use dbus... | 18:27 |
alecu | just a few I've been looking at: | 18:27 |
alecu | upstart, gnome-display-manager, telepathy, networkmanager, pulseaudio, vlc | 18:27 |
alecu | there are lots! | 18:27 |
alecu | so, you can use dbus to export services that your app provides | 18:27 |
alecu | and you can use dbus to use those services from other apps or scripts. | 18:27 |
alecu | d-feet is a great way to learn about dbus | 18:27 |
alecu | let's all install it: | 18:27 |
alecu | sudo apt-get install d-feet | 18:27 |
alecu | and meanwhile you can all tell me what is your main development language. | 18:27 |
alecu | d-feet is a great way to learn about dbus | 18:27 |
alecu | let's all install it: | 18:27 |
alecu | sudo apt-get install d-feet | 18:27 |
alecu | and meanwhile you can all tell me what is your main development language. | 18:27 |
alecu | so, let's open d-feet | 18:27 |
alecu | go to file->connect to Session bus | 18:27 |
alecu | on the right hand you'll see a lot of Bus Names | 18:27 |
alecu | sorry | 18:27 |
alecu | left hand | 18:27 |
alecu | clicking on one of the bus names you'll see the paths exported by the objects on that bus | 18:27 |
alecu | (I know I'm talking on the wrong channel, but I'm getting disconnected very often, and the nick registered on the main channel is only one) | 18:28 |
alecu | each object may implement several interfaces | 18:28 |
alecu | in the interfaces you'll find methods and signals | 18:28 |
alecu | those are the bits that make up dbus: | 18:28 |
alecu | Buses -> object paths -> interfaces -> [methods, signals, properties] | 18:28 |
alecu | does it make sense? | 18:28 |
alecu | let's try playing with some interface | 18:28 |
alecu | so... make sure you have vlc installed, and fire it up | 18:28 |
alecu | In vlc the dbus control interface has to be manually activated from tools->preferences->show settings:All->Interface->control interface->D-Bus control interface | 18:28 |
alecu | (most other apps already have dbus plugged in, but vlc makes for a nicer experiment) | 18:28 |
alecu | so, after restarting vlc (just close it and open it again), go to dfeet | 18:28 |
alecu | bus: org.mpris.vlc -> path: /TrackList -> Interfaces -> org.freedesktop.MediaPlayer -> Methods -> AddTrack | 18:28 |
alecu | so, double click on "AddTrack" in dfeet | 18:28 |
alecu | and put: "http://upload.wikimedia.org/wikipedia/commons/7/79/Big_Buck_Bunny_small.ogv", True | 18:28 |
alecu | in parameters | 18:28 |
alecu | and click on "Execute" | 18:28 |
alecu | it should start playing | 18:28 |
alecu | then let's close that dialog | 18:28 |
alecu | and go to the object path /Player | 18:28 |
alecu | on the org.freedesktop.MediaPlayer interface we'll find | 18:28 |
alecu | Pause | 18:28 |
alecu | Play | 18:28 |
alecu | PositionGet | 18:29 |
alecu | PositionSet | 18:29 |
alecu | let's play a bit with those | 18:29 |
alecu | well, this is what dbus is used for. | 18:29 |
alecu | to let very different programs, coded in different languages, even belonging to different desktop environments talk to each other. | 18:29 |
alecu | (sorry, catching up with the logs) | 18:29 |
alecu | so... | 18:30 |
alecu | the common use case for dbus is to have two instances of the dbus daemon running | 18:30 |
alecu | one instance is the system bus: used for daemons that run as root or having to do privileged operations | 18:31 |
alecu | the other is the session bus, (well, one is started per logged in user) | 18:31 |
alecu | the apps that run as your unix user are all using that session bus | 18:31 |
alecu | (you can start up other daemon instances in order to run functional tests and so on) | 18:32 |
alecu | let's get back to the dbus parts | 18:33 |
alecu | we already said we have a bus, with a bus name | 18:33 |
alecu | and we have objects exported thru some object path | 18:34 |
alecu | and those objects implement some interfaces | 18:34 |
alecu | most of this is just for having some order | 18:34 |
alecu | there's nowhere set in stone how this should be partitioned. And you don't need to register anywhere in order to ask for a name to use here (unlike dnss or some other ipc systems) | 18:35 |
alecu | you just pick a few names, and you are ready to go. | 18:35 |
alecu | But it really makes sense to write some spec so other apps can easily use your services. | 18:36 |
alecu | For instance, for the vlc demo we were doing we were using the services in this spec: http://xmms2.org/wiki/MPRIS | 18:36 |
alecu | a few media playing apps developers wrote that spec so different remote control programs can control different media playing programs. | 18:37 |
alecu | any questions? | 18:37 |
ClassBot | MAfifi asked: Do all players provide such a specification? | 18:39 |
alecu | MAfifi, many players are moving to it. I'm not sure if all will | 18:39 |
alecu | MAfifi, anyway, not all dbus services are specced at all. | 18:40 |
alecu | MAfifi, I try to make a spec for the services I write, for instance the ubuntu-sso-client dbus service... let me fetch the link | 18:40 |
alecu | https://wiki.ubuntu.com/SingleSignOn/UbuntuSsoClient | 18:41 |
alecu | let's branch some sample code... | 18:41 |
alecu | bzr branch lp:~alecu/+junk/dbus-lessons | 18:41 |
alecu | in the first example, 01-call_method.py we'll see how we connect to a bus, and call a method on that bus | 18:43 |
alecu | I'm using the /org/gnome/PowerManager/Backlight dbus object, that exports a way to set the display brightness | 18:43 |
alecu | I know it works on my lenovo laptop, and I know it won't work on my old compaq one, so if it does not work for you, try adapting it to control vlc | 18:44 |
alecu | in this sample I'm using the dbus integration with the gtk main loop (the lower level gobject main loop in fact) | 18:45 |
alecu | dbus has integration with qt as well, and with twisted, and probably some other frameworks too. | 18:45 |
ClassBot | papibe asked: how do I see the sample code? | 18:46 |
alecu | papibe, try running this in a terminal: | 18:47 |
alecu | bzr branch lp:~alecu/+junk/dbus-lessons | 18:47 |
alecu | papibe, it will fetch the sample code from its bazaar repository on launchpad | 18:47 |
alecu | so, the code gets at a bus, looks up an object in that bus given the object path | 18:48 |
alecu | and gets an interface implemented by that object | 18:48 |
alecu | then it starts calling the SetBrightness method in that interface | 18:48 |
alecu | and that's how you call a method provided by some other app. | 18:50 |
alecu | now, it may be the case that the app wants to tell you something | 18:51 |
alecu | for instance, a media player app may want to tell you that it has finished playing a file | 18:51 |
alecu | or perhaps you are listening on the bus for an instant messaging app and want to know when a message arrives... | 18:52 |
alecu | for this DBus provides Signals | 18:52 |
alecu | Signals are specified by the app providing a service, and are fired in response to some action happening in the app. | 18:53 |
alecu | your program or script may listen to some those signals. | 18:53 |
alecu | we can see that in the 02-listen_signal.py script | 18:54 |
alecu | in the line: iface.connect_to_signal("BrightnessChanged", brightness_changed_callback) | 18:54 |
alecu | we are hooking a function to a signal | 18:54 |
alecu | if I run that sample, I see the signal being fired when I change the brightness using my keyboard | 18:55 |
alecu | and finally, on 03-export_object.py we can see a sample of exporting a method and a signal on the bus | 18:55 |
alecu | we didn't got around to explaining Data Types on Dbus, but if this all was interesting, you may continue here: http://dbus.freedesktop.org/doc/dbus-python/doc/tutorial.html | 18:56 |
ClassBot | MAfifi asked: How may I make my program emit a signal? | 18:58 |
alecu | MAfifi, great question | 18:58 |
alecu | MAfifi, your program can only emit signals that it implements itself | 18:59 |
alecu | MAfifi, it cannot fire signals implemented by other modules | 18:59 |
alecu | MAfifi, the way to fire the signals, is to just call the method where they are defined | 18:59 |
alecu | MAfifi, in the python dbus, a signal is emmited if the method decorated with @signal returns properly (ie, it does not fire an exception) | 19:00 |
=== ChanServ changed the topic of #ubuntu-classroom to: Welcome to the Ubuntu Classroom - https://wiki.ubuntu.com/Classroom || Support in #ubuntu || Upcoming Schedule: http://is.gd/8rtIi || Questions in #ubuntu-classroom-chat || | ||
alecu | MAfifi, thought the python method may be blank, in which case it will just work. | 19:00 |
alecu | I hope you liked this session, thanks all for attending. | 19:01 |
jcastro | thanks alecu! | 19:01 |
alecu | my mail is alecu@canonical.com or alecu@protocultura.net | 19:01 |
alecu | in case you have some other Q | 19:01 |
alecu | bye! | 19:01 |
jcastro | ok so we have a last minute change, due to Seif having to reschedule his class. So the session on Zeitgeist will be moved to tomorrow | 19:01 |
jcastro | please see the /topic for the session | 19:01 |
jcastro | so since this was kind of last minute, we're going to take a break | 19:02 |
jcastro | HOWEVER | 19:02 |
seiflotfy | jcastro, I can answer general zeitgeist questions | 19:02 |
jcastro | Jono is doing open Ubuntu Q+A if you want to participate there for this hour: http://www.ustream.tv/channel/at-home-with-jono-bacon | 19:02 |
jcastro | seiflotfy: sure! | 19:02 |
seiflotfy | jcastro, do i have the mic | 19:02 |
seiflotfy | ? | 19:02 |
jcastro | it's all yours | 19:02 |
seiflotfy | ok | 19:03 |
seiflotfy | so guys i will be doing a real tutorial tomorrow | 19:03 |
seiflotfy | today i will just fill the gap by explaining zeitgeist | 19:03 |
seiflotfy | and answering questions | 19:03 |
seiflotfy | so let me start | 19:03 |
seiflotfy | My name is Seif Lotfy | 19:03 |
seiflotfy | I am a GNOME member, and mostly known for being the initiator of the zeitgeist project | 19:04 |
seiflotfy | I am one of the lead developers and maintainers | 19:04 |
seiflotfy | we are 4 maintainers | 19:04 |
seiflotfy | Markus Korn (thekorn) is responsible for the QA | 19:05 |
seiflotfy | Mikkel Kamstrup (kamstrup) is the guy behind the architecture | 19:05 |
seiflotfy | and Siegfried Gevatter (RainCT) mostly DB work | 19:05 |
seiflotfy | however we all work on all aspects of zeitgeist | 19:06 |
seiflotfy | its just that these are the areas in which each developer proved himself very capable | 19:06 |
seiflotfy | so what is zeitgeist? | 19:06 |
seiflotfy | does any1 care to explain? | 19:06 |
seiflotfy | Zeitgeist is a service which logs the users's activities and events, anywhere from files opened to websites visited and conversations. | 19:07 |
seiflotfy | It makes this information readily available for other applications to use. | 19:07 |
seiflotfy | It is able to establish relationships between items based on similarity and usage patterns. | 19:07 |
seiflotfy | so its ur personal spyware :P | 19:08 |
seiflotfy | it is now part of unit | 19:08 |
seiflotfy | *unity | 19:08 |
seiflotfy | you can think of it as the gtk.RecentlyUsed | 19:09 |
=== ChanServ changed the topic of #ubuntu-classroom to: Welcome to the Ubuntu Classroom - https://wiki.ubuntu.com/Classroom || Support in #ubuntu || Upcoming Schedule: http://is.gd/8rtIi || Questions in #ubuntu-classroom-chat || Event: Ubuntu App Developer Week - Current Session: Zeitgeist Q &\; A - Instructors: seiflotfy | ||
seiflotfy | but on steroids | 19:09 |
seiflotfy | so who has a question ? | 19:09 |
=== ChanServ changed the topic of #ubuntu-classroom to: Welcome to the Ubuntu Classroom - https://wiki.ubuntu.com/Classroom || Support in #ubuntu || Upcoming Schedule: http://is.gd/8rtIi || Questions in #ubuntu-classroom-chat || Event: Ubuntu App Developer Week - Current Session: Zeitgeist Q and A - Instructors: seiflotfy | ||
seiflotfy | !q | 19:10 |
seiflotfy | ClassBot !q | 19:10 |
seiflotfy | how do i work the classbot | 19:10 |
seiflotfy | ? | 19:10 |
seiflotfy | ok got it | 19:11 |
seiflotfy | hmmm this class seems empty | 19:12 |
seiflotfy | sorry guys but the real talk is tomorrow | 19:12 |
seiflotfy | i will show you how to extend your applications to make use of zeitgeist | 19:13 |
ClassBot | titeuf_87 asked: How is privacy/security handled: will all apps be allowed to access everything by default? Or do you first have to allow them to? | 19:15 |
seiflotfy | titeuf_87, its a globael history | 19:15 |
seiflotfy | titeuf_87, zeitgeist allows applications to push data into it | 19:16 |
seiflotfy | once its in zeitgeist its available for everyone | 19:16 |
seiflotfy | so zeitgeist doesnt really look at what the applications are doing | 19:16 |
seiflotfy | but rather lets applications store their history into zeitgeist using a specific template | 19:17 |
seiflotfy | we also support blacklisting | 19:17 |
seiflotfy | so applications can tell zeitgeist "never log uri's with porn" | 19:17 |
seiflotfy | as well as registrations such as "Only totem is allowed to push events for media files" | 19:18 |
ClassBot | meebey asked: why did we have to wait for this amazing project to show up after all those years and what brought this project to life? | 19:19 |
seiflotfy | meebey, first thanks for the compliments | 19:19 |
seiflotfy | well there are many aspects to how it started | 19:20 |
seiflotfy | i used to work for an institute in germany that wrote similar software | 19:20 |
seiflotfy | and i also worked on Gimmie which sorted things based on time using gtk.RecentlyUsed | 19:20 |
seiflotfy | then Federico Mena came up with an Idea to write a Journal for GNOME | 19:20 |
seiflotfy | so i took the initiative to develop it | 19:21 |
seiflotfy | basically in my bedroom | 19:21 |
seiflotfy | then during the course of time aantn and RainCT started hacking | 19:22 |
seiflotfy | shortly afterwards thekorn and kamstrup started hacking on it too | 19:22 |
seiflotfy | and its from there where I personally consider zeitgeist being born (everything before that was just a working prototype) | 19:22 |
=== asd is now known as Guest61364 | ||
ClassBot | sinisterstuf asked: Is it obvious if a program is able to use zeitgeist or not without reading the source? | 19:23 |
seiflotfy | i dont get the question sinisterstuf | 19:23 |
ClassBot | zinga49 asked: do i get this right: a popular app (e.g. a game) could spy sensible data like browser history of thousands of users though? | 19:24 |
seiflotfy | zinga49, uhm yes but its possible over the firefox history too :) | 19:24 |
seiflotfy | zinga49, we actually have a an old sample code where we hooked into the firefox DB and sent everything to zeitgeist | 19:25 |
ClassBot | sinisterstuf asked: Well maybe I don't quite get it, what I mean is, if any program can read zeitgeist's journal of activities and stuff and do things with it, is there a way for the average user to know if a program is able to do that or not? | 19:27 |
seiflotfy | sinisterstuf, well the idea is to have zeitgeist integrated into the apps without popping out (ZEITGEIST IS LOGGING) | 19:27 |
seiflotfy | sinisterstuf, what we are adding is a zeitgeist-manager UI where u can see which applications are pushing information into Zeitgeist etc... | 19:28 |
seiflotfy | sinisterstuf, you can use GNOME Activity Journal to figure out what is being logged | 19:28 |
ClassBot | meebey asked: does zeitgeist have some kind of protocol / log that could be checked in order to find out which application has added or fetched information from/to zeitgeist? | 19:29 |
seiflotfy | meebey, well the log tells you who added info to zeitgeist (its usually the actor in our DB tables) | 19:29 |
ClassBot | sinisterstuf asked: I see. Do you aim to have zeitgeist integrated into EVERYTHING? | 19:30 |
seiflotfy | sinisterstuf, well its opensource | 19:30 |
seiflotfy | so 1) its transparent | 19:30 |
seiflotfy | 2) yes we would like to but its always an option to disallow applications from pushing info into zeitgeist | 19:30 |
seiflotfy | RainCT, can u write up a quick template that is sent to our blacklist manager to stop logging firefox history | 19:31 |
seiflotfy | this way we can show it to sinisterstuf | 19:31 |
ClassBot | meebey asked: well, say I want to debug or audit my application with zeitgeist, is there some log file that could be consulted to find out whats going on, besides the general information zeitgeist provides by it's database? | 19:32 |
seiflotfy | meebey, the more info you push into zeitgeist from you application the more u can use it for auditing | 19:32 |
seiflotfy | but i wouldnt recommend doing so | 19:33 |
ClassBot | zinga49 asked: will the blacklisting be possible for the user from a central place? so that the user has really control over every program. not just only if the program itself decides to let the user choose... | 19:33 |
seiflotfy | zinga49, yes its possible | 19:33 |
seiflotfy | we already support this over the engine | 19:33 |
seiflotfy | we just need a UI for it | 19:33 |
seiflotfy | but AFAIK m4n1sh (one of our new hackers) is working on the new blacklist stuff | 19:34 |
seiflotfy | and shortly after we will have a central UI to stop applications from logging | 19:34 |
seiflotfy | as well as stop zeitgeist completely from logging anything | 19:35 |
ClassBot | sinisterstuf asked: is there a zeitgeist 'journal' for each user or is it all lumped together in a common place? | 19:35 |
seiflotfy | sinisterstuf, per user | 19:35 |
seiflotfy | :) | 19:35 |
seiflotfy | if u want a log of several users you will need something called teamgeist (but this is a different story) | 19:36 |
seiflotfy | sinisterstuf, an example on how to make zeitgeist stop logging firefox is written here | 19:37 |
seiflotfy | http://paste.ubuntu.com/502775/ | 19:37 |
seiflotfy | any other questions? | 19:40 |
ClassBot | sinisterstuf asked: could admin use this to check what people use their computers for the most to see what type of people use what apps and how much and to do what with? that way he would know what to focus on, which things are not as important what other apps he might want to install⦠eg. for an internet cafe, library, or any company. (is that ethical, if the users don't knowâ½) | 19:41 |
seiflotfy | sinisterstuf, its not ethical but on the other hand it does make people's life much easier | 19:43 |
seiflotfy | sinisterstuf, yes admins could do that | 19:43 |
seiflotfy | but hey | 19:43 |
seiflotfy | admins at your company can also read your mails | 19:43 |
seiflotfy | a quote from spiderman: | 19:44 |
seiflotfy | Uncle Ben: Remember, with great power. comes great responsibility | 19:44 |
seiflotfy | so sinisterstuf you better hope your admin is a good guy | 19:44 |
ClassBot | nzmm asked: I have heard of teamgeist where does this fit in? | 19:45 |
seiflotfy | nzmm, teamgeist was an idea we had before guadec 2009 | 19:45 |
seiflotfy | the guys from collabora helped us out make it possible | 19:45 |
seiflotfy | what it does is really simple | 19:45 |
seiflotfy | its a service that uses zeitgeist | 19:46 |
seiflotfy | and telepathy | 19:46 |
seiflotfy | and ALLOWS you to decide what "events" you want to share with whom | 19:46 |
seiflotfy | so lets say me and thekorn want to plan a trip | 19:46 |
seiflotfy | we decide to share "all events from firefox with the words BRAZIL or SPAIN" | 19:46 |
seiflotfy | and then anything i do on firefox with BRAZIL or SPAIN is shared wit him | 19:47 |
seiflotfy | so its a log for a whole team for a specific topic | 19:47 |
seiflotfy | it can be very useful for developer | 19:47 |
seiflotfy | knowing who is working on which file in realtime | 19:47 |
ClassBot | sinisterstuf asked: do you know anything about this reward system I heard about, I think it'd use zeitgeist. Based of rewards in games it let's your computer award you for your actinos, eg. you used GIMP 50 times you won the artist award, your screensaver has been on more than 50% of the time you've achieved the laziness award? | 19:51 |
seiflotfy | sinisterstuf, yes its called OMG | 19:51 |
seiflotfy | it was something initiated but now its being developed and maintained by m4n1sh | 19:52 |
seiflotfy | it will be using zeitgeist intensivly | 19:52 |
seiflotfy | it actually is done | 19:52 |
seiflotfy | but needs a UI to show the toprhies | 19:52 |
seiflotfy | i just never got around to do that | 19:52 |
ClassBot | sinisterstuf asked: is it available to be used/tested? | 19:54 |
seiflotfy | sure | 19:55 |
seiflotfy | lp:omg :) | 19:55 |
=== ChanServ changed the topic of #ubuntu-classroom to: Welcome to the Ubuntu Classroom - https://wiki.ubuntu.com/Classroom || Support in #ubuntu || Upcoming Schedule: http://is.gd/8rtIi || Questions in #ubuntu-classroom-chat || Event: Ubuntu App Developer Week - Current Session: Custom widgets - Instructors: nzmm | ||
nzmm | hey folks! | 20:01 |
nzmm | my name is Matthew McGowan, I help out a bit with the Software Center UI | 20:01 |
nzmm | I am going to give a basic introduction on custom widgets in pygtk | 20:02 |
nzmm | I have notes up here: | 20:03 |
nzmm | http://nzmm.blogspot.com/2010/09/custom-widgets-notes-plain-text-version.html | 20:03 |
nzmm | if you would like to refer to them at a later date | 20:03 |
nzmm | so firstly why would we want to write our own widget? | 20:04 |
nzmm | with custom widgets you can potentially solve UI problems more elegantly or in more intersting ways | 20:05 |
nzmm | it adds flare to your application, a bit of bling perhaps | 20:06 |
nzmm | writing custom widgets is also a great way to get to know the gtk toolkit | 20:06 |
nzmm | but you need to be aware that a widget is a very user facing thing | 20:08 |
nzmm | so you need to ensure they are well tested and implement all the behaviours a user will expect from a widget, ie. keyboard shortcuts | 20:09 |
nzmm | Also, when it comes to colourising a custom widget you need to take care that you select appropriate colours that fit in with the widgets surroundings | 20:10 |
nzmm | and a big one that is easily overlooked is provided accessibility | 20:10 |
nzmm | *providing accessibility | 20:10 |
nzmm | for users who rely on apps such as the Orca screen-reader | 20:11 |
nzmm | so i hope with my chat, i will cover some of these basics | 20:11 |
nzmm | before you get started with a custom widget, get familiar with the gtk.Widget class | 20:13 |
nzmm | it provides a lot of methods, so it pays to know whats on offer, to avoid duplicating stuff in your own widget | 20:14 |
nzmm | also investigate other widgets, as their api may provide an insight into things you may need to consider writing your own widget | 20:14 |
nzmm | also you'll want to decide which widget you want to base your widget on | 20:16 |
nzmm | do you want to start with a gtk.EventBox or a gtk.DrawingArea or some other widget | 20:16 |
nzmm | !q | 20:16 |
ClassBot | sinisterstuf asked: what do I need in order to do this? | 20:17 |
nzmm | so i forgot to mention: to write a custom widget in pygtk, you dont need to install anything really, as the default Ubuntu install provides what you need out of the box | 20:19 |
nzmm | if you have bzr installed i have some sample code that shows the basics | 20:20 |
nzmm | bzr branch lp:~mmcg069/+junk/custom-widget-examples | 20:21 |
=== harrisonk_away is now known as harrisonk | ||
nzmm | or if bzr is a bit daunting, i have an example here: http://dl.dropbox.com/u/123544/custom-widget-1.py | 20:21 |
nzmm | so in this example a simple push-button type widget is written | 20:22 |
nzmm | the important parts are that we import gtk and cairo | 20:23 |
nzmm | gtk provides the 'windows' | 20:23 |
nzmm | cairo provides a the drawing operations | 20:23 |
nzmm | the method rounded_rect at the top of the file, is a helper method for drawing rounded rectangle | 20:24 |
nzmm | the guts of this example though is the class MyCustomWidget | 20:24 |
nzmm | notice that i have subclassed a gtk.EventBox | 20:24 |
nzmm | IMHO subclassing a gtk.EventBox is a great foundation for a custom widget | 20:25 |
nzmm | as the name suggests an EventBox allows you to be notified of events such as | 20:25 |
nzmm | button-presses, key-presses, motion, enter & leave events, focus events and many more | 20:26 |
nzmm | oh btw to run this file, simply > python custom-widget-1.py | 20:26 |
nzmm | the other great thing about EventBoxes is that with the method set_visible_window(True|False) | 20:27 |
nzmm | you can hide the EventBox 'background' window | 20:27 |
nzmm | this can be great for a number of reasons | 20:28 |
nzmm | a hidden background window can allow for a pseudo compositing effect | 20:28 |
nzmm | if the container you are putting your custom widget is a gradient or if you want to paint a shape to your custom widget that is non-rectangular | 20:29 |
nzmm | you can do so without unsightly little colour mismatches between the container widget and your custom widget | 20:30 |
nzmm | moving on | 20:30 |
nzmm | self.set_flags(gtk.CAN_FOCUS) | 20:30 |
nzmm | is important because this tells gtk that your widget wants to be in the focus-chain | 20:30 |
nzmm | the focus-chain it the order widgets are cycled through when you press Tab or Shift+Tab | 20:31 |
nzmm | self.set_size_request(36, 64) | 20:31 |
nzmm | requests a minimum size for your widget | 20:31 |
nzmm | in your own widget you'd want to calculate this based on the contents of your widget | 20:32 |
nzmm | its important to note that your widget can still grow to fill space, so keep that in mind | 20:32 |
nzmm | self.set_events(gtk.gdk.KEY_PRESS_MASK| ... | 20:33 |
nzmm | you need to also specify which events you want your widget to be sensitive to | 20:33 |
ClassBot | gaberlunzie asked: how to peek at available methods in the Widget class? | 20:34 |
nzmm | a good way would be dir(Widget) | 20:34 |
nzmm | that would list methods etc | 20:34 |
ClassBot | sinisterstuf asked: Can you set a max size for the widget too? | 20:35 |
nzmm | tbh, you cant really enforce a max or min on a widget | 20:36 |
nzmm | the size allocated to a widget depends a lot on how other widgets surrounding it are behaving | 20:36 |
nzmm | so yo need to design your widget to handle different sizes as gracefully as possible | 20:37 |
nzmm | next, you want to connect to signals that are issues when events occur, such as expose | 20:38 |
nzmm | self.connect('expose-event', self._on_expose) | 20:38 |
nzmm | expose-event is an important one as it is issued when a redraw is required | 20:38 |
nzmm | your signal handler, in my case def _on_expose(self, widget, event): | 20:39 |
nzmm | is where you want to handle the drawing | 20:39 |
nzmm | drawing is where custom widgets get really fun | 20:40 |
nzmm | in my case i do my drawing with cairo | 20:40 |
nzmm | # first we need to create a Cairo Context | 20:40 |
nzmm | cr = widget.window.cairo_create() | 20:40 |
nzmm | cr.rectangle(event.area) | 20:40 |
nzmm | cr.clip() | 20:40 |
nzmm | the event.area is the area of the widget that needs a redraw | 20:40 |
nzmm | usually because the contents have been changed or resized | 20:41 |
nzmm | widget.allocation is the space your widget occupies in the window | 20:42 |
nzmm | its a gtk.gdk.Rectangle() | 20:42 |
nzmm | it offers an x, y coord | 20:42 |
nzmm | and also the width and height dimensions | 20:42 |
nzmm | be aware that event.area and widget.allocation can differ | 20:43 |
nzmm | in this example we used fixed colours to the drawing, this most likely does not play well with gtk-themes | 20:44 |
nzmm | http://dl.dropbox.com/u/123544/custom-widget-2.py | 20:45 |
nzmm | in the above example however we prefer to use colours from gtk.Style | 20:45 |
nzmm | gtk.Style offers a palatte of colours derived from the current gtk-theme | 20:46 |
nzmm | for instance widget.style.mid[gkt.STATE_NORMAL] | 20:46 |
=== asd is now known as Guest93109 | ||
nzmm | will return the mid gtk.gdk.Color for your theme | 20:46 |
nzmm | bg = self.style.mid[self.state] | 20:47 |
nzmm | notice the self.state | 20:47 |
nzmm | the mid color selection offers a range of colours for each 'state' | 20:48 |
nzmm | states include gtk.STATE_PRELIGHT (onhover) gtk.STATE_ACTIVE (clicked) gtk.STATE_INSENSITIVE (inactive widget) | 20:48 |
nzmm | so mid will return slightly different colours depending on the state specified | 20:49 |
nzmm | to take advantage of this fact in your custom widget, you need to set the state depending on how the user is interacting with your widget | 20:50 |
nzmm | def _on_enter(self, widget, event): | 20:50 |
nzmm | # set state to enter. an expose event is generated | 20:50 |
nzmm | self.set_state(gtk.STATE_PRELIGHT) | 20:50 |
nzmm | return | 20:50 |
nzmm | the above code is the signal handler for the 'enter' event, i.e. when the user mouses over your widget | 20:50 |
nzmm | notice the set_state method | 20:51 |
nzmm | i set the state to gtk.STATE_PRELIGHT | 20:51 |
nzmm | this automatically triggers an expose event as well | 20:51 |
nzmm | resulting in a slightly different shade of colour when the user mouses over the widget | 20:52 |
nzmm | i am getting low on time so i want to move on to accessibility | 20:52 |
nzmm | http://dl.dropbox.com/u/123544/custom-widget-3.py | 20:52 |
nzmm | i changed the widget only slightly to provide some really basic accessibility information | 20:53 |
ClassBot | gaberlunzie asked: gtk.Style exempts cairo from being imported? | 20:53 |
nzmm | you can use both cairo drawing and gtk.STyle.paint_* methods in teh expose handler, for instance you can draw the rounded rectangle with cairo, the use gtk.Style.paint_layout to draw some text with the correct hinting etc | 20:54 |
nzmm | back to accessibility | 20:54 |
nzmm | accessibility allows you to provide additional contextual information about what your widget is for and how it behaves | 20:55 |
nzmm | acc = self.get_accessible() | 20:56 |
nzmm | # define an accessible name | 20:56 |
nzmm | acc.set_name('Big accessible button') | 20:56 |
nzmm | # define an accessible description | 20:56 |
nzmm | acc.set_description('An example custom widget in gtk') | 20:56 |
nzmm | # define the widgets role in terms of Atk Role Constants | 20:56 |
nzmm | acc.set_role(atk.ROLE_PUSH_BUTTON) | 20:56 |
nzmm | these are definately the basic methods you want to include in your widget | 20:56 |
nzmm | its should be obvious what they provide in terms of accessibility info | 20:56 |
nzmm | this info is then consumed by apps such as Orca to aid some disabled users of your widget | 20:58 |
nzmm | you can test your app for accessibility using the application Accerciser, which is available fromt he repositories | 20:58 |
nzmm | looks like i am out of time | 20:59 |
nzmm | i hope that was helpful starter | 20:59 |
=== ChanServ changed the topic of #ubuntu-classroom to: Welcome to the Ubuntu Classroom - https://wiki.ubuntu.com/Classroom || Support in #ubuntu || Upcoming Schedule: http://is.gd/8rtIi || Questions in #ubuntu-classroom-chat || | ||
=== yofel_ is now known as yofel | ||
=== tj is now known as Guest83474 |
Generated by irclog2html.py 2.7 by Marius Gedminas - find it at mg.pov.lt!