Advertisement
lpugoy

Insync extension for Nautilus

Jul 3rd, 2013
156
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.41 KB | None | 0 0
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. # Insync Nautilus Plugin
  4. #
  5. # Authors
  6. # Brett Hartshorn <brett@insynchq.com>
  7. # Luis Manuel R. Pugoy <lpugoy@insynchq.com>
  8. #
  9. # Copyright Insynchq Pte. Ltd., ("Insync") | http://www.insynchq.com
  10. # License: GNU GPL v2
  11.  
  12. DEV_MODE = False
  13.  
  14. import json
  15. import os
  16. import socket
  17. import sys
  18. import urllib
  19. from gi.repository import GObject
  20.  
  21. GTK3 = False
  22. try:
  23.   from gi.repository import Nautilus
  24.   GTK3 = True   # this might not be true
  25. except:
  26.   print('WARN - using Nautilus2 wrapper')
  27.   import nautilus as Nautilus   # Nautilus2
  28.  
  29. if GTK3:
  30.   # http://python-gtk-3-tutorial.readthedocs.org/en/latest/drag_and_drop.html
  31.   from gi.repository import Gtk
  32.   from gi.repository import Gdk
  33.   (TARGET_ENTRY_TEXT, TARGET_ENTRY_PIXBUF) = range(2)
  34.   (COLUMN_TEXT, COLUMN_PIXBUF) = range(2)
  35.   DRAG_ACTION = Gdk.DragAction.COPY
  36.  
  37. print(Nautilus)
  38.  
  39. if __name__ == '__main__':  ## this is only for testing ##
  40.   if '--install-local' in sys.argv:
  41.     ## Nautilus2 is no longer supported ##
  42.     #if os.path.isdir( os.path.expanduser('~/.nautilus') ):
  43.     #  path = os.path.expanduser('~/.nautilus/python-extensions')
  44.     #  if not os.path.isdir(path): os.makedirs( path )
  45.     #  os.system('cp -v %s ~/.nautilus/python-extensions/.' %__file__)
  46.  
  47.     ## Nautilus3 ##
  48.     path = os.path.expanduser('~/.local/share/nautilus-python/extensions')
  49.     if not os.path.isdir(path): os.makedirs( path )
  50.     os.system('cp -v %s ~/.local/share/nautilus-python/extensions/.' %__file__)
  51.     os.system('cp -Rv ../core/linux/libgio/ ~/.local/share/nautilus-python/extensions/.')
  52.  
  53.   else:
  54.     os.system('cp -v %s /usr/share/nautilus-python/extensions/.' %__file__)
  55.     os.system('cp -Rv ../core/linux/libgio/ /usr/share/nautilus-python/extensions/.')
  56.  
  57.   os.system('nautilus -q')
  58.   sys.exit()
  59.  
  60.  
  61. print('Insync Nautilus Plugin')
  62.  
  63. FS_ENCODING = sys.getfilesystemencoding()   # C is 'ANSI_X3.4-1968'
  64.  
  65.  
  66. def ipc_insync(**kw):
  67.   data = json.dumps(kw)
  68.   socket_file = u'/tmp/insync%r.sock' % os.getuid()
  69.   if not os.path.exists(socket_file):
  70.     if DEV_MODE:
  71.       print('WARN: insync socket not found')
  72.     return None
  73.   sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  74.   try:
  75.     sock.connect(socket_file)
  76.   except:
  77.     return None
  78.   sock.send(data)
  79.   res = sock.recv(4096)
  80.   sock.close()
  81.   if res.strip():
  82.     return json.loads(res.strip())
  83.   else:
  84.     return None
  85.  
  86.  
  87. ## Keep in sync with client/insyncd/clientmenuitems.py :: context_menu_items_for
  88. GDRIVE = ('Open in Google Drive', 'cm:open', 'web-browser-symbolic')
  89. SEPARATOR = (unicode(u'\u2015' * 10), None, None)
  90. SHARE =  ('Share', 'cm:share', 'send-to-symbolic')
  91. LINK =   ('Copy public Link', 'cm:public_link', 'document-save-symbolic')
  92.  
  93.  
  94. _EMBLEM_KEYS = {
  95.   'SYNCED': 'emblem-insync-synced',
  96.   'SYNCING': 'emblem-insync-syncing',
  97.   'ERROR': 'emblem-insync-error',
  98.   'DES_ERROR': 'emblem-insync-des-error',
  99.   'DES_SYNCING': 'emblem-insync-syncing',
  100. }
  101.  
  102.  
  103. class InsyncExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.InfoProvider):
  104.   '''
  105.  see: /usr/share/doc/python-nautilus/examples/
  106.  
  107.  1. nautilus.ColumnProvider - It exposes a single function get_columns . This function has to return a sequence of nautilus.Column objects.
  108.  2. nautilus.InfoProvider - Exposes a single function update_file_info with a file as an argument.
  109.  3. nautilus.LocationWidgetProvider - Exposes a single function get_widget, return some gtk widget near the location bar.
  110.  4. nautilus.MenuProvider - Probably the most used interface. It exposes three functions :
  111.     get_file_items, get_background_items and get_toolbar_items . The first two functions determine the entries that appear in the context menu. The difference is that get_background_items is usually called for a folder.
  112.     The last function is used for toolbar items hence you must name the icon parameter of the menuItem.
  113.  5. nautilus.PropertyPageProvider - Exposes get_property_pages function where you reture one or more "pages" or tabs.
  114.  
  115.  '''
  116.  
  117.   DEFAULT_INSYNC_CONFIG_PATH = os.path.expanduser('~/.config/Insync')
  118.   _hack_ = False  # Nautilus 3.4 has fixed the issue
  119.  
  120.  
  121.   def load_insync_settings(self):
  122.     msg = ipc_insync(command='GET-INFO')
  123.     if msg:
  124.       if DEV_MODE:
  125.         print('INSYNC ACTIVE')
  126.         print(msg)
  127.       self._insync_active = True
  128.       self._insync_roots = msg
  129.     else:
  130.       self._insync_active = False
  131.     symcache = os.path.join(self.DEFAULT_INSYNC_CONFIG_PATH, 'symlink-cache.json')
  132.     if os.path.isfile(symcache):
  133.       f = open(symcache, 'rb')
  134.       self._symlinks = json.load(f)
  135.       f.close()
  136.  
  137.  
  138.   def __init__(self):
  139.     print('[__init__ insync plugin]', self)
  140.     self._insync_active = False
  141.     self._insync_roots = None
  142.     self._symlinks = {}  # link : target
  143.  
  144.     if InsyncExtension._hack_:
  145.       print('[WARN old nautilus bug]')
  146.       self.get_file_items = lambda win, files: None
  147.       self.get_widget = lambda uri, win: None
  148.       InsyncExtension._hack_ = False
  149.     else:
  150.       print('::new insync extension::')
  151.       #InsyncExtension._hack_ = self
  152.       InsyncExtension._hack_ = False
  153.       #self.get_property_pages = lambda files: None
  154.       self.get_widget = lambda uri, win: None
  155.  
  156.  
  157.   def get_background_items(self, window, folder):
  158.     '''
  159.    This method needs to be defined to avoid this warning:
  160.    ** (nautilus:2913): CRITICAL **: nautilus_menu_provider_get_background_items: assertion
  161.    `NAUTILUS_IS_MENU_PROVIDER (provider)' failed
  162.    '''
  163.     #print('background items', window, folder)
  164.     return None
  165.  
  166.  
  167.   def get_file_items(self, window, files):
  168.     if DEV_MODE:
  169.       print('get file items', files)
  170.  
  171.     if len(files) != 1:
  172.       # TODO what can we do with mutiple files here
  173.       return None
  174.  
  175.     self.load_insync_settings()
  176.     if not self._insync_roots:
  177.       return None
  178.  
  179.     file = files[0]
  180.     path = urllib.unquote(file.get_uri()[len('file://'):])
  181.     path = unicode(path, FS_ENCODING)
  182.     if DEV_MODE:
  183.       print path
  184.     is_dir = os.path.isdir(path)
  185.  
  186.     roots = tuple([root + '/' for root in self._insync_roots])
  187.     if self._insync_active and path.startswith(roots):
  188.       tip = 'Insync folder actions' if is_dir else 'Insync file actions'
  189.       item = Nautilus.MenuItem(
  190.         name="Insync",
  191.         label="Insync",
  192.         tip=tip,
  193.         icon='insync'
  194.       )
  195.       sub_menu = Nautilus.Menu()
  196.       item.set_submenu(sub_menu)
  197.  
  198.       for text, cmd, icon in [GDRIVE, SEPARATOR, SHARE, LINK]:
  199.         menu_item = Nautilus.MenuItem(name=text, label=text, tip=text, icon=icon)
  200.         if cmd:
  201.           menu_item.connect('activate', self.do_action, file, cmd)
  202.         else:
  203.           menu_item.sensitive = False
  204.         sub_menu.append_item(menu_item)
  205.  
  206.       return [item]
  207.     elif not path.startswith(tuple(self._insync_roots)):
  208.       targets = self._symlinks.values()
  209.       if DEV_MODE:
  210.         print 'self._symlinks', self._symlinks
  211.  
  212.       symlink_target = unicode(
  213.         urllib.unquote(file.get_uri()[len('file://'):]),
  214.         FS_ENCODING
  215.       )
  216.  
  217.       toplevel = self.make_symlinks_menu(symlink_target)
  218.       return [toplevel]
  219.  
  220.  
  221.   def make_symlinks_menu(self, symlink_target):
  222.     uid = 0   # this is lame
  223.  
  224.     toplevel = Nautilus.MenuItem(
  225.       name="Insync::%s" % uid,
  226.       label="Add to Insync",
  227.       tip="Add to Insync - creates a symlink to this folder, in the user folder you select",
  228.       icon='insync'
  229.     )
  230.     uid += 1
  231.  
  232.     submenu = Nautilus.Menu()
  233.     toplevel.set_submenu(submenu)
  234.  
  235.     reverse_symlinks = dict((value, key) for key, value in self._symlinks.iteritems())
  236.     if symlink_target in reverse_symlinks:
  237.       full_path, symlink_name = os.path.split(reverse_symlinks[symlink_target])
  238.       _, folder_name = os.path.split(full_path)
  239.       label = u'%s/%s (linked)' % (folder_name, symlink_name)
  240.       item = Nautilus.MenuItem(
  241.         name='Insync::%s' % uid,
  242.         label=label,
  243.         tip=label
  244.       )
  245.       submenu.append_item(item)
  246.       uid += 1
  247.     else:
  248.       _, target_name = os.path.split(symlink_target)
  249.  
  250.       for insync_root in self._insync_roots:
  251.         _, folder_name = os.path.split(insync_root)
  252.         if target_name in os.listdir(insync_root):
  253.           label = u'%s/%s (name in use)' % (folder_name, target_name)
  254.           item = Nautilus.MenuItem(
  255.             name='Insync::%s' % uid,
  256.             label=label,
  257.             tip=label
  258.           )
  259.         else:
  260.           item = Nautilus.MenuItem(
  261.             name='Insync::%s' % uid,
  262.             label=folder_name,
  263.             tip='create symlink in user root folder'
  264.           )
  265.           item.connect('activate', self.do_symlink, symlink_target, target_name, insync_root)
  266.  
  267.         submenu.append_item(item)
  268.         uid += 1
  269.  
  270.     return toplevel
  271.  
  272.  
  273.   def do_symlink(self, menu, path, target, insync_root):
  274.     dest = os.path.join(insync_root, target)
  275.     if DEV_MODE:
  276.       print('<doing symlink>', path, dest)
  277.     self._symlinks[path] = dest
  278.     os.symlink(path, dest)
  279.     if not ipc_insync(command='IS-ALIVE') and os.path.isdir(self.DEFAULT_INSYNC_CONFIG_PATH):
  280.       symcache = os.path.join(self.DEFAULT_INSYNC_CONFIG_PATH, 'symlink-cache.json')
  281.       with open(symcache, 'wb') as f:
  282.         json.dump(self._symlinks, f)
  283.  
  284.  
  285.   def do_action(self, menu, file, method):
  286.     path = urllib.unquote(
  287.       file.get_uri()[len('file://'):]
  288.     )
  289.     ipc_insync(method=method, full_path=path)
  290.  
  291.  
  292.   def update_file_info_full(self, provider, handle, closure, file):
  293.     if file.get_uri_scheme() != 'file':
  294.       return
  295.  
  296.     filename = urllib.unquote(file.get_uri()[len('file://'):])
  297.     status = ipc_insync(command='GET-FILE-STATUS', full_path=filename)
  298.     if DEV_MODE:
  299.       print filename, status
  300.     if not status or status == 'UNKNOWN':
  301.       return Nautilus.OperationResult.COMPLETE
  302.  
  303.     GObject.timeout_add(1, self.add_emblem, provider, handle, closure, file,
  304.                         filename, status)
  305.     return Nautilus.OperationResult.IN_PROGRESS
  306.  
  307.  
  308.   def add_emblem(self, provider, handle, closure, file, filename, status):
  309.     emblem = _EMBLEM_KEYS[status]
  310.     if os.path.isdir(filename):
  311.       is_shared = ipc_insync(command='IS-FILE-SHARED', full_path=filename)
  312.       if is_shared:
  313.         emblem += '-shared'
  314.  
  315.     file.add_emblem(emblem)
  316.  
  317.     Nautilus.info_provider_update_complete_invoke(closure, provider, handle,
  318.                                                   Nautilus.OperationResult.COMPLETE)
  319.     return False
  320.  
  321.  
  322. print(InsyncExtension)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement