Class: Ezmlm::List

Inherits:
Object
  • Object
show all
Defined in:
lib/ezmlm/list.rb

Overview

A Ruby interface to a single Ezmlm-idx mailing list directory.

list = Ezmlm::List.new( '/path/to/listdir' )

Defined Under Namespace

Classes: Author, Message, Thread

Constant Summary collapse

SUBSCRIPTION_DIRS =

Valid subdirectories/sections for subscriptions.

%w[ deny mod digest allow ]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(listdir) ⇒ List

Create a new Ezmlm::List object for the specified listdir, which should be an ezmlm-idx mailing list directory.



25
26
27
28
29
30
31
# File 'lib/ezmlm/list.rb', line 25

def initialize( listdir )
	listdir = Pathname.new( listdir ) unless listdir.is_a?( Pathname )
	unless listdir.directory? && ( listdir + 'ezmlmrc' ).exist?
		raise ArgumentError, "%p doesn't appear to be an ezmlm-idx list." % [ listdir.to_s ]
	end
	@listdir = listdir
end

Instance Attribute Details

#listdirObject (readonly)

The Pathname object for the list directory



34
35
36
# File 'lib/ezmlm/list.rb', line 34

def listdir
  @listdir
end

Instance Method Details

#add_allowed(*addr) ⇒ Object

Add addr to allow posting to user-post only lists, when addr isn’t a subscriber.



210
211
212
# File 'lib/ezmlm/list.rb', line 210

def add_allowed( *addr )
	return self.subscribe( *addr, section: 'allow' )
end

#add_blacklisted(*addr) ⇒ Object

Blacklist addr from the list.



181
182
183
# File 'lib/ezmlm/list.rb', line 181

def add_blacklisted( *addr )
	return self.subscribe( *addr, section: 'deny' )
end

#add_moderator(*addr) ⇒ Object

Subscribe addr to the list as a Moderator.



155
156
157
# File 'lib/ezmlm/list.rb', line 155

def add_moderator( *addr )
	return self.subscribe( *addr, section: 'mod' )
end

#addressObject Also known as: fullname

Return the configured address of the list (in list@host form)



55
56
57
# File 'lib/ezmlm/list.rb', line 55

def address
	return "%s@%s" % [ self.name, self.host ]
end

#allow_remote_listing=(enable = false) ⇒ Object Also known as: allow_remote_listing

Disable or enable the ability for moderators to remotely fetch a subscriber list.



572
573
574
575
576
577
578
# File 'lib/ezmlm/list.rb', line 572

def allow_remote_listing=( enable=false )
	if enable
		self.touch( 'modcanlist' )
	else
		self.unlink( 'modcanlist' )
	end
end

#allow_remote_listing?Boolean

Returns true if the list allows moderators to fetch a subscriber list remotely.

Returns:

  • (Boolean)


565
566
567
# File 'lib/ezmlm/list.rb', line 565

def allow_remote_listing?
	return ( self.listdir + 'modcanlist' ).exist?
end

#allowedObject

Returns an Array of email addresses that act like regular subscribers for user-post only lists.



196
197
198
# File 'lib/ezmlm/list.rb', line 196

def allowed
	return self.read_subscriber_dir( 'allow' )
end

#archived=(enable = true) ⇒ Object Also known as: archived

Disable or enable message archiving (and indexing/threading.)



370
371
372
373
374
375
376
# File 'lib/ezmlm/list.rb', line 370

def archived=( enable=true )
	if enable
		self.touch( 'archived', 'indexed', 'threaded' )
	else
		self.unlink( 'archived', 'indexed', 'threaded' )
	end
end

#archived?Boolean

Returns true if message archival is enabled.

Returns:

  • (Boolean)


360
361
362
363
364
365
366
# File 'lib/ezmlm/list.rb', line 360

def archived?
	test = %w[ archived indexed threaded ].each_with_object( [] ) do |f, acc|
		acc << self.listdir + f
	end

	return test.all?( &:exist? )
end

#author(author_id) ⇒ Object

Return an Author object for the given author_id, which could also be an email address.



660
661
662
663
# File 'lib/ezmlm/list.rb', line 660

def author( author_id )
	author_id = Ezmlm::Hash.address(author_id) if author_id.index( '@' )
	return Ezmlm::List::Author.new( self, author_id ) rescue nil
end

#blacklistedObject

Returns an Array of email addresses denied access to the list.



169
170
171
# File 'lib/ezmlm/list.rb', line 169

def blacklisted
	return self.read_subscriber_dir( 'deny' )
end

#bounce_warnings=(enable = true) ⇒ Object Also known as: bounce_warnings

Disable or enable automatic bounce probes and warnings.



591
592
593
594
595
596
597
# File 'lib/ezmlm/list.rb', line 591

def bounce_warnings=( enable=true )
	if enable
		self.unlink( 'nowarn' )
	else
		self.touch( 'nowarn' )
	end
end

#bounce_warnings?Boolean

Returns true if the list automatically manages bouncing subscriber addresses.

Returns:

  • (Boolean)


585
586
587
# File 'lib/ezmlm/list.rb', line 585

def bounce_warnings?
	return ! ( self.listdir + 'nowarn' ).exist?
end

#confirm_postings=(enable = false) ⇒ Object Also known as: confirm_postings

Disable or enable message confirmation.



552
553
554
555
556
557
558
# File 'lib/ezmlm/list.rb', line 552

def confirm_postings=( enable=false )
	if enable
		self.touch( 'confirmpost' )
	else
		self.unlink( 'confirmpost' )
	end
end

#confirm_postings?Boolean

Returns true if the list requires regular message postings to be confirmed by the original sender.

Returns:

  • (Boolean)


546
547
548
# File 'lib/ezmlm/list.rb', line 546

def confirm_postings?
	return ( self.listdir + 'confirmpost' ).exist?
end

#confirm_subscriptions=(enable = true) ⇒ Object Also known as: confirm_subscriptions

Disable or enable subscription confirmation. AKA “help” mode if disabled.



514
515
516
517
518
519
520
# File 'lib/ezmlm/list.rb', line 514

def confirm_subscriptions=( enable=true )
	if enable
		self.unlink( 'nosubconfirm' )
	else
		self.touch( 'nosubconfirm' )
	end
end

#confirm_subscriptions?Boolean

Returns true if the list requires subscriptions to be confirmed. AKA “help” mode if disabled.

Returns:

  • (Boolean)


507
508
509
# File 'lib/ezmlm/list.rb', line 507

def confirm_subscriptions?
	return ! ( self.listdir + 'nosubconfirm' ).exist?
end

#confirm_unsubscriptions=(enable = true) ⇒ Object Also known as: confirm_unsubscriptions

Disable or enable unsubscription confirmation. AKA “jump” mode.



533
534
535
536
537
538
539
# File 'lib/ezmlm/list.rb', line 533

def confirm_unsubscriptions=( enable=true )
	if enable
		self.unlink( 'nounsubconfirm' )
	else
		self.touch( 'nounsubconfirm' )
	end
end

#confirm_unsubscriptions?Boolean

Returns true if the list requires unsubscriptions to be confirmed. AKA “jump” mode.

Returns:

  • (Boolean)


526
527
528
# File 'lib/ezmlm/list.rb', line 526

def confirm_unsubscriptions?
	return ! ( self.listdir + 'nounsubconfirm' ).exist?
end

#digest=(enable = true) ⇒ Object Also known as: digest

Disable or enable message digesting.



437
438
439
440
441
442
443
# File 'lib/ezmlm/list.rb', line 437

def digest=( enable=true )
	if enable
		self.touch( 'digested' )
	else
		self.unlink( 'digested' )
	end
end

#digest_countObject

If the list is digestable, trigger the digest after this many messages have accumulated since the latest digest.

See: ezmlm-tstdig(1)



470
471
472
473
# File 'lib/ezmlm/list.rb', line 470

def digest_count
	count = self.read( 'digcount' ).to_i
	return count.zero? ? 30 : count
end

#digest_count=(count = 30) ⇒ Object

If the list is digestable, trigger the digest after this many messages have accumulated since the latest digest.

See: ezmlm-tstdig(1)



480
481
482
# File 'lib/ezmlm/list.rb', line 480

def digest_count=( count=30 )
	self.write( 'digcount' ) {|f| f.puts count.to_i }
end

#digest_kbytesizeObject

If the list is digestable, trigger the digest after this amount of message body since the latest digest, in kbytes.

See: ezmlm-tstdig(1)



451
452
453
454
# File 'lib/ezmlm/list.rb', line 451

def digest_kbytesize
	size = self.read( 'digsize' ).to_i
	return size.zero? ? 64 : size
end

#digest_kbytesize=(size = 64) ⇒ Object

If the list is digestable, trigger the digest after this amount of message body since the latest digest, in kbytes.

See: ezmlm-tstdig(1)



461
462
463
# File 'lib/ezmlm/list.rb', line 461

def digest_kbytesize=( size=64 )
	self.write( 'digsize' ) {|f| f.puts size.to_i }
end

#digest_timeoutObject

If the list is digestable, trigger the digest after this much time has passed since the last digest, in hours.

See: ezmlm-tstdig(1)



489
490
491
492
# File 'lib/ezmlm/list.rb', line 489

def digest_timeout
	hours = self.read( 'digtime' ).to_i
	return hours.zero? ? 48 : hours
end

#digest_timeout=(hours = 48) ⇒ Object

If the list is digestable, trigger the digest after this much time has passed since the last digest, in hours.

See: ezmlm-tstdig(1)



499
500
501
# File 'lib/ezmlm/list.rb', line 499

def digest_timeout=( hours=48 )
	self.write( 'digtime' ) {|f| f.puts hours.to_i }
end

#digested?Boolean

Returns true if message digests are enabled.

Returns:

  • (Boolean)


431
432
433
# File 'lib/ezmlm/list.rb', line 431

def digested?
	return ( self.listdir + 'digested' ).exist?
end

#each_messageObject

Lazy load each message ID as a Ezmlm::List::Message, yielding it to the block.



643
644
645
646
647
# File 'lib/ezmlm/list.rb', line 643

def each_message
	( 1 .. self.message_count ).each do |id|
		yield self.message( id )
	end
end

#guarded_archive=(enable = true) ⇒ Object Also known as: guarded_archive

Disable or enable loimited access to the archive.



419
420
421
422
423
424
425
# File 'lib/ezmlm/list.rb', line 419

def guarded_archive=( enable=true )
	if enable
		self.touch( 'subgetonly' )
	else
		self.unlink( 'subgetonly' )
	end
end

#guarded_archive?Boolean

Returns true if the message archive is accessible only to list subscribers.

Returns:

  • (Boolean)


413
414
415
# File 'lib/ezmlm/list.rb', line 413

def guarded_archive?
	return ( self.listdir + 'subgetonly' ).exist?
end

#hostObject

Return the configured host of the list



47
48
49
50
# File 'lib/ezmlm/list.rb', line 47

def host
	@host = self.read( 'outhost' ) unless @host
	return @host
end

#include?(addr, section: nil) ⇒ Boolean Also known as: is_subscriber?

Returns true if address is a subscriber to this list.

Returns:

  • (Boolean)


71
72
73
74
75
76
# File 'lib/ezmlm/list.rb', line 71

def include?( addr, section: nil )
	addr = addr.downcase
	file = self.subscription_dir( section ) + Ezmlm::Hash.subscriber( addr )
	return false unless file.exist?
	return file.read.scan( /T([^\0]+)\0/ ).flatten.include?( addr )
end

#indexObject

Parse all thread indexes into a single array that can be used as a lookup table.

These are not expanded into objects, use #message, #thread, and #author to do so.



682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
# File 'lib/ezmlm/list.rb', line 682

def index
	raise "Archiving is not enabled." unless self.archived?
	archivedir = listdir + 'archive'

	idx = ( 0 .. self.message_count / 100 ).each_with_object( [] ) do |dir, acc|
		index = archivedir + dir.to_s + 'index'
		next unless index.exist?

		index.open( 'r', encoding: Encoding::ISO8859_1 ) do |fh|
			fh.each_line.lazy.slice_before( /^\d+:/ ).each do |message|

				match = message[0].match( /^(?<message_id>\d+): (?<thread_id>\w+)/ )
				next unless match
				thread_id  = match[ :thread_id ]

				match = message[1].match( /^(?<date>[^;]+);(?<author_id>\w+) / )
				next unless match
				author_id  = match[ :author_id ]
				date       = match[ :date ]

				 = {
					date:   Time.parse( date ),
					thread: thread_id,
					author: author_id
				}
				acc << 
			end
		end
	end

	return idx
end

#is_allowed?(addr) ⇒ Boolean

Returns true if address is given the same benefits as a regular subscriber for user-post only lists.

Returns:

  • (Boolean)


203
204
205
# File 'lib/ezmlm/list.rb', line 203

def is_allowed?( addr )
	return self.include?( addr, section: 'allow' )
end

#is_blacklisted?(addr) ⇒ Boolean

Returns true if address is disallowed from participating.

Returns:

  • (Boolean)


175
176
177
# File 'lib/ezmlm/list.rb', line 175

def is_blacklisted?( addr )
	return self.include?( addr, section: 'deny' )
end

#is_moderator?(addr) ⇒ Boolean

Returns true if address is a moderator.

Returns:

  • (Boolean)


149
150
151
# File 'lib/ezmlm/list.rb', line 149

def is_moderator?( addr )
	return self.include?( addr, section: 'mod' )
end

#last_activityObject

Return a Time object for the last activity on the list, or nil if archiving is disabled or there are no posts.



669
670
671
672
673
# File 'lib/ezmlm/list.rb', line 669

def last_activity
	file = self.listdir + 'archnum'
	return unless file.exist?
	return file.stat.mtime
end

#maximum_message_sizeObject

Return the maximum message size, in bytes. Messages larger than this size will be rejected.

See: ezmlm-reject(1)



606
607
608
609
# File 'lib/ezmlm/list.rb', line 606

def maximum_message_size
	size = self.read( 'msgsize' )
	return size ? size.split( ':' ).first.to_i : 0
end

#maximum_message_size=(size = 307200) ⇒ Object

Set the maximum message size, in bytes. Messages larger than this size will be rejected. Defaults to 300kb.

See: ezmlm-reject(1)



616
617
618
619
620
621
622
# File 'lib/ezmlm/list.rb', line 616

def maximum_message_size=( size=307200 )
	if size.to_i.zero?
		self.unlink( 'msgsize' )
	else
		self.write( 'msgsize' ) {|f| f.puts "#{size.to_i}:0" }
	end
end

#message(message_id) ⇒ Object

Returns an individual message if archiving was enabled.



635
636
637
638
# File 'lib/ezmlm/list.rb', line 635

def message( message_id )
	raise "Message archive is empty." if self.message_count.zero?
	return Ezmlm::List::Message.new( self, message_id ) rescue nil
end

#message_countObject

Return the number of messages in the list archive.



628
629
630
631
# File 'lib/ezmlm/list.rb', line 628

def message_count
	count = self.read( 'archnum' )
	return count ? Integer( count ) : 0
end

#moderated=(enable = false) ⇒ Object Also known as: moderated

Disable or enable message moderation.

This has special meaning when combined with user_posts_only setting. Lists act as unmoderated for subscribers, and posts from unknown addresses go to moderation.



303
304
305
306
307
308
309
310
311
# File 'lib/ezmlm/list.rb', line 303

def moderated=( enable=false )
	if enable
		self.touch( 'modpost' )
		self.touch( 'noreturnposts' ) if self.user_posts_only?
	else
		self.unlink( 'modpost' )
		self.unlink( 'noreturnposts' ) if self.user_posts_only?
	end
end

#moderated?Boolean

Returns true if message moderation is enabled.

Returns:

  • (Boolean)


293
294
295
# File 'lib/ezmlm/list.rb', line 293

def moderated?
	return ( self.listdir + 'modpost' ).exist?
end

#moderated_subscriptions=(enable = false) ⇒ Object Also known as: moderated_subscriptions

Disable or enable subscription moderation.



282
283
284
285
286
287
288
# File 'lib/ezmlm/list.rb', line 282

def moderated_subscriptions=( enable=false )
	if enable
		self.touch( 'modsub' )
	else
		self.unlink( 'modsub' )
	end
end

#moderated_subscriptions?Boolean

Returns true if list subscription requests require moderator approval.

Returns:

  • (Boolean)


276
277
278
# File 'lib/ezmlm/list.rb', line 276

def moderated_subscriptions?
	return ( self.listdir + 'modsub' ).exist?
end

#moderator_posts_only=(enable = false) ⇒ Object Also known as: moderator_posts_only

Disable or enable moderation only posts.



323
324
325
326
327
328
329
# File 'lib/ezmlm/list.rb', line 323

def moderator_posts_only=( enable=false )
	if enable
		self.touch( 'modpostonly' )
	else
		self.unlink( 'modpostonly' )
	end
end

#moderator_posts_only?Boolean

Returns true if posting is only allowed by moderators.

Returns:

  • (Boolean)


317
318
319
# File 'lib/ezmlm/list.rb', line 317

def moderator_posts_only?
	return ( self.listdir + 'modpostonly' ).exist?
end

#moderatorsObject

Returns an Array of email addresses of people responsible for moderating subscription of a closed list.



143
144
145
# File 'lib/ezmlm/list.rb', line 143

def moderators
	return self.read_subscriber_dir( 'mod' )
end

#nameObject

Return the configured name of the list (without the host)



39
40
41
42
# File 'lib/ezmlm/list.rb', line 39

def name
	@name = self.read( 'outlocal' ) unless @name
	return @name
end

#ownerObject

Return the email address of the list’s owner.



63
64
65
66
# File 'lib/ezmlm/list.rb', line 63

def owner
	owner = self.read( 'owner' )
	return owner =~ /@/ ? owner : nil
end

#private=(enable = false) ⇒ Object Also known as: private

Disable or enable remote management requests.



248
249
250
# File 'lib/ezmlm/list.rb', line 248

def private=( enable=false )
	self.public = ! enable
end

#private?Boolean

Returns true if the list is not configured to respond to remote management requests.

Returns:

  • (Boolean)


242
243
244
# File 'lib/ezmlm/list.rb', line 242

def private?
	return ! self.public?
end

#private_archive=(enable = true) ⇒ Object Also known as: private_archive

Disable or enable private access to the archive.



388
389
390
391
392
393
394
# File 'lib/ezmlm/list.rb', line 388

def private_archive=( enable=true )
	if enable
		self.touch( 'modgetonly' )
	else
		self.unlink( 'modgetonly' )
	end
end

#private_archive?Boolean

Returns true if the message archive is accessible only to moderators.

Returns:

  • (Boolean)


382
383
384
# File 'lib/ezmlm/list.rb', line 382

def private_archive?
	return ( self.listdir + 'modgetonly' ).exist?
end

#public=(enable = true) ⇒ Object Also known as: public

Disable or enable remote management requests.



230
231
232
233
234
235
236
# File 'lib/ezmlm/list.rb', line 230

def public=( enable=true )
	if enable
		self.touch( 'public' )
	else
		self.unlink( 'public' )
	end
end

#public?Boolean

Returns true if the list is configured to respond to remote management requests.

Returns:

  • (Boolean)


224
225
226
# File 'lib/ezmlm/list.rb', line 224

def public?
	return ( self.listdir + 'public' ).exist?
end

#public_archive=(enable = true) ⇒ Object Also known as: public_archive

Disable or enable private access to the archive.



405
406
407
# File 'lib/ezmlm/list.rb', line 405

def public_archive=( enable=true )
	self.private_archive = ! enable
end

#public_archive?Boolean

Returns true if the message archive is accessible to anyone.

Returns:

  • (Boolean)


399
400
401
# File 'lib/ezmlm/list.rb', line 399

def public_archive?
	return ! self.private_archive?
end

#remote_subscriptions=(enable = false) ⇒ Object Also known as: remote_subscriptions

Disable or enable remote subscription requests.



263
264
265
266
267
268
269
# File 'lib/ezmlm/list.rb', line 263

def remote_subscriptions=( enable=false )
	if enable
		self.touch( 'remote' )
	else
		self.unlink( 'remote' )
	end
end

#remote_subscriptions?Boolean

Returns true if the list supports remote administration subscribe/unsubscribe requests from moderators.

Returns:

  • (Boolean)


257
258
259
# File 'lib/ezmlm/list.rb', line 257

def remote_subscriptions?
	return ( self.listdir + 'remote' ).exist?
end

#remove_allowed(*addr) ⇒ Object

Remove addr from the allowed list.



216
217
218
# File 'lib/ezmlm/list.rb', line 216

def remove_allowed( *addr )
	return self.unsubscribe( *addr, section: 'allow' )
end

#remove_blacklisted(*addr) ⇒ Object

Remove addr from the blacklist.



187
188
189
# File 'lib/ezmlm/list.rb', line 187

def remove_blacklisted( *addr )
	return self.unsubscribe( *addr, section: 'deny' )
end

#remove_moderator(*addr) ⇒ Object

Remove addr from the list as a Moderator.



161
162
163
# File 'lib/ezmlm/list.rb', line 161

def remove_moderator( *addr )
	return self.unsubscribe( *addr, section: 'mod' )
end

#subscribe(*addr, section: nil) ⇒ Object Also known as: add_subscriber

Subscribe addr to the list within section.



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/ezmlm/list.rb', line 90

def subscribe( *addr, section: nil )
	addr.each do |address|
		next unless address.index( '@' )
		address = address.downcase

		file = self.subscription_dir( section ) + Ezmlm::Hash.subscriber( address )
		self.with_safety do
			if file.exist?
				addresses = file.read.scan( /T([^\0]+)\0/ ).flatten
				addresses << address
				file.open( 'w' ) do |f|
					f.print addresses.uniq.sort.map{|a| "T#{a}\0" }.join
				end

			else
				file.open( 'w' ) do |f|
					f.print "T%s\0" % [ address ]
				end
			end
		end
	end
end

#subscribersObject

Fetch a sorted Array of the email addresses for all of the list’s subscribers.



83
84
85
# File 'lib/ezmlm/list.rb', line 83

def subscribers
	return self.read_subscriber_dir
end

#thread(thread_id) ⇒ Object

Return a Thread object for the given thread_id.



652
653
654
# File 'lib/ezmlm/list.rb', line 652

def thread( thread_id )
	return Ezmlm::List::Thread.new( self, thread_id ) rescue nil
end

#unsubscribe(*addr, section: nil) ⇒ Object Also known as: remove_subscriber

Unsubscribe addr from the list within section.



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/ezmlm/list.rb', line 117

def unsubscribe( *addr, section: nil )
	addr.each do |address|
		address = address.downcase

		file = self.subscription_dir( section ) + Ezmlm::Hash.subscriber( address )
		self.with_safety do
			next unless file.exist?
			addresses = file.read.scan( /T([^\0]+)\0/ ).flatten
			addresses = addresses - [ address ]

			if addresses.empty?
				file.unlink
			else
				file.open( 'w' ) do |f|
					f.print addresses.uniq.sort.map{|a| "T#{a}\0" }.join
				end
			end
		end
	end
end

#user_posts_only=(enable = false) ⇒ Object Also known as: user_posts_only

Disable or enable user only posts. This is easily defeated, moderated lists are preferred.

This has special meaning for moderated lists. Lists act as unmoderated for subscribers, and posts from unknown addresses go to moderation.



346
347
348
349
350
351
352
353
354
# File 'lib/ezmlm/list.rb', line 346

def user_posts_only=( enable=false )
	if enable
		self.touch( 'subpostonly' )
		self.touch( 'noreturnposts' )if self.moderated?
	else
		self.unlink( 'subpostonly' )
		self.unlink( 'noreturnposts' ) if self.moderated?
	end
end

#user_posts_only?Boolean

Returns true if posting is only allowed by subscribers.

Returns:

  • (Boolean)


335
336
337
# File 'lib/ezmlm/list.rb', line 335

def user_posts_only?
	return ( self.listdir + 'subpostonly' ).exist?
end