]>
code.delx.au - bg-scripts/blob - randombg.py
6 import asyncore
, asynchat
, socket
7 import os
, os
. path
, random
, sys
, time
8 from optparse
import OptionParser
11 logging
. basicConfig ( format
= " %(levelname)s : %(message)s " )
13 # Python 2.3's logging.basicConfig does not support parameters
17 import cPickle
as pickle
25 except ImportError , e
:
26 logging
. critical ( "Missing libraries! Exiting..." , exc_info
= 1 )
32 def filter_images ( filenames
):
33 extensions
= ( '.jpg' , '.jpe' , '.jpeg' , '.png' , '.gif' , '.bmp' )
34 for filename
in filenames
:
35 _
, ext
= os
. path
. splitext ( filename
)
36 if ext
. lower () in extensions
:
39 class BaseFileList ( object ):
40 """Base file list implementation"""
45 def add_path ( self
, path
):
46 self
. paths
. append ( path
)
48 def store_cache ( self
, filename
):
50 logging
. debug ( "Attempting to store cache" )
51 fd
= open ( filename
, 'wb' )
52 pickle
. dump ( self
, fd
, 2 )
53 logging
. debug ( "Cache successfully stored" )
55 warning ( "Storing cache: %s " % e
)
57 def load_cache ( self
, filename
):
59 logging
. debug ( "Attempting to load cache from: %s " % filename
)
62 fd
= open ( filename
, 'rb' )
65 if tmp
.__ class
__ != self
.__ class
__ :
66 raise ValueError ( "Using different file list type" )
69 if self
. paths
!= tmp
. paths
:
70 raise ValueError , "Path list changed"
72 # Overwrite this object with the other
73 for attr
, value
in tmp
.__ dict
__ . items ():
74 setattr ( self
, attr
, value
)
79 logging
. warning ( "Loading cache: %s " % e
)
82 def add_to_favourites ( self
):
83 '''Adds the current image to the list of favourites'''
84 self
. favourites
. append ( self
. get_current_image ())
87 raise NotImplementedError ()
89 def get_next_image ( self
):
90 raise NotImplementedError ()
92 def get_prev_image ( self
):
93 raise NotImplementedError ()
95 def get_current_image ( self
):
96 raise NotImplementedError ()
102 class RandomFileList ( BaseFileList
):
104 super ( RandomFileList
, self
) .__ init
__ ()
106 self
. last_image
= None
108 def scan_paths ( self
):
109 for path
in self
. paths
:
110 for dirpath
, dirsnames
, filenames
in os
. walk ( path
):
111 for filename
in filter_images ( filenames
):
112 self
. list . append ( os
. path
. join ( dirpath
, filename
))
114 def add_path ( self
, path
):
115 self
. paths
. append ( path
)
116 logging
. debug ( 'Added path " %s " to the list' % path
)
118 def get_next_image ( self
):
119 n
= random
. randint ( 0 , len ( self
. list )- 1 )
120 self
. last_image
= self
. list [ n
]
121 logging
. debug ( "Picked file ' %s ' from list" % self
. last_image
)
122 return self
. last_image
124 def get_current_image ( self
):
126 return self
. last_image
128 return self
. get_next_image ()
131 return len ( self
. list ) == 0
134 class AllRandomFileList ( BaseFileList
):
136 super ( AllRandomFileList
, self
) .__ init
__ ()
138 self
. imagePointer
= 0
140 # Scan the input directory, and then randomize the file list
141 def scan_paths ( self
):
142 logging
. debug ( "Scanning paths" )
145 for path
in self
. paths
:
146 logging
. debug ( 'Scanning " %s "' % path
)
147 for dirpath
, dirsnames
, filenames
in os
. walk ( path
):
148 for filename
in filter_images ( filenames
):
149 logging
. debug ( 'Adding file " %s "' % filename
)
150 self
. list . append ( os
. path
. join ( dirpath
, filename
))
152 random
. shuffle ( self
. list )
154 def add_path ( self
, path
):
155 self
. paths
. append ( path
)
156 logging
. debug ( 'Added path " %s " to the list' % path
)
158 def store_cache ( self
, filename
):
160 fd
= open ( filename
, 'wb' )
161 pickle
. dump ( self
, fd
, 2 )
162 logging
. debug ( "Cache successfully stored" )
164 logging
. warning ( "Storing cache" , exc_info
= 1 )
166 def get_current_image ( self
):
167 return self
. list [ self
. imagePointer
]
169 def __inc_in_range ( self
, n
, amount
= 1 , rangeMax
= None , rangeMin
= 0 ):
170 if rangeMax
== None : rangeMax
= len ( self
. list )
172 return ( n
+ amount
) % rangeMax
174 def get_next_image ( self
):
175 self
. imagePointer
= self
.__ inc
_ in
_ range
( self
. imagePointer
)
176 imageName
= self
. list [ self
. imagePointer
]
177 logging
. debug ( "Picked file ' %s ' (pointer= %d ) from list" % ( imageName
, self
. imagePointer
))
180 def get_prev_image ( self
):
181 self
. imagePointer
= self
.__ inc
_ in
_ range
( self
. imagePointer
, amount
=- 1 )
182 imageName
= self
. list [ self
. imagePointer
]
183 logging
. debug ( "Picked file ' %s ' (pointer= %d ) from list" % ( imageName
, self
. imagePointer
))
187 return len ( self
. list ) == 0
190 class FolderRandomFileList ( BaseFileList
):
191 """A file list that will pick a file randomly within a directory. Each
192 directory has the same chance of being chosen."""
194 super ( FolderRandomFileList
, self
) .__ init
__ ()
195 self
. directories
= {}
196 self
. last_image
= None
198 def scan_paths ( self
):
201 def add_path ( self
, path
):
202 logging
. debug ( 'Added path " %s " to the list' % path
)
203 for dirpath
, dirs
, filenames
in os
. walk ( path
):
204 logging
. debug ( 'Scanning " %s " for images' % dirpath
)
205 if self
. directories
. has_key ( dirpath
):
207 filenames
= list ( filter_images ( filenames
))
209 self
. directories
[ dirpath
] = filenames
210 logging
. debug ( 'Adding " %s " to " %s "' % ( filenames
, dirpath
))
212 logging
. debug ( "No images found in ' %s '" % dirpath
)
214 def get_next_image ( self
):
215 directory
= random
. choice ( self
. directories
. keys ())
216 logging
. debug ( 'directory: " %s "' % directory
)
217 filename
= random
. choice ( self
. directories
[ directory
])
218 logging
. debug ( 'filename: " %s "' % filename
)
219 return os
. path
. join ( directory
, filename
)
221 def get_current_image ( self
):
223 return self
. last_image
225 return self
. get_next_image ()
228 return len ( self
. directories
. values ()) == 0
231 class Cycler ( object ):
232 def init ( self
, options
, paths
, oneshot
= False ):
233 self
. cycle_time
= options
. cycle_time
234 self
. history_filename
= options
. history_filename
236 logging
. debug ( "Initialising wallchanger" )
237 wallchanger
. init ( options
. background_colour
, options
. permanent
, options
. convert
)
239 logging
. debug ( "Initialising file list" )
240 if options
. all_random
:
241 self
. filelist
= AllRandomFileList ()
242 elif options
. folder_random
:
243 self
. filelist
= FolderRandomFileList ()
245 self
. filelist
= RandomFileList ()
248 self
. filelist
. add_path ( path
)
250 if self
. filelist
. load_cache ( self
. history_filename
):
251 logging
. debug ( "Loaded cache successfully" )
253 logging
. debug ( "Could not load cache" )
254 self
. filelist
. scan_paths ()
256 if self
. filelist
. is_empty ():
257 logging
. error ( "No images were found. Exiting..." )
267 self
. filelist
. store_cache ( self
. history_filename
)
269 def find_files ( self
, options
, paths
):
274 image
= self
. filelist
. get_next_image ()
275 wallchanger
. set_image ( image
)
279 if self
. task
is not None :
281 self
. task
= asyncsched
. schedule ( self
. cycle_time
, next
)
282 logging
. debug ( "Reset timer for %s seconds" % self
. cycle_time
)
283 self
. filelist
. store_cache ( self
. history_filename
)
285 def cmd_reload ( self
):
286 image
= self
. filelist
. get_current_image ()
287 wallchanger
. set_image ( image
)
291 image
= self
. filelist
. get_next_image ()
292 wallchanger
. set_image ( image
)
296 image
= self
. filelist
. get_prev_image ()
297 wallchanger
. set_image ( image
)
300 def cmd_rescan ( self
):
301 self
. filelist
. scan_paths ()
304 if self
. task
is not None :
311 def cmd_favourite ( self
):
312 self
. filelist
. add_to_favourites ()
314 class Server ( asynchat
. async_chat
):
315 def __init__ ( self
, cycler
, conn
, addr
):
316 asynchat
. async_chat
.__ init
__ ( self
, conn
= conn
)
319 self
. set_terminator ( " \n " )
321 def collect_incoming_data ( self
, data
):
322 self
. ibuffer
. append ( data
)
324 def found_terminator ( self
):
325 line
= "" . join ( self
. ibuffer
). lower ()
327 prefix
, cmd
= line
. split ( None , 1 )
329 logging
. debug ( 'Bad line received " %s "' % line
)
331 if hasattr ( self
. cycler
, "cmd_" + cmd
):
332 logging
. debug ( 'Executing command " %s "' % cmd
)
333 getattr ( self
. cycler
, "cmd_" + cmd
)()
335 logging
. debug ( 'Unknown command received " %s "' % cmd
)
338 class Listener ( asyncore
. dispatcher
):
339 def __init__ ( self
, socket_filename
, cycler
):
340 asyncore
. dispatcher
.__ init
__ ( self
)
342 self
. create_socket ( socket
. AF_UNIX
, socket
. SOCK_STREAM
)
343 self
. bind ( socket_filename
)
344 self
. listen ( 2 ) # Backlog = 2
346 def handle_accept ( self
):
347 conn
, addr
= self
. accept ()
348 Server ( self
. cycler
, conn
, addr
)
354 def do_server ( options
, paths
):
357 listener
= Listener ( options
. socket_filename
, cycler
)
358 # Initialisation of Cycler delayed so we grab the socket quickly
359 cycler
. init ( options
, paths
)
362 except KeyboardInterrupt :
366 # Make sure that the socket is cleaned up
368 os
. unlink ( options
. socket_filename
)
372 def do_client ( options
, args
):
375 sock
= socket
. socket ( socket
. AF_UNIX
, socket
. SOCK_STREAM
)
376 sock
. connect ( options
. socket_filename
)
377 sock
= sock
. makefile ()
378 for i
, cmd
in enumerate ( args
):
379 sock
. write ( "cmd %s \n " % cmd
)
380 if i
< len ( args
) - 1 :
381 time
. sleep ( options
. cycle_time
)
384 def do_oneshot ( options
, paths
):
386 cycler
. init ( options
, paths
, oneshot
= True )
389 parser
= OptionParser ( version
= "%prog " + VERSION
,
390 description
= "Cycles through random background images." ,
392 " \n (server) %prog [options] dir [dir2 ...]"
393 " \n (client) %prog [options] [next|prev|rescan|reload|pause] [...]"
394 " \n The first instance to be run will be the server. \n "
396 parser
. add_option ( "-p" , "--permanent" ,
397 action
= "store_true" , dest
= "permanent" , default
= False ,
398 help = "Make the background permanent. Note: This will cause all machines logged in with this account to simultaneously change background [Default: %def ault]" )
399 parser
. add_option ( "-v" , '-d' , "--verbose" , "--debug" ,
400 action
= "count" , dest
= "verbose" , default
= 0 ,
401 help = "Make the louder (good for debugging, or those who are curious)" )
402 parser
. add_option ( "-b" , "--background-colour" ,
403 action
= "store" , type = "string" , dest
= "background_colour" , default
= "black" ,
404 help = "Change the default background colour that is displayed if the image is not in the correct aspect ratio [Default: %def ault]" )
405 parser
. add_option ( "--all-random" ,
406 action
= "store_true" , dest
= "all_random" , default
= False ,
407 help = "Make sure that all images have been displayed before repeating an image" )
408 parser
. add_option ( "-1" , "--oneshot" ,
409 action
= "store_true" , dest
= "oneshot" , default
= False ,
410 help = "Set one random image and terminate immediately." )
411 parser
. add_option ( "--folder-random" ,
412 action
= "store_true" , dest
= "folder_random" , default
= False ,
413 help = "Give each folder an equal chance of having an image selected from it" )
414 parser
. add_option ( "--convert" ,
415 action
= "store_true" , dest
= "convert" , default
= False ,
416 help = "Do conversions using ImageMagick or PIL, don't rely on the window manager" )
417 parser
. add_option ( "--cycle-time" ,
418 action
= "store" , type = "int" , default
= 1800 , dest
= "cycle_time" ,
419 help = "Cause the image to cycle every X seconds" )
420 parser
. add_option ( "--socket" ,
421 action
= "store" , type = "string" , dest
= "socket_filename" , default
= os
. path
. expanduser ( '~/.randombg_socket' ),
422 help = "Location of the command/control socket." )
423 parser
. add_option ( "--history-file" ,
424 action
= "store" , type = "string" , dest
= "history_filename" , default
= os
. path
. expanduser ( '~/.randombg_historyfile' ),
425 help = "Stores the location of the last image to be loaded." )
429 parser
= build_parser ()
430 options
, args
= parser
. parse_args ( sys
. argv
[ 1 :])
432 if options
. verbose
== 1 :
433 logging
. getLogger (). setLevel ( logging
. INFO
)
434 elif options
. verbose
>= 2 :
435 logging
. getLogger (). setLevel ( logging
. DEBUG
)
438 do_oneshot ( options
, args
)
440 if os
. path
. exists ( options
. socket_filename
):
441 do_client ( options
, args
)
443 do_server ( options
, args
)
446 if __name__
== "__main__" :