stackbuilders-campfire_export 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +44 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +160 -0
- data/README.md +114 -0
- data/Rakefile +12 -0
- data/bin/campfire_export +66 -0
- data/campfire_export.gemspec +36 -0
- data/lib/campfire_export/account.rb +32 -0
- data/lib/campfire_export/exception.rb +16 -0
- data/lib/campfire_export/message.rb +95 -0
- data/lib/campfire_export/room.rb +44 -0
- data/lib/campfire_export/timezone.rb +168 -0
- data/lib/campfire_export/transcript.rb +100 -0
- data/lib/campfire_export/upload.rb +78 -0
- data/lib/campfire_export/version.rb +3 -0
- data/lib/campfire_export.rb +133 -0
- data/spec/campfire_export/account_spec.rb +85 -0
- data/spec/campfire_export/message_spec.rb +60 -0
- data/spec/campfire_export/room_spec.rb +27 -0
- data/spec/spec_helper.rb +4 -0
- metadata +174 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
module CampfireExport
|
2
|
+
class Account
|
3
|
+
include CampfireExport::IO
|
4
|
+
include CampfireExport::TimeZone
|
5
|
+
|
6
|
+
@subdomain = ""
|
7
|
+
@api_token = ""
|
8
|
+
@base_url = ""
|
9
|
+
@timezone = nil
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :subdomain, :api_token, :base_url, :timezone
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(subdomain, api_token)
|
16
|
+
Account.subdomain = subdomain
|
17
|
+
Account.api_token = api_token
|
18
|
+
Account.base_url = "https://#{subdomain}.campfirenow.com"
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_timezone
|
22
|
+
settings = Nokogiri::XML get('/account.xml').body
|
23
|
+
selected_zone = settings.xpath('/account/time-zone')
|
24
|
+
Account.timezone = find_tzinfo(selected_zone.text)
|
25
|
+
end
|
26
|
+
|
27
|
+
def rooms
|
28
|
+
doc = Nokogiri::XML get('/rooms.xml').body
|
29
|
+
doc.xpath('/rooms/room').map {|room_xml| Room.new(room_xml) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CampfireExport
|
2
|
+
class Exception < StandardError
|
3
|
+
|
4
|
+
attr_reader :resource, :message, :code
|
5
|
+
|
6
|
+
def initialize(resource, message, code=nil)
|
7
|
+
@resource = resource
|
8
|
+
@message = message
|
9
|
+
@code = code
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
"<#{resource}>: #{message}" + (code ? " (#{code})" : "")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module CampfireExport
|
2
|
+
class Message
|
3
|
+
include CampfireExport::IO
|
4
|
+
attr_accessor :id, :room, :body, :type, :user, :date, :timestamp, :upload
|
5
|
+
|
6
|
+
def initialize(message, room, date)
|
7
|
+
@id = message.xpath('id').text
|
8
|
+
@room = room
|
9
|
+
@date = date
|
10
|
+
@body = message.xpath('body').text
|
11
|
+
@type = message.xpath('type').text
|
12
|
+
|
13
|
+
time = Time.parse message.xpath('created-at').text
|
14
|
+
localtime = CampfireExport::Account.timezone.utc_to_local(time)
|
15
|
+
@timestamp = localtime.strftime '%I:%M %p'
|
16
|
+
|
17
|
+
no_user = ['TimestampMessage', 'SystemMessage', 'AdvertisementMessage']
|
18
|
+
unless no_user.include?(@type)
|
19
|
+
@user = username(message.xpath('user-id').text)
|
20
|
+
end
|
21
|
+
|
22
|
+
@upload = CampfireExport::Upload.new(self) if is_upload?
|
23
|
+
end
|
24
|
+
|
25
|
+
def username(user_id)
|
26
|
+
@@usernames ||= {}
|
27
|
+
@@usernames[user_id] ||= begin
|
28
|
+
doc = Nokogiri::XML get("/users/#{user_id}.xml").body
|
29
|
+
rescue => e
|
30
|
+
"[unknown user]"
|
31
|
+
else
|
32
|
+
# Take the first name and last initial, if there is more than one name.
|
33
|
+
name_parts = doc.xpath('/user/name').text.split
|
34
|
+
if name_parts.length > 1
|
35
|
+
name_parts[-1] = "#{name_parts.last[0,1]}."
|
36
|
+
name_parts.join(" ")
|
37
|
+
else
|
38
|
+
name_parts[0]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def is_upload?
|
44
|
+
@type == 'UploadMessage'
|
45
|
+
end
|
46
|
+
|
47
|
+
def indent(string, count)
|
48
|
+
(' ' * count) + string.gsub(/(\n+)/) { $1 + (' ' * count) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
case type
|
53
|
+
when 'EnterMessage'
|
54
|
+
"[#{user} has entered the room]\n"
|
55
|
+
when 'KickMessage', 'LeaveMessage'
|
56
|
+
"[#{user} has left the room]\n"
|
57
|
+
when 'TextMessage'
|
58
|
+
"[#{user.rjust(12)}:] #{body}\n"
|
59
|
+
when 'UploadMessage'
|
60
|
+
"[#{user} uploaded: #{body}]\n"
|
61
|
+
when 'PasteMessage'
|
62
|
+
"[" + "#{user} pasted:]".rjust(14) + "\n#{indent(body, 16)}\n"
|
63
|
+
when 'TopicChangeMessage'
|
64
|
+
"[#{user} changed the topic to: #{body}]\n"
|
65
|
+
when 'ConferenceCreatedMessage'
|
66
|
+
"[#{user} created conference: #{body}]\n"
|
67
|
+
when 'AllowGuestsMessage'
|
68
|
+
"[#{user} opened the room to guests]\n"
|
69
|
+
when 'DisallowGuestsMessage'
|
70
|
+
"[#{user} closed the room to guests]\n"
|
71
|
+
when 'LockMessage'
|
72
|
+
"[#{user} locked the room]\n"
|
73
|
+
when 'UnlockMessage'
|
74
|
+
"[#{user} unlocked the room]\n"
|
75
|
+
when 'IdleMessage'
|
76
|
+
"[#{user} became idle]\n"
|
77
|
+
when 'UnidleMessage'
|
78
|
+
"[#{user} became active]\n"
|
79
|
+
when 'TweetMessage'
|
80
|
+
"[#{user} tweeted:] #{body}\n"
|
81
|
+
when 'SoundMessage'
|
82
|
+
"[#{user} played a sound:] #{body}\n"
|
83
|
+
when 'TimestampMessage'
|
84
|
+
"--- #{timestamp} ---\n"
|
85
|
+
when 'SystemMessage'
|
86
|
+
""
|
87
|
+
when 'AdvertisementMessage'
|
88
|
+
""
|
89
|
+
else
|
90
|
+
log(:error, "unknown message type: #{type} - '#{body}'")
|
91
|
+
""
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module CampfireExport
|
2
|
+
class Room
|
3
|
+
include CampfireExport::IO
|
4
|
+
attr_accessor :id, :name, :created_at, :last_update
|
5
|
+
|
6
|
+
def initialize(room_xml)
|
7
|
+
@id = room_xml.xpath('id').text
|
8
|
+
@name = room_xml.xpath('name').text
|
9
|
+
created_utc = DateTime.parse(room_xml.xpath('created-at').text)
|
10
|
+
@created_at = Account.timezone.utc_to_local(created_utc)
|
11
|
+
end
|
12
|
+
|
13
|
+
def export(start_date=nil, end_date=nil)
|
14
|
+
# Figure out how to do the least amount of work while still conforming
|
15
|
+
# to the requester's boundary dates.
|
16
|
+
find_last_update
|
17
|
+
start_date.nil? ? date = created_at : date = [start_date, created_at].max
|
18
|
+
end_date.nil? ? end_date = last_update : end_date = [end_date, last_update].min
|
19
|
+
|
20
|
+
while date <= end_date
|
21
|
+
transcript = Transcript.new(self, date)
|
22
|
+
transcript.export
|
23
|
+
|
24
|
+
# Ensure that we stay well below the 37signals API limits.
|
25
|
+
sleep(1.0/10.0)
|
26
|
+
date = date.next
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def find_last_update
|
32
|
+
begin
|
33
|
+
last_message = Nokogiri::XML get("/room/#{id}/recent.xml?limit=1").body
|
34
|
+
update_utc = DateTime.parse(last_message.xpath('/messages/message[1]/created-at').text)
|
35
|
+
@last_update = Account.timezone.utc_to_local(update_utc)
|
36
|
+
rescue => e
|
37
|
+
log(:error,
|
38
|
+
"couldn't get last update in #{room} (defaulting to today)",
|
39
|
+
e)
|
40
|
+
@last_update = Time.now
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# Ruby on Rails is released under the MIT license.
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
require 'tzinfo'
|
6
|
+
|
7
|
+
module CampfireExport
|
8
|
+
# This is a total cut & paste from:
|
9
|
+
# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/values/time_zone.rb
|
10
|
+
# I'm copying it here to avoid bugs in the current active_support gem, to
|
11
|
+
# avoid having a dependency on active_support that might freak out Rails
|
12
|
+
# users, and to avoid fighting with RubyGems about threads and deprecation.
|
13
|
+
# See for background:
|
14
|
+
# https://github.com/rails/rails/pull/1215
|
15
|
+
# http://stackoverflow.com/questions/5176782/uninitialized-constant-activesupportdependenciesmutex-nameerror
|
16
|
+
module TimeZone
|
17
|
+
# Keys are Rails TimeZone names, values are TZInfo identifiers
|
18
|
+
MAPPING = {
|
19
|
+
"International Date Line West" => "Pacific/Midway",
|
20
|
+
"Midway Island" => "Pacific/Midway",
|
21
|
+
"Samoa" => "Pacific/Pago_Pago",
|
22
|
+
"Hawaii" => "Pacific/Honolulu",
|
23
|
+
"Alaska" => "America/Juneau",
|
24
|
+
"Pacific Time (US & Canada)" => "America/Los_Angeles",
|
25
|
+
"Tijuana" => "America/Tijuana",
|
26
|
+
"Mountain Time (US & Canada)" => "America/Denver",
|
27
|
+
"Arizona" => "America/Phoenix",
|
28
|
+
"Chihuahua" => "America/Chihuahua",
|
29
|
+
"Mazatlan" => "America/Mazatlan",
|
30
|
+
"Central Time (US & Canada)" => "America/Chicago",
|
31
|
+
"Saskatchewan" => "America/Regina",
|
32
|
+
"Guadalajara" => "America/Mexico_City",
|
33
|
+
"Mexico City" => "America/Mexico_City",
|
34
|
+
"Monterrey" => "America/Monterrey",
|
35
|
+
"Central America" => "America/Guatemala",
|
36
|
+
"Eastern Time (US & Canada)" => "America/New_York",
|
37
|
+
"Indiana (East)" => "America/Indiana/Indianapolis",
|
38
|
+
"Bogota" => "America/Bogota",
|
39
|
+
"Lima" => "America/Lima",
|
40
|
+
"Quito" => "America/Lima",
|
41
|
+
"Atlantic Time (Canada)" => "America/Halifax",
|
42
|
+
"Caracas" => "America/Caracas",
|
43
|
+
"La Paz" => "America/La_Paz",
|
44
|
+
"Santiago" => "America/Santiago",
|
45
|
+
"Newfoundland" => "America/St_Johns",
|
46
|
+
"Brasilia" => "America/Sao_Paulo",
|
47
|
+
"Buenos Aires" => "America/Argentina/Buenos_Aires",
|
48
|
+
"Georgetown" => "America/Guyana",
|
49
|
+
"Greenland" => "America/Godthab",
|
50
|
+
"Mid-Atlantic" => "Atlantic/South_Georgia",
|
51
|
+
"Azores" => "Atlantic/Azores",
|
52
|
+
"Cape Verde Is." => "Atlantic/Cape_Verde",
|
53
|
+
"Dublin" => "Europe/Dublin",
|
54
|
+
"Edinburgh" => "Europe/London",
|
55
|
+
"Lisbon" => "Europe/Lisbon",
|
56
|
+
"London" => "Europe/London",
|
57
|
+
"Casablanca" => "Africa/Casablanca",
|
58
|
+
"Monrovia" => "Africa/Monrovia",
|
59
|
+
"UTC" => "Etc/UTC",
|
60
|
+
"Belgrade" => "Europe/Belgrade",
|
61
|
+
"Bratislava" => "Europe/Bratislava",
|
62
|
+
"Budapest" => "Europe/Budapest",
|
63
|
+
"Ljubljana" => "Europe/Ljubljana",
|
64
|
+
"Prague" => "Europe/Prague",
|
65
|
+
"Sarajevo" => "Europe/Sarajevo",
|
66
|
+
"Skopje" => "Europe/Skopje",
|
67
|
+
"Warsaw" => "Europe/Warsaw",
|
68
|
+
"Zagreb" => "Europe/Zagreb",
|
69
|
+
"Brussels" => "Europe/Brussels",
|
70
|
+
"Copenhagen" => "Europe/Copenhagen",
|
71
|
+
"Madrid" => "Europe/Madrid",
|
72
|
+
"Paris" => "Europe/Paris",
|
73
|
+
"Amsterdam" => "Europe/Amsterdam",
|
74
|
+
"Berlin" => "Europe/Berlin",
|
75
|
+
"Bern" => "Europe/Berlin",
|
76
|
+
"Rome" => "Europe/Rome",
|
77
|
+
"Stockholm" => "Europe/Stockholm",
|
78
|
+
"Vienna" => "Europe/Vienna",
|
79
|
+
"West Central Africa" => "Africa/Algiers",
|
80
|
+
"Bucharest" => "Europe/Bucharest",
|
81
|
+
"Cairo" => "Africa/Cairo",
|
82
|
+
"Helsinki" => "Europe/Helsinki",
|
83
|
+
"Kyiv" => "Europe/Kiev",
|
84
|
+
"Riga" => "Europe/Riga",
|
85
|
+
"Sofia" => "Europe/Sofia",
|
86
|
+
"Tallinn" => "Europe/Tallinn",
|
87
|
+
"Vilnius" => "Europe/Vilnius",
|
88
|
+
"Athens" => "Europe/Athens",
|
89
|
+
"Istanbul" => "Europe/Istanbul",
|
90
|
+
"Minsk" => "Europe/Minsk",
|
91
|
+
"Jerusalem" => "Asia/Jerusalem",
|
92
|
+
"Harare" => "Africa/Harare",
|
93
|
+
"Pretoria" => "Africa/Johannesburg",
|
94
|
+
"Moscow" => "Europe/Moscow",
|
95
|
+
"St. Petersburg" => "Europe/Moscow",
|
96
|
+
"Volgograd" => "Europe/Moscow",
|
97
|
+
"Kuwait" => "Asia/Kuwait",
|
98
|
+
"Riyadh" => "Asia/Riyadh",
|
99
|
+
"Nairobi" => "Africa/Nairobi",
|
100
|
+
"Baghdad" => "Asia/Baghdad",
|
101
|
+
"Tehran" => "Asia/Tehran",
|
102
|
+
"Abu Dhabi" => "Asia/Muscat",
|
103
|
+
"Muscat" => "Asia/Muscat",
|
104
|
+
"Baku" => "Asia/Baku",
|
105
|
+
"Tbilisi" => "Asia/Tbilisi",
|
106
|
+
"Yerevan" => "Asia/Yerevan",
|
107
|
+
"Kabul" => "Asia/Kabul",
|
108
|
+
"Ekaterinburg" => "Asia/Yekaterinburg",
|
109
|
+
"Islamabad" => "Asia/Karachi",
|
110
|
+
"Karachi" => "Asia/Karachi",
|
111
|
+
"Tashkent" => "Asia/Tashkent",
|
112
|
+
"Chennai" => "Asia/Kolkata",
|
113
|
+
"Kolkata" => "Asia/Kolkata",
|
114
|
+
"Mumbai" => "Asia/Kolkata",
|
115
|
+
"New Delhi" => "Asia/Kolkata",
|
116
|
+
"Kathmandu" => "Asia/Kathmandu",
|
117
|
+
"Astana" => "Asia/Dhaka",
|
118
|
+
"Dhaka" => "Asia/Dhaka",
|
119
|
+
"Sri Jayawardenepura" => "Asia/Colombo",
|
120
|
+
"Almaty" => "Asia/Almaty",
|
121
|
+
"Novosibirsk" => "Asia/Novosibirsk",
|
122
|
+
"Rangoon" => "Asia/Rangoon",
|
123
|
+
"Bangkok" => "Asia/Bangkok",
|
124
|
+
"Hanoi" => "Asia/Bangkok",
|
125
|
+
"Jakarta" => "Asia/Jakarta",
|
126
|
+
"Krasnoyarsk" => "Asia/Krasnoyarsk",
|
127
|
+
"Beijing" => "Asia/Shanghai",
|
128
|
+
"Chongqing" => "Asia/Chongqing",
|
129
|
+
"Hong Kong" => "Asia/Hong_Kong",
|
130
|
+
"Urumqi" => "Asia/Urumqi",
|
131
|
+
"Kuala Lumpur" => "Asia/Kuala_Lumpur",
|
132
|
+
"Singapore" => "Asia/Singapore",
|
133
|
+
"Taipei" => "Asia/Taipei",
|
134
|
+
"Perth" => "Australia/Perth",
|
135
|
+
"Irkutsk" => "Asia/Irkutsk",
|
136
|
+
"Ulaan Bataar" => "Asia/Ulaanbaatar",
|
137
|
+
"Seoul" => "Asia/Seoul",
|
138
|
+
"Osaka" => "Asia/Tokyo",
|
139
|
+
"Sapporo" => "Asia/Tokyo",
|
140
|
+
"Tokyo" => "Asia/Tokyo",
|
141
|
+
"Yakutsk" => "Asia/Yakutsk",
|
142
|
+
"Darwin" => "Australia/Darwin",
|
143
|
+
"Adelaide" => "Australia/Adelaide",
|
144
|
+
"Canberra" => "Australia/Melbourne",
|
145
|
+
"Melbourne" => "Australia/Melbourne",
|
146
|
+
"Sydney" => "Australia/Sydney",
|
147
|
+
"Brisbane" => "Australia/Brisbane",
|
148
|
+
"Hobart" => "Australia/Hobart",
|
149
|
+
"Vladivostok" => "Asia/Vladivostok",
|
150
|
+
"Guam" => "Pacific/Guam",
|
151
|
+
"Port Moresby" => "Pacific/Port_Moresby",
|
152
|
+
"Magadan" => "Asia/Magadan",
|
153
|
+
"Solomon Is." => "Asia/Magadan",
|
154
|
+
"New Caledonia" => "Pacific/Noumea",
|
155
|
+
"Fiji" => "Pacific/Fiji",
|
156
|
+
"Kamchatka" => "Asia/Kamchatka",
|
157
|
+
"Marshall Is." => "Pacific/Majuro",
|
158
|
+
"Auckland" => "Pacific/Auckland",
|
159
|
+
"Wellington" => "Pacific/Auckland",
|
160
|
+
"Nuku'alofa" => "Pacific/Tongatapu"
|
161
|
+
}.each { |name, zone| name.freeze; zone.freeze }
|
162
|
+
MAPPING.freeze
|
163
|
+
|
164
|
+
def find_tzinfo(name)
|
165
|
+
TZInfo::Timezone.get(MAPPING[name] || name)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module CampfireExport
|
2
|
+
class Transcript
|
3
|
+
include CampfireExport::IO
|
4
|
+
attr_accessor :room, :date, :xml, :messages
|
5
|
+
|
6
|
+
def initialize(room, date)
|
7
|
+
@room = room
|
8
|
+
@date = date
|
9
|
+
end
|
10
|
+
|
11
|
+
def transcript_path
|
12
|
+
"/room/#{room.id}/transcript/#{date.year}/#{date.mon}/#{date.mday}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def export
|
16
|
+
begin
|
17
|
+
log(:info, "#{export_dir} ... ")
|
18
|
+
@xml = Nokogiri::XML get("#{transcript_path}.xml").body
|
19
|
+
rescue => e
|
20
|
+
log(:error, "transcript export for #{export_dir} failed", e)
|
21
|
+
else
|
22
|
+
@messages = xml.xpath('/messages/message').map do |message|
|
23
|
+
CampfireExport::Message.new(message, room, date)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Only export transcripts that contain at least one message.
|
27
|
+
if messages.length > 0
|
28
|
+
log(:info, "exporting transcripts\n")
|
29
|
+
begin
|
30
|
+
FileUtils.mkdir_p export_dir
|
31
|
+
rescue => e
|
32
|
+
log(:error, "Unable to create #{export_dir}", e)
|
33
|
+
else
|
34
|
+
export_xml
|
35
|
+
export_plaintext
|
36
|
+
export_html
|
37
|
+
export_uploads
|
38
|
+
end
|
39
|
+
else
|
40
|
+
log(:info, "no messages\n")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def export_xml
|
46
|
+
begin
|
47
|
+
export_file(xml, 'transcript.xml')
|
48
|
+
verify_export('transcript.xml', xml.to_s.bytesize)
|
49
|
+
rescue => e
|
50
|
+
log(:error, "XML transcript export for #{export_dir} failed", e)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def export_plaintext
|
55
|
+
begin
|
56
|
+
date_header = date.strftime('%A, %B %e, %Y').squeeze(" ")
|
57
|
+
plaintext = "#{CampfireExport::Account.subdomain.upcase} CAMPFIRE\n"
|
58
|
+
plaintext << "#{room.name}: #{date_header}\n\n"
|
59
|
+
messages.each {|message| plaintext << message.to_s }
|
60
|
+
export_file(plaintext, 'transcript.txt')
|
61
|
+
verify_export('transcript.txt', plaintext.bytesize)
|
62
|
+
rescue => e
|
63
|
+
log(:error, "Plaintext transcript export for #{export_dir} failed", e)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def export_html
|
68
|
+
begin
|
69
|
+
transcript_html = get(transcript_path).to_s
|
70
|
+
|
71
|
+
# Make the upload links in the transcript clickable from the exported
|
72
|
+
# directory layout.
|
73
|
+
transcript_html.gsub!(%Q{href="/room/#{room.id}/uploads/},
|
74
|
+
%Q{href="uploads/})
|
75
|
+
# Likewise, make the image thumbnails embeddable from the exported
|
76
|
+
# directory layout.
|
77
|
+
transcript_html.gsub!(%Q{src="/room/#{room.id}/thumb/},
|
78
|
+
%Q{src="thumbs/})
|
79
|
+
|
80
|
+
export_file(transcript_html, 'transcript.html')
|
81
|
+
verify_export('transcript.html', transcript_html.bytesize)
|
82
|
+
rescue => e
|
83
|
+
log(:error, "HTML transcript export for #{export_dir} failed", e)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def export_uploads
|
88
|
+
messages.each do |message|
|
89
|
+
if message.is_upload?
|
90
|
+
begin
|
91
|
+
message.upload.export
|
92
|
+
rescue => e
|
93
|
+
path = "#{message.upload.export_dir}/#{message.upload.filename}"
|
94
|
+
log(:error, "Upload export for #{path} failed", e)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|