Merge lp:~nataliabidart/ubuntuone-client/xdg-return-bytes into lp:ubuntuone-client

Proposed by Natalia Bidart
Status: Merged
Approved by: Natalia Bidart
Approved revision: 1082
Merged at revision: 1080
Proposed branch: lp:~nataliabidart/ubuntuone-client/xdg-return-bytes
Merge into: lp:ubuntuone-client
Diff against target: 446 lines (+127/-88)
11 files modified
bin/ubuntuone-syncdaemon (+26/-13)
tests/platform/test_xdg_base_directory.py (+10/-0)
tests/platform/windows/test_xdg_base_directory.py (+5/-6)
tests/syncdaemon/test_config.py (+49/-33)
ubuntuone/platform/linux/__init__.py (+1/-1)
ubuntuone/platform/linux/os_helper.py (+1/-6)
ubuntuone/platform/windows/__init__.py (+0/-1)
ubuntuone/platform/windows/os_helper.py (+0/-6)
ubuntuone/platform/xdg_base_directory/windows.py (+4/-3)
ubuntuone/syncdaemon/config.py (+28/-16)
ubuntuone/syncdaemon/main.py (+3/-3)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-client/xdg-return-bytes
Reviewer Review Type Date Requested Status
Alejandro J. Cura (community) Approve
Guillermo Gonzalez Approve
Review via email: mp+69808@code.launchpad.net

Commit message

- XDG data dirs must return bytes (LP: #818030).

To post a comment you must log in.
Revision history for this message
Guillermo Gonzalez (verterok) wrote :

looks good.

review: Approve
1080. By Natalia Bidart

Typo!

Revision history for this message
Alejandro J. Cura (alecu) wrote :

Waiting for a few style changes, but basically "Approved!!!!!"

review: Approve
1081. By Natalia Bidart

Merged trunk in.

1082. By Natalia Bidart

Style fixes.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bin/ubuntuone-syncdaemon'
--- bin/ubuntuone-syncdaemon 2011-07-28 14:36:35 +0000
+++ bin/ubuntuone-syncdaemon 2011-07-29 19:11:49 +0000
@@ -37,7 +37,15 @@
37import sys37import sys
38import shutil38import shutil
3939
40from ubuntuone.platform import is_already_running, set_application_name, is_root40from ubuntuone.platform import (
41 can_write,
42 set_dir_readwrite,
43 is_already_running,
44 is_root,
45 make_dir,
46 path_exists,
47 set_application_name,
48)
41from ubuntuone.syncdaemon import logger, config49from ubuntuone.syncdaemon import logger, config
42from ubuntuone.syncdaemon.config import (50from ubuntuone.syncdaemon.config import (
43 get_config_files,51 get_config_files,
@@ -127,31 +135,36 @@
127135
128 # check if we are using xdg_data_home and it doesn't exists136 # check if we are using xdg_data_home and it doesn't exists
129 if xdg_data_home in options.data_dir and \137 if xdg_data_home in options.data_dir and \
130 not os.path.exists(options.data_dir):138 not path_exists(options.data_dir):
131 # if we have metadata in the old xdg_cache, move it!139 # if we have metadata in the old xdg_cache, move it!
132 old_data_dir = options.data_dir.replace(xdg_data_home, xdg_cache_home)140 old_data_dir = options.data_dir.replace(xdg_data_home, xdg_cache_home)
133 if os.path.exists(old_data_dir):141 if path_exists(old_data_dir):
134 parent = os.path.dirname(options.data_dir)142 parent = os.path.dirname(options.data_dir)
135 if os.path.exists(parent) and not os.access(parent, os.W_OK):143 if path_exists(parent) and not can_write(parent):
136 # make the parent dir writable144 # make the parent dir writable
137 os.chmod(parent, 0775)145 set_dir_readwrite(parent)
138 elif not os.path.exists(parent):146 elif not path_exists(parent):
139 # if it don't exits147 # if it don't exits
140 os.makedirs(parent)148 make_dir(parent, recursive=True)
141 shutil.move(old_data_dir, options.data_dir)149 shutil.move(old_data_dir, options.data_dir)
142 if not os.path.exists(options.data_dir):150 if not path_exists(options.data_dir):
143 parent = os.path.dirname(options.data_dir)151 parent = os.path.dirname(options.data_dir)
144 if os.path.exists(parent) and not os.access(parent, os.W_OK):152 if path_exists(parent) and not can_write(parent):
145 # make the parent dir writable153 # make the parent dir writable
146 os.chmod(parent, 0775)154 set_dir_readwrite(parent)
147 os.makedirs(options.data_dir)155 make_dir(options.data_dir, recursive=True)
148156
149 # create the partials_dir157 # create the partials_dir
150 partials_dir = os.path.join(xdg_cache_home, 'ubuntuone', 'partials')158 partials_dir = os.path.join(xdg_cache_home, 'ubuntuone', 'partials')
151 if not os.path.exists(partials_dir):159 if not path_exists(partials_dir):
152 os.makedirs(partials_dir)160 make_dir(partials_dir, recursive=True)
153161
154 logger.rotate_logs()162 logger.rotate_logs()
163
164 assert isinstance(options.root_dir, str)
165 assert isinstance(options.shares_dir, str)
166 assert isinstance(options.data_dir, str)
167
155 main = Main(options.root_dir, options.shares_dir, options.data_dir,168 main = Main(options.root_dir, options.shares_dir, options.data_dir,
156 partials_dir, host=options.host, port=int(options.port),169 partials_dir, host=options.host, port=int(options.port),
157 dns_srv=options.dns_srv, ssl=True,170 dns_srv=options.dns_srv, ssl=True,
158171
=== modified file 'tests/platform/test_xdg_base_directory.py'
--- tests/platform/test_xdg_base_directory.py 2011-07-26 16:07:08 +0000
+++ tests/platform/test_xdg_base_directory.py 2011-07-29 19:11:49 +0000
@@ -34,3 +34,13 @@
34 'ubuntuone', 'log')34 'ubuntuone', 'log')
35 self.assertEqual(expected, xdg_base_directory.ubuntuone_log_dir)35 self.assertEqual(expected, xdg_base_directory.ubuntuone_log_dir)
36 self.assertTrue(os.path.exists(expected))36 self.assertTrue(os.path.exists(expected))
37
38 def test_xdg_cache_home_is_bytes(self):
39 """The returned path is bytes."""
40 actual = xdg_base_directory.xdg_cache_home
41 self.assertIsInstance(actual, str)
42
43 def test_xdg_data_home_is_bytes(self):
44 """The returned path is bytes."""
45 actual = xdg_base_directory.xdg_data_home
46 self.assertIsInstance(actual, str)
3747
=== modified file 'tests/platform/windows/test_xdg_base_directory.py'
--- tests/platform/windows/test_xdg_base_directory.py 2011-07-28 12:51:07 +0000
+++ tests/platform/windows/test_xdg_base_directory.py 2011-07-29 19:11:49 +0000
@@ -17,12 +17,11 @@
17# with this program. If not, see <http://www.gnu.org/licenses/>.17# with this program. If not, see <http://www.gnu.org/licenses/>.
18"""Platform independent tests for the credentials management."""18"""Platform independent tests for the credentials management."""
1919
20import os
21
22import win32com.shell20import win32com.shell
2321
24from twisted.trial.unittest import TestCase22from twisted.trial.unittest import TestCase
2523
24from ubuntuone.platform.windows.os_helper import assert_syncdaemon_path
26from ubuntuone.platform.xdg_base_directory.windows import get_special_folders25from ubuntuone.platform.xdg_base_directory.windows import get_special_folders
2726
2827
@@ -38,8 +37,8 @@
38 def __init__(self):37 def __init__(self):
39 """Set the proper mapping between CSIDL_ consts."""38 """Set the proper mapping between CSIDL_ consts."""
40 self.values = {39 self.values = {
41 0: r'c:\path\to\users\home',40 0: u'c:\\path\\to\\users\\home',
42 1: r'c:\path\to\users\home\appData\local'}41 1: u'c:\\path\\to\\users\\home\\appData\\local'}
4342
44 def SHGetFolderPath(self, dummy0, shellconValue, dummy2, dummy3):43 def SHGetFolderPath(self, dummy0, shellconValue, dummy2, dummy3):
45 """Override SHGetFolderPath functionality."""44 """Override SHGetFolderPath functionality."""
@@ -67,5 +66,5 @@
67 self.assertTrue(special_folders['Local AppData'].startswith(66 self.assertTrue(special_folders['Local AppData'].startswith(
68 special_folders['AppData']))67 special_folders['AppData']))
6968
70 for k in special_folders:69 for val in special_folders.itervalues():
71 os.access(special_folders[k], os.W_OK)70 assert_syncdaemon_path(val)
7271
=== modified file 'tests/syncdaemon/test_config.py'
--- tests/syncdaemon/test_config.py 2011-07-27 13:27:22 +0000
+++ tests/syncdaemon/test_config.py 2011-07-29 19:11:49 +0000
@@ -471,7 +471,7 @@
471471
472472
473class ConfigglueParsersTests(BaseTwistedTestCase):473class ConfigglueParsersTests(BaseTwistedTestCase):
474 """Tests for our custom configglue parsers"""474 """Tests for our custom configglue parsers."""
475475
476 def test_throttling_limit_parser(self):476 def test_throttling_limit_parser(self):
477 """Test throttling_limit_parser"""477 """Test throttling_limit_parser"""
@@ -497,39 +497,55 @@
497 self.assertEqual(logging.DEBUG, parser(bad_value))497 self.assertEqual(logging.DEBUG, parser(bad_value))
498 self.assertEqual(logging.DEBUG, parser(invalid_value))498 self.assertEqual(logging.DEBUG, parser(invalid_value))
499499
500 def test_home_dir_parser(self):500
501 """Test home_dir_parser"""501class XdgHomeParsersTests(BaseTwistedTestCase):
502 good_value = '~/hola'502 """Tests for our custom xdg parsers."""
503 bad_value = 'hola'503
504 invalid_value = None504 good_value = '~/hola/mundo'
505 parser = config.home_dir_parser505 name = 'home'
506 homedir = os.path.join('/', 'home', 'fake')506 xdg_dir = os.path.join('', 'home', 'fake')
507
508 @defer.inlineCallbacks
509 def setUp(self):
510 yield super(XdgHomeParsersTests, self).setUp()
511 self.parser = getattr(config, '%s_dir_parser' % self.name)
512
513 def test_good_value(self):
514 """Test the parser using a good value."""
515 homedir = os.path.join('', 'home', 'fake')
507 with environ('HOME', homedir):516 with environ('HOME', homedir):
508 self.assertEqual(os.path.join(homedir, 'hola'), parser(good_value))517 expected = os.path.join(self.xdg_dir, 'hola', 'mundo')
509 self.assertEqual('hola', parser(bad_value))518 actual = self.parser(self.good_value)
510 self.assertRaises(AttributeError, parser, invalid_value)519 self.assertEqual(expected, actual)
511520 self.assertIsInstance(actual, str)
512 def test_xdg_cache_dir_parser(self):521 self.assertNotIsInstance(actual, unicode)
513 """Test xdg_cache_dir_parser"""522
514 good_value = 'hola'523 def test_bad_value(self):
515 bad_value = '/hola'524 """Test the parser using a bad value."""
516 invalid_value = None525 bad_value = '/hola'
517 parser = config.xdg_cache_dir_parser526 self.assertEqual(config.path_from_unix(bad_value),
518 self.assertEquals(os.path.join(xdg_cache_home, 'hola'),527 self.parser(bad_value))
519 parser(good_value))528
520 self.assertEquals('/hola', parser(bad_value))529 def test_invalid_value(self):
521 self.assertRaises(AttributeError, parser, invalid_value)530 """Test the parser using an invalid value."""
522531 invalid_value = None
523 def test_xdg_data_dir_parser(self):532 self.assertRaises(AttributeError, self.parser, invalid_value)
524 """Test xdg_data_dir_parser"""533
525 good_value = 'hola'534
526 bad_value = '/hola'535class XdgCacheParsersTests(XdgHomeParsersTests):
527 invalid_value = None536 """Tests for our custom xdg parsers."""
528 parser = config.xdg_data_dir_parser537
529 self.assertEquals(os.path.join(xdg_data_home, 'hola'),538 good_value = 'hola/mundo'
530 parser(good_value))539 name = 'xdg_cache'
531 self.assertEquals('/hola', parser(bad_value))540 xdg_dir = xdg_cache_home
532 self.assertRaises(AttributeError, parser, invalid_value)541
542
543class XdgDataParsersTests(XdgCacheParsersTests):
544 """Tests for our custom xdg parsers."""
545
546 good_value = 'hola/mundo'
547 name = 'xdg_data'
548 xdg_dir = xdg_data_home
533549
534550
535class SyncDaemonConfigParserTests(BaseTwistedTestCase):551class SyncDaemonConfigParserTests(BaseTwistedTestCase):
536552
=== modified file 'ubuntuone/platform/linux/__init__.py'
--- ubuntuone/platform/linux/__init__.py 2011-07-28 17:32:56 +0000
+++ ubuntuone/platform/linux/__init__.py 2011-07-29 19:11:49 +0000
@@ -52,13 +52,13 @@
52 set_file_readwrite,52 set_file_readwrite,
53 set_no_rights,53 set_no_rights,
54 stat_path,54 stat_path,
55 validate_path_from_unix,
56)55)
57from ubuntuone.platform.linux.credentials import CredentialsManagementTool56from ubuntuone.platform.linux.credentials import CredentialsManagementTool
58from ubuntuone.platform.linux.logger import setup_filesystem_logging, get_filesystem_logger57from ubuntuone.platform.linux.logger import setup_filesystem_logging, get_filesystem_logger
59from ubuntuone.platform.linux.filesystem_notifications import FilesystemMonitor58from ubuntuone.platform.linux.filesystem_notifications import FilesystemMonitor
60from ubuntuone.platform.linux.notification import Notification59from ubuntuone.platform.linux.notification import Notification
6160
61
62class ExternalInterface(object):62class ExternalInterface(object):
63 """An ExternalInterface implemented with a DBus interface."""63 """An ExternalInterface implemented with a DBus interface."""
6464
6565
=== modified file 'ubuntuone/platform/linux/os_helper.py'
--- ubuntuone/platform/linux/os_helper.py 2011-07-19 21:59:27 +0000
+++ ubuntuone/platform/linux/os_helper.py 2011-07-29 19:11:49 +0000
@@ -154,12 +154,7 @@
154154
155def is_root():155def is_root():
156 """Return if the user is running as root."""156 """Return if the user is running as root."""
157 return not os.geteuid()157 return not os.geteuid()
158
159
160def validate_path_from_unix(path):
161 """Get a unix path and return it so that it cna be used."""
162 return path
163158
164159
165def get_path_list(path):160def get_path_list(path):
166161
=== modified file 'ubuntuone/platform/windows/__init__.py'
--- ubuntuone/platform/windows/__init__.py 2011-07-28 17:32:56 +0000
+++ ubuntuone/platform/windows/__init__.py 2011-07-29 19:11:49 +0000
@@ -51,7 +51,6 @@
51 set_file_readwrite,51 set_file_readwrite,
52 set_no_rights,52 set_no_rights,
53 stat_path,53 stat_path,
54 validate_path_from_unix,
55)54)
56from ubuntuone.platform.windows.credentials import CredentialsManagementTool55from ubuntuone.platform.windows.credentials import CredentialsManagementTool
57from ubuntuone.platform.windows import event_logging56from ubuntuone.platform.windows import event_logging
5857
=== modified file 'ubuntuone/platform/windows/os_helper.py'
--- ubuntuone/platform/windows/os_helper.py 2011-07-28 15:43:05 +0000
+++ ubuntuone/platform/windows/os_helper.py 2011-07-29 19:11:49 +0000
@@ -716,12 +716,6 @@
716 return False716 return False
717717
718718
719def validate_path_from_unix(path):
720 """Get a unix path and return it so that it can be used."""
721 path = path.replace('/', '\\')
722 return path
723
724
725@windowspath()719@windowspath()
726def get_path_list(path):720def get_path_list(path):
727 """Return a list with the diff components of the path."""721 """Return a list with the diff components of the path."""
728722
=== modified file 'ubuntuone/platform/xdg_base_directory/windows.py'
--- ubuntuone/platform/xdg_base_directory/windows.py 2011-07-27 21:09:30 +0000
+++ ubuntuone/platform/xdg_base_directory/windows.py 2011-07-29 19:11:49 +0000
@@ -31,11 +31,12 @@
31 # CSIDL_LOCAL_APPDATA = C:\Users\<username>\AppData\Local31 # CSIDL_LOCAL_APPDATA = C:\Users\<username>\AppData\Local
32 # CSIDL_PROFILE = C:\Users\<username>32 # CSIDL_PROFILE = C:\Users\<username>
33 path = shell.SHGetFolderPath(0, shellcon.CSIDL_PROFILE, None, 0)33 path = shell.SHGetFolderPath(0, shellcon.CSIDL_PROFILE, None, 0)
34 special_folders['Personal'] = path34 special_folders['Personal'] = path.encode('utf8')
3535
36 path = shell.SHGetFolderPath(0, shellcon.CSIDL_LOCAL_APPDATA, None, 0)36 path = shell.SHGetFolderPath(0, shellcon.CSIDL_LOCAL_APPDATA, None, 0)
37 special_folders['Local AppData'] = path37 special_folders['Local AppData'] = path.encode('utf8')
38 special_folders['AppData'] = os.path.dirname(path)38 path = os.path.dirname(path)
39 special_folders['AppData'] = path.encode('utf8')
3940
40 return special_folders41 return special_folders
4142
4243
=== modified file 'ubuntuone/syncdaemon/config.py'
--- ubuntuone/syncdaemon/config.py 2011-07-28 15:43:05 +0000
+++ ubuntuone/syncdaemon/config.py 2011-07-29 19:11:49 +0000
@@ -25,6 +25,7 @@
2525
26from ConfigParser import NoOptionError, NoSectionError26from ConfigParser import NoOptionError, NoSectionError
27from optparse import OptionParser27from optparse import OptionParser
28from ubuntuone.platform import path_exists, rename
28from ubuntuone.platform.xdg_base_directory import (29from ubuntuone.platform.xdg_base_directory import (
29 load_config_paths,30 load_config_paths,
30 save_config_path,31 save_config_path,
@@ -56,7 +57,6 @@
56 TypedConfigParser = new_tcp57 TypedConfigParser = new_tcp
57 del new_tcp58 del new_tcp
58# end of naming shenanigans59# end of naming shenanigans
59from ubuntuone.platform import validate_path_from_unix
6060
61CONFIG_FILE = 'syncdaemon.conf'61CONFIG_FILE = 'syncdaemon.conf'
62CONFIG_LOGS = 'logging.conf'62CONFIG_LOGS = 'logging.conf'
@@ -76,6 +76,8 @@
76# this object is the shared config76# this object is the shared config
77_user_config = None77_user_config = None
7878
79path_from_unix = lambda path: path.replace('/', os.path.sep)
80
7981
80def home_dir_parser(value):82def home_dir_parser(value):
81 """Parser for the root_dir and shares_dir options.83 """Parser for the root_dir and shares_dir options.
@@ -83,21 +85,31 @@
83 Return the path using user home + value.85 Return the path using user home + value.
8486
85 """87 """
86 return os.path.expanduser(validate_path_from_unix(value))88 result = os.path.expanduser(path_from_unix(value))
89 assert isinstance(result, str)
90 return result
8791
8892
89def xdg_cache_dir_parser(value):93def xdg_cache_dir_parser(value):
90 """ Parser for the data_dir option.94 """Parser for the data_dir option.
91 returns the path using xdg_cache_home + value.95
96 Return the path using xdg_cache_home + value.
97
92 """98 """
93 return os.path.join(xdg_cache_home, validate_path_from_unix(value))99 result = os.path.join(xdg_cache_home, path_from_unix(value))
100 assert isinstance(result, str)
101 return result
94102
95103
96def xdg_data_dir_parser(value):104def xdg_data_dir_parser(value):
97 """ Parser for the data_dir option.105 """Parser for the data_dir option.
98 returns the path using xdg_data_home + value.106
107 Return the path using xdg_data_home + value.
108
99 """109 """
100 return os.path.join(xdg_data_home, validate_path_from_unix(value))110 result = os.path.join(xdg_data_home, path_from_unix(value))
111 assert isinstance(result, str)
112 return result
101113
102114
103def log_level_parser(value):115def log_level_parser(value):
@@ -140,11 +152,11 @@
140 config_files = []152 config_files = []
141 for xdg_config_dir in load_config_paths('ubuntuone'):153 for xdg_config_dir in load_config_paths('ubuntuone'):
142 config_file = os.path.join(xdg_config_dir, CONFIG_FILE)154 config_file = os.path.join(xdg_config_dir, CONFIG_FILE)
143 if os.path.exists(config_file):155 if path_exists(config_file):
144 config_files.append(config_file)156 config_files.append(config_file)
145157
146 config_logs = os.path.join(xdg_config_dir, CONFIG_LOGS)158 config_logs = os.path.join(xdg_config_dir, CONFIG_LOGS)
147 if os.path.exists(config_logs):159 if path_exists(config_logs):
148 config_files.append(config_logs)160 config_files.append(config_logs)
149161
150 # reverse the list as load_config_paths returns the user dir first162 # reverse the list as load_config_paths returns the user dir first
@@ -152,11 +164,11 @@
152 # if we are running from a branch, get the config files from it too164 # if we are running from a branch, get the config files from it too
153 config_file = os.path.join(os.path.dirname(__file__), os.path.pardir,165 config_file = os.path.join(os.path.dirname(__file__), os.path.pardir,
154 os.path.pardir, 'data', CONFIG_FILE)166 os.path.pardir, 'data', CONFIG_FILE)
155 if os.path.exists(config_file):167 if path_exists(config_file):
156 config_files.append(config_file)168 config_files.append(config_file)
157169
158 config_logs = os.path.join('data', CONFIG_LOGS)170 config_logs = os.path.join('data', CONFIG_LOGS)
159 if os.path.exists(config_logs):171 if path_exists(config_logs):
160 config_files.append(config_logs)172 config_files.append(config_logs)
161173
162 return config_files174 return config_files
@@ -279,11 +291,11 @@
279 for section in [MAIN, THROTTLING, NOTIFICATIONS]:291 for section in [MAIN, THROTTLING, NOTIFICATIONS]:
280 if self.has_section(section) and not self.options(section):292 if self.has_section(section) and not self.options(section):
281 self.remove_section(section)293 self.remove_section(section)
282 with open(self.config_file+'.new', 'w') as fp:294 with open(self.config_file + '.new', 'w') as fp:
283 self.write(fp)295 self.write(fp)
284 if os.path.exists(self.config_file):296 if path_exists(self.config_file):
285 os.rename(self.config_file, self.config_file+'.old')297 rename(self.config_file, self.config_file + '.old')
286 os.rename(self.config_file+'.new', self.config_file)298 rename(self.config_file + '.new', self.config_file)
287299
288 def get_parsed(self, section, option):300 def get_parsed(self, section, option):
289 """get that fallbacks to our custom defaults"""301 """get that fallbacks to our custom defaults"""
290302
=== modified file 'ubuntuone/syncdaemon/main.py'
--- ubuntuone/syncdaemon/main.py 2011-06-23 13:10:19 +0000
+++ ubuntuone/syncdaemon/main.py 2011-07-29 19:11:49 +0000
@@ -98,6 +98,9 @@
9898
99 self.logger.info("Starting Ubuntu One client version %s",99 self.logger.info("Starting Ubuntu One client version %s",
100 clientdefs.VERSION)100 clientdefs.VERSION)
101 self.logger.info("Using %r as root dir", self.root_dir)
102 self.logger.info("Using %r as data dir", self.data_dir)
103 self.logger.info("Using %r as shares root dir", self.shares_dir)
101 self.db = tritcask.Tritcask(self.tritcask_dir)104 self.db = tritcask.Tritcask(self.tritcask_dir)
102 self.vm = volume_manager.VolumeManager(self)105 self.vm = volume_manager.VolumeManager(self)
103 self.fs = filesystem_manager.FileSystemManager(106 self.fs = filesystem_manager.FileSystemManager(
@@ -135,9 +138,6 @@
135 self.eventlog_listener = None138 self.eventlog_listener = None
136 self.start_event_logger()139 self.start_event_logger()
137 self.start_status_listener()140 self.start_status_listener()
138 self.logger.info("Using %r as root dir", self.root_dir)
139 self.logger.info("Using %r as data dir", self.data_dir)
140 self.logger.info("Using %r as shares root dir", self.shares_dir)
141 self.mark = task.LoopingCall(self.log_mark)141 self.mark = task.LoopingCall(self.log_mark)
142 self.mark.start(mark_interval)142 self.mark.start(mark_interval)
143143

Subscribers

People subscribed via source and target branches