]>
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
10 from logging
import debug
, info
, warning
, error
, critical
11 logging
. basicConfig ( format
= " %(levelname)s : %(message)s " )
13 import cPickle
as pickle
21 except ImportError , e
:
22 critical ( "Missing libraries! Exiting..." )
28 def filter_images ( filenames
):
29 extensions
= ( '.jpg' , '.jpe' , '.jpeg' , '.png' , '.gif' , '.bmp' )
30 for filename
in filenames
:
31 _
, ext
= os
. path
. splitext ( filename
)
32 if ext
. lower () in extensions
:
35 class BaseFileList ( object ):
36 """Base file list implementation"""
40 def add_path ( self
, path
):
41 self
. paths
. append ( path
)
43 def store_cache ( self
, filename
):
45 debug ( "Attempting to store cache" )
46 fd
= open ( filename
, 'wb' )
47 pickle
. dump ( obj
= self
, file = fd
, protocol
= 2 )
48 debug ( "Cache successfully stored" )
50 warning ( "Storing cache: %s " % e
)
52 def load_cache ( self
, filename
):
54 debug ( "Attempting to load cache from: %s " % filename
)
57 fd
= open ( filename
, 'rb' )
60 if tmp
.__ class
__ != self
.__ class
__ :
61 raise ValueError ( "Using different file list type" )
64 if self
. paths
!= getattr ( tmp
, "paths" ):
65 raise ValueError ( "Path list changed" )
67 for attr
, value
in tmp
.__ dict
__ . items ():
68 setattr ( self
, attr
, value
)
73 warning ( "Loading cache: %s " % e
)
77 raise NotImplementedError ()
79 def get_next_image ( self
):
80 raise NotImplementedError ()
82 def get_prev_image ( self
):
83 raise NotImplementedError ()
85 def get_current_image ( self
):
86 raise NotImplementedError ()
92 class RandomFileList ( BaseFileList
):
94 super ( RandomFileList
, self
) .__ init
__ ()
96 self
. last_image
= None
99 for path
in self
. paths
:
100 for dirpath
, dirsnames
, filenames
in os
. walk ( path
):
101 for filename
in filter_images ( filenames
):
102 self
. list . append ( os
. path
. join ( dirpath
, filename
))
104 def get_next_image ( self
):
105 n
= random
. randint ( 0 , len ( self
. list )- 1 )
106 self
. last_image
= self
. list [ n
]
107 debug ( "Picked file ' %s ' from list" % self
. last_image
)
108 return self
. last_image
110 def get_current_image ( self
):
112 return self
. last_image
114 return self
. get_next_image ()
117 return len ( self
. list ) == 0
120 class AllRandomFileList ( BaseFileList
):
122 super ( AllRandomFileList
, self
) .__ init
__ ()
124 self
. imagePointer
= 0
126 # Scan the input directory, and then randomize the file list
127 def scan_paths ( self
):
129 for path
in self
. paths
:
130 debug ( 'Scanning " %s "' % path
)
131 for dirpath
, dirsnames
, filenames
in os
. walk ( path
):
132 for filename
in filter_images ( filenames
):
133 debug ( 'Adding file " %s "' % filename
)
134 self
. list . append ( os
. path
. join ( dirpath
, filename
))
136 random
. shuffle ( self
. list )
138 def get_current_image ( self
):
139 return self
. list [ self
. imagePointer
]
141 def __inc_in_range ( self
, n
, amount
= 1 , rangeMax
= None , rangeMin
= 0 ):
142 if rangeMax
== None : rangeMax
= len ( self
. list )
144 return ( n
+ amount
) % rangeMax
146 def get_next_image ( self
):
147 self
. imagePointer
= self
.__ inc
_ in
_ range
( self
. imagePointer
)
148 imageName
= self
. list [ self
. imagePointer
]
149 debug ( "Picked file ' %s ' (pointer= %d ) from list" % ( imageName
, self
. imagePointer
))
152 def get_prev_image ( self
):
153 self
. imagePointer
= self
.__ inc
_ in
_ range
( self
. imagePointer
, amount
=- 1 )
154 imageName
= self
. list [ self
. imagePointer
]
155 debug ( "Picked file ' %s ' (pointer= %d ) from list" % ( imageName
, self
. imagePointer
))
159 return len ( self
. list ) == 0
161 class FolderRandomFileList ( BaseFileList
):
162 """A file list that will pick a file randomly within a directory. Each
163 directory has the same chance of being chosen."""
165 super ( FolderRandomFileList
, self
) .__ init
__ ()
166 self
. directories
= {}
167 self
. last_image
= None
169 def scan_paths ( self
):
170 for path
in self
. paths
:
171 for dirpath
, dirs
, filenames
in os
. walk ( path
):
172 debug ( 'Scanning " %s " for images' % dirpath
)
173 if self
. directories
. has_key ( dirpath
):
175 filenames
= list ( filter_images ( filenames
))
177 self
. directories
[ dirpath
] = filenames
178 debug ( 'Adding " %s " to " %s "' % ( filenames
, dirpath
))
180 debug ( "No images found in ' %s '" % dirpath
)
182 def get_next_image ( self
):
183 directory
= random
. choice ( self
. directories
. keys ())
184 debug ( 'directory: " %s "' % directory
)
185 filename
= random
. choice ( self
. directories
[ directory
])
186 debug ( 'filename: " %s "' % filename
)
187 self
. last_image
= os
. path
. join ( directory
, filename
)
188 return self
. last_image
190 def get_current_image ( self
):
192 return self
. last_image
194 return self
. get_next_image ()
197 return len ( self
. directories
. values ()) == 0
200 class Cycler ( object ):
201 def init ( self
, options
, paths
):
202 self
. cycle_time
= options
. cycle_time
203 self
. history_filename
= options
. history_filename
205 debug ( "Initialising wallchanger" )
206 wallchanger
. init ( options
. background_colour
, options
. permanent
)
208 debug ( "Initialising file list" )
209 if options
. all_random
:
210 self
. filelist
= AllRandomFileList ()
211 elif options
. folder_random
:
212 self
. filelist
= FolderRandomFileList ()
214 self
. filelist
= RandomFileList ()
217 self
. filelist
. add_path ( path
)
219 if self
. filelist
. load_cache ( self
. history_filename
):
220 debug ( "Loaded cache successfully" )
222 debug ( "Could not load cache" )
223 self
. filelist
. scan_paths ()
225 if self
. filelist
. is_empty ():
226 error ( "No images were found. Exiting..." )
233 self
. filelist
. store_cache ( self
. history_filename
)
235 def find_files ( self
, options
, paths
):
240 image
= self
. filelist
. get_next_image ()
241 wallchanger
. set_image ( image
)
245 if self
. task
is not None :
247 self
. task
= asyncsched
. schedule ( self
. cycle_time
, next
)
248 debug ( "Reset timer for %s seconds" % self
. cycle_time
)
250 def cmd_reload ( self
):
251 image
= self
. filelist
. get_current_image ()
252 wallchanger
. set_image ( image
)
256 image
= self
. filelist
. get_next_image ()
257 wallchanger
. set_image ( image
)
261 image
= self
. filelist
. get_prev_image ()
262 wallchanger
. set_image ( image
)
265 def cmd_rescan ( self
):
266 self
. filelist
. scan_paths ()
269 if self
. task
is not None :
276 class Server ( asynchat
. async_chat
):
277 def __init__ ( self
, cycler
, conn
, addr
):
278 asynchat
. async_chat
.__ init
__ ( self
, conn
= conn
)
281 self
. set_terminator ( " \n " )
283 def collect_incoming_data ( self
, data
):
284 self
. ibuffer
. append ( data
)
286 def found_terminator ( self
):
287 line
= "" . join ( self
. ibuffer
). lower ()
289 prefix
, cmd
= line
. split ( None , 1 )
291 debug ( 'Bad line received " %s "' % line
)
293 if hasattr ( self
. cycler
, "cmd_" + cmd
):
294 debug ( 'Executing command " %s "' % cmd
)
295 getattr ( self
. cycler
, "cmd_" + cmd
)()
297 debug ( 'Unknown command received " %s "' % cmd
)
301 class Listener ( asyncore
. dispatcher
):
302 def __init__ ( self
, socket_filename
, cycler
):
303 asyncore
. dispatcher
.__ init
__ ( self
)
305 self
. create_socket ( socket
. AF_UNIX
, socket
. SOCK_STREAM
)
306 self
. bind ( socket_filename
)
307 self
. listen ( 2 ) # Backlog = 2
309 def handle_accept ( self
):
310 conn
, addr
= self
. accept ()
311 Server ( self
. cycler
, conn
, addr
)
317 def do_server ( options
, paths
):
320 listener
= Listener ( options
. socket_filename
, cycler
)
321 # Initialisation of Cycler delayed so we grab the socket quickly
322 cycler
. init ( options
, paths
)
325 except KeyboardInterrupt :
329 # Make sure that the socket is cleaned up
331 os
. unlink ( options
. socket_filename
)
335 def do_client ( options
, args
):
338 sock
= socket
. socket ( socket
. AF_UNIX
, socket
. SOCK_STREAM
)
339 sock
. connect ( options
. socket_filename
)
340 sock
= sock
. makefile ()
341 for i
, cmd
in enumerate ( args
):
342 sock
. write ( "cmd %s \n " % cmd
)
344 time
. sleep ( options
. cycle_time
)
347 def do_oneshot ( options
, paths
):
349 cycler
. init ( options
, paths
)
352 parser
= OptionParser ( version
= "%prog " + VERSION
,
353 description
= "Cycles through random background images." ,
355 " \n (server) %prog [options] dir [dir2 ...]"
356 " \n (client) %prog [options] [next|prev|rescan|reload|pause] [...]"
357 " \n The first instance to be run will be the server. \n "
359 parser
. add_option ( "-p" , "--permanent" ,
360 action
= "store_true" , dest
= "permanent" , default
= False ,
361 help = "Make the background permanent. Note: This will cause all machines logged in with this account to simultaneously change background [Default: %def ault]" )
362 parser
. add_option ( "-v" , '-d' , "--verbose" , "--debug" ,
363 action
= "count" , dest
= "verbose" , default
= 0 ,
364 help = "Make the louder (good for debugging, or those who are curious)" )
365 parser
. add_option ( "-b" , "--background-colour" ,
366 action
= "store" , type = "string" , dest
= "background_colour" , default
= "black" ,
367 help = "Change the default background colour that is displayed if the image is not in the correct aspect ratio [Default: %def ault]" )
368 parser
. add_option ( "--all-random" ,
369 action
= "store_true" , dest
= "all_random" , default
= False ,
370 help = "Make sure that all images have been displayed before repeating an image" )
371 parser
. add_option ( "-1" , "--oneshot" ,
372 action
= "store_true" , dest
= "oneshot" , default
= False ,
373 help = "Set one random image and terminate immediately." )
374 parser
. add_option ( "--folder-random" ,
375 action
= "store_true" , dest
= "folder_random" , default
= False ,
376 help = "Give each folder an equal chance of having an image selected from it" )
377 parser
. add_option ( "--convert" ,
378 action
= "store_true" , dest
= "convert" , default
= False ,
379 help = "Do conversions using ImageMagick or PIL, don't rely on the window manager" )
380 parser
. add_option ( "--cycle-time" ,
381 action
= "store" , type = "int" , default
= 1800 , dest
= "cycle_time" ,
382 help = "Cause the image to cycle every X seconds" )
383 parser
. add_option ( "--socket" ,
384 action
= "store" , type = "string" , dest
= "socket_filename" , default
= os
. path
. expanduser ( '~/.randombg_socket' ),
385 help = "Location of the command/control socket." )
386 parser
. add_option ( "--history-file" ,
387 action
= "store" , type = "string" , dest
= "history_filename" , default
= os
. path
. expanduser ( '~/.randombg_historyfile' ),
388 help = "Stores the location of the last image to be loaded." )
392 parser
= build_parser ()
393 options
, args
= parser
. parse_args ( sys
. argv
[ 1 :])
395 if options
. verbose
== 1 :
396 logging
. getLogger (). setLevel ( logging
. INFO
)
397 elif options
. verbose
>= 2 :
398 logging
. getLogger (). setLevel ( logging
. DEBUG
)
401 do_oneshot ( options
, args
)
403 if os
. path
. exists ( options
. socket_filename
):
404 do_client ( options
, args
)
406 do_server ( options
, args
)
409 if __name__
== "__main__" :