/[svn]/linuxsampler/trunk/src/network/lscpserver.cpp
ViewVC logotype

Diff of /linuxsampler/trunk/src/network/lscpserver.cpp

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 35 by schoenebeck, Fri Mar 5 13:46:15 2004 UTC revision 121 by senkov, Sat Jun 12 07:46:02 2004 UTC
# Line 2  Line 2 
2   *                                                                         *   *                                                                         *
3   *   LinuxSampler - modular, streaming capable sampler                     *   *   LinuxSampler - modular, streaming capable sampler                     *
4   *                                                                         *   *                                                                         *
5   *   Copyright (C) 2003 by Benno Senoner and Christian Schoenebeck         *   *   Copyright (C) 2003, 2004 by Benno Senoner and Christian Schoenebeck   *
6   *                                                                         *   *                                                                         *
7   *   This program is free software; you can redistribute it and/or modify  *   *   This program is free software; you can redistribute it and/or modify  *
8   *   it under the terms of the GNU General Public License as published by  *   *   it under the terms of the GNU General Public License as published by  *
# Line 21  Line 21 
21   ***************************************************************************/   ***************************************************************************/
22    
23  #include "lscpserver.h"  #include "lscpserver.h"
24    #include "lscpresultset.h"
25    
26  LSCPServer::LSCPServer(AudioThread* pEngine) : Thread(false, 0, -4) {  #include "../engines/gig/Engine.h"
27      this->pEngine = pEngine;  
28    LSCPServer::LSCPServer(Sampler* pSampler) : Thread(false, 0, -4) {
29        this->pSampler = pSampler;
30  }  }
31    
32  int LSCPServer::Main() {  int LSCPServer::Main() {
33      hSocket = socket(AF_INET, SOCK_STREAM, 0);      hSocket = socket(AF_INET, SOCK_STREAM, 0);
34      if (hSocket < 0) {      if (hSocket < 0) {
35          std::cerr << "LSCPServer: Could not create server socket." << std::endl;          std::cerr << "LSCPServer: Could not create server socket." << std::endl;
36          return -1;          //return -1;
37            exit(EXIT_FAILURE);
38      }      }
39    
40      SocketAddress.sin_family      = AF_INET;      SocketAddress.sin_family      = AF_INET;
# Line 40  int LSCPServer::Main() { Line 44  int LSCPServer::Main() {
44      if (bind(hSocket, (sockaddr*) &SocketAddress, sizeof(sockaddr_in)) < 0) {      if (bind(hSocket, (sockaddr*) &SocketAddress, sizeof(sockaddr_in)) < 0) {
45          std::cerr << "LSCPServer: Could not bind server socket." << std::endl;          std::cerr << "LSCPServer: Could not bind server socket." << std::endl;
46          close(hSocket);          close(hSocket);
47          return -1;          //return -1;
48            exit(EXIT_FAILURE);
49      }      }
50    
51      listen(hSocket, 1);      listen(hSocket, 1);
# Line 54  int LSCPServer::Main() { Line 59  int LSCPServer::Main() {
59          if (hSession < 0) {          if (hSession < 0) {
60              std::cerr << "LSCPServer: Client connection failed." << std::endl;              std::cerr << "LSCPServer: Client connection failed." << std::endl;
61              close(hSocket);              close(hSocket);
62              return -1;              //return -1;
63                exit(EXIT_FAILURE);
64          }          }
65    
66          dmsg(1,("LSCPServer: Client connection established.\n"));          dmsg(1,("LSCPServer: Client connection established.\n"));
# Line 86  void LSCPServer::AnswerClient(String Ret Line 92  void LSCPServer::AnswerClient(String Ret
92  /**  /**
93   * Will be called by the parser to load an instrument.   * Will be called by the parser to load an instrument.
94   */   */
95  String LSCPServer::LoadInstrument(String Filename, uint Instrument, uint SamplerChannel) {  String LSCPServer::LoadInstrument(String Filename, uint uiInstrument, uint uiSamplerChannel) {
96      dmsg(2,("LSCPServer: LoadInstrument(Filename=%s,Instrument=%d,SamplerChannel=%d)\n", Filename.c_str(), Instrument, SamplerChannel));      dmsg(2,("LSCPServer: LoadInstrument(Filename=%s,Instrument=%d,SamplerChannel=%d)\n", Filename.c_str(), uiInstrument, uiSamplerChannel));
97      result_t res = pEngine->LoadInstrument(Filename.c_str(), Instrument);      LSCPResultSet result;
98      return ConvertResult(res);      try {
99            SamplerChannel* pSamplerChannel = pSampler->GetSamplerChannel(uiSamplerChannel);
100            if (!pSamplerChannel) throw LinuxSamplerException("Index out of bounds");
101            Engine* pEngine = pSamplerChannel->GetEngine();
102            if (!pEngine) throw LinuxSamplerException("No engine loaded on channel");
103            pEngine->LoadInstrument(Filename.c_str(), uiInstrument);
104        }
105        catch (LinuxSamplerException e) {
106             result.Error(e);
107        }
108        return result.Produce();
109  }  }
110    
111  /**  /**
112   * Will be called by the parser to load and deploy an engine.   * Will be called by the parser to load and deploy an engine.
113   */   */
114  String LSCPServer::LoadEngine(String EngineName, uint SamplerChannel) {  String LSCPServer::LoadEngine(String EngineName, uint uiSamplerChannel) {
115      dmsg(2,("LSCPServer: LoadEngine(EngineName=%s,SamplerChannel=%d)\n", EngineName.c_str(), SamplerChannel));      dmsg(2,("LSCPServer: LoadEngine(EngineName=%s,SamplerChannel=%d)\n", EngineName.c_str(), uiSamplerChannel));
116      return "ERR:0:Not implemented yet.\r\n";      LSCPResultSet result;
117        try {
118            Engine::type_t type;
119            if ((EngineName == "GigEngine") || (EngineName == "gig")) type = Engine::type_gig;
120            else throw LinuxSamplerException("Unknown engine type");
121            SamplerChannel* pSamplerChannel = pSampler->GetSamplerChannel(uiSamplerChannel);
122            if (!pSamplerChannel) throw LinuxSamplerException("Index out of bounds");
123            pSamplerChannel->LoadEngine(type);
124        }
125        catch (LinuxSamplerException e) {
126             result.Error(e);
127        }
128        return result.Produce();
129  }  }
130    
131  /**  /**
# Line 105  String LSCPServer::LoadEngine(String Eng Line 133  String LSCPServer::LoadEngine(String Eng
133   */   */
134  String LSCPServer::GetChannels() {  String LSCPServer::GetChannels() {
135      dmsg(2,("LSCPServer: GetChannels()\n"));      dmsg(2,("LSCPServer: GetChannels()\n"));
136      return "1\r\n";      LSCPResultSet result;
137        result.Add(pSampler->SamplerChannels());
138        return result.Produce();
139  }  }
140    
141  /**  /**
# Line 113  String LSCPServer::GetChannels() { Line 143  String LSCPServer::GetChannels() {
143   */   */
144  String LSCPServer::AddChannel() {  String LSCPServer::AddChannel() {
145      dmsg(2,("LSCPServer: AddChannel()\n"));      dmsg(2,("LSCPServer: AddChannel()\n"));
146      return "ERR:0:Not implemented yet.\r\n";      SamplerChannel* pSamplerChannel = pSampler->AddSamplerChannel();
147        LSCPResultSet result(pSamplerChannel->Index());
148        return result.Produce();
149  }  }
150    
151  /**  /**
152   * Will be called by the parser to remove a sampler channel.   * Will be called by the parser to remove a sampler channel.
153   */   */
154  String LSCPServer::RemoveChannel(uint SamplerChannel) {  String LSCPServer::RemoveChannel(uint uiSamplerChannel) {
155      dmsg(2,("LSCPServer: RemoveChannel(SamplerChannel=%d)\n", SamplerChannel));      dmsg(2,("LSCPServer: RemoveChannel(SamplerChannel=%d)\n", uiSamplerChannel));
156      return "ERR:0:Not implemented yet.\r\n";      LSCPResultSet result;
157        pSampler->RemoveSamplerChannel(uiSamplerChannel);
158        return result.Produce();
159  }  }
160    
161  /**  /**
# Line 129  String LSCPServer::RemoveChannel(uint Sa Line 163  String LSCPServer::RemoveChannel(uint Sa
163   */   */
164  String LSCPServer::GetAvailableEngines() {  String LSCPServer::GetAvailableEngines() {
165      dmsg(2,("LSCPServer: GetAvailableEngines()\n"));      dmsg(2,("LSCPServer: GetAvailableEngines()\n"));
166      return "ERR:0:Not implemented yet.\r\n";      LSCPResultSet result("GigEngine");
167        return result.Produce();
168  }  }
169    
170  /**  /**
# Line 137  String LSCPServer::GetAvailableEngines() Line 172  String LSCPServer::GetAvailableEngines()
172   */   */
173  String LSCPServer::GetEngineInfo(String EngineName) {  String LSCPServer::GetEngineInfo(String EngineName) {
174      dmsg(2,("LSCPServer: GetEngineInfo(EngineName=%s)\n", EngineName.c_str()));      dmsg(2,("LSCPServer: GetEngineInfo(EngineName=%s)\n", EngineName.c_str()));
175      return "ERR:0:Not implemented yet.\r\n";      LSCPResultSet result;
176        try {
177            if ((EngineName == "GigEngine") || (EngineName == "gig")) {
178                Engine* pEngine = new LinuxSampler::gig::Engine;
179                result.Add(pEngine->Description());
180                delete pEngine;
181            }
182            else throw LinuxSamplerException("Unknown engine type");
183        }
184        catch (LinuxSamplerException e) {
185             result.Error(e);
186        }
187        return result.Produce();
188  }  }
189    
190  /**  /**
191   * Will be called by the parser to get informations about a particular   * Will be called by the parser to get informations about a particular
192   * sampler channel.   * sampler channel.
193   */   */
194  String LSCPServer::GetChannelInfo(uint SamplerChannel) {  String LSCPServer::GetChannelInfo(uint uiSamplerChannel) {
195      dmsg(2,("LSCPServer: GetChannelInfo(SamplerChannel=%d)\n", SamplerChannel));      dmsg(2,("LSCPServer: GetChannelInfo(SamplerChannel=%d)\n", uiSamplerChannel));
196      return "ERR:0:Not implemented yet.\r\n";      LSCPResultSet result;
197        try {
198            SamplerChannel* pSamplerChannel = pSampler->GetSamplerChannel(uiSamplerChannel);
199            if (!pSamplerChannel) throw LinuxSamplerException("Index out of bounds");
200            Engine* pEngine = pSamplerChannel->GetEngine();
201            
202            //Defaults values
203            String EngineName = "NONE";
204            float Volume = 0;
205            String InstrumentFileName = "NONE";
206            int InstrumentIndex = 0;
207            
208            if (pEngine) {
209                EngineName =  pEngine->EngineName();
210                Volume = pEngine->Volume();
211                int iIdx = pEngine->InstrumentIndex();
212                if (iIdx != -1) {
213                    InstrumentFileName = pEngine->InstrumentFileName();
214                    InstrumentIndex = iIdx;
215                }
216            }
217    
218            result.Add("ENGINE_NAME", EngineName);
219            result.Add("VOLUME", Volume);
220    
221            //Some hardcoded stuff for now to make GUI look good
222            result.Add("AUDIO_OUTPUT_DEVICE", "0");
223            result.Add("AUDIO_OUTPUT_CHANNELS", "2");
224            result.Add("AUDIO_OUTPUT_ROUTING", "0,1");
225    
226            result.Add("INSTRUMENT_FILE", InstrumentFileName);
227            result.Add("INSTRUMENT_NR", InstrumentIndex);
228            
229            //Some more hardcoded stuff for now to make GUI look good
230            result.Add("MIDI_INPUT_DEVICE", "0");
231            result.Add("MIDI_INPUT_PORT", "0");
232            result.Add("MIDI_INPUT_CHANNEL", "1");
233        }
234        catch (LinuxSamplerException e) {
235             result.Error(e);
236        }
237        return result.Produce();
238  }  }
239    
240  /**  /**
241   * Will be called by the parser to get the amount of active voices on a   * Will be called by the parser to get the amount of active voices on a
242   * particular sampler channel.   * particular sampler channel.
243   */   */
244  String LSCPServer::GetVoiceCount(uint SamplerChannel) {  String LSCPServer::GetVoiceCount(uint uiSamplerChannel) {
245      dmsg(2,("LSCPServer: GetVoiceCount(SamplerChannel=%d)\n", SamplerChannel));      dmsg(2,("LSCPServer: GetVoiceCount(SamplerChannel=%d)\n", uiSamplerChannel));
246      return ToString(pEngine->ActiveVoiceCount) + "\r\n";      LSCPResultSet result;
247        try {
248            SamplerChannel* pSamplerChannel = pSampler->GetSamplerChannel(uiSamplerChannel);
249            if (!pSamplerChannel) throw LinuxSamplerException("Index out of bounds");
250            Engine* pEngine = pSamplerChannel->GetEngine();
251            if (!pEngine) throw LinuxSamplerException("No engine loaded on channel");
252            result.Add(pEngine->VoiceCount());
253        }
254        catch (LinuxSamplerException e) {
255             result.Error(e);
256        }
257        return result.Produce();
258  }  }
259    
260  /**  /**
261   * Will be called by the parser to get the amount of active disk streams on a   * Will be called by the parser to get the amount of active disk streams on a
262   * particular sampler channel.   * particular sampler channel.
263   */   */
264  String LSCPServer::GetStreamCount(uint SamplerChannel) {  String LSCPServer::GetStreamCount(uint uiSamplerChannel) {
265      dmsg(2,("LSCPServer: GetStreamCount(SamplerChannel=%d)\n", SamplerChannel));      dmsg(2,("LSCPServer: GetStreamCount(SamplerChannel=%d)\n", uiSamplerChannel));
266      return ToString(pEngine->pDiskThread->ActiveStreamCount) + "\r\n";      LSCPResultSet result;
267        try {
268            SamplerChannel* pSamplerChannel = pSampler->GetSamplerChannel(uiSamplerChannel);
269            if (!pSamplerChannel) throw LinuxSamplerException("Index out of bounds");
270            Engine* pEngine = pSamplerChannel->GetEngine();
271            if (!pEngine) throw LinuxSamplerException("No engine loaded on channel");
272            result.Add(pEngine->DiskStreamCount());
273        }
274        catch (LinuxSamplerException e) {
275             result.Error(e);
276        }
277        return result.Produce();
278  }  }
279    
280  /**  /**
281   * Will be called by the parser to get the buffer fill states of all disk   * Will be called by the parser to get the buffer fill states of all disk
282   * streams on a particular sampler channel.   * streams on a particular sampler channel.
283   */   */
284  String LSCPServer::GetBufferFill(fill_response_t ResponseType, uint SamplerChannel) {  String LSCPServer::GetBufferFill(fill_response_t ResponseType, uint uiSamplerChannel) {
285      dmsg(2,("LSCPServer: GetBufferFill(ResponseType=%d, SamplerChannel=%d)\n", ResponseType, SamplerChannel));      dmsg(2,("LSCPServer: GetBufferFill(ResponseType=%d, SamplerChannel=%d)\n", ResponseType, uiSamplerChannel));
286      return (ResponseType == fill_response_bytes) ? pEngine->pDiskThread->GetBufferFillBytes() + "\r\n"      LSCPResultSet result;
287                                                   : pEngine->pDiskThread->GetBufferFillPercentage() + "\r\n";      try {
288            SamplerChannel* pSamplerChannel = pSampler->GetSamplerChannel(uiSamplerChannel);
289            if (!pSamplerChannel) throw LinuxSamplerException("Index out of bounds");
290            Engine* pEngine = pSamplerChannel->GetEngine();
291            if (!pEngine) throw LinuxSamplerException("No engine loaded on channel");
292            if (!pEngine->DiskStreamSupported()) return "NA\r\n"; //FIXME: Update resultset class to support "NA"
293            switch (ResponseType) {
294                case fill_response_bytes:
295                    result.Add(pEngine->DiskStreamBufferFillBytes());
296                    break;
297                case fill_response_percentage:
298                    result.Add(pEngine->DiskStreamBufferFillPercentage());
299                    break;
300                default:
301                    throw LinuxSamplerException("Unknown fill response type");
302            }
303        }
304        catch (LinuxSamplerException e) {
305             result.Error(e);
306        }
307        return result.Produce();
308  }  }
309    
310  /**  /**
311   * Will be called by the parser to change the audio output type on a   * Will be called by the parser to change the audio output type on a
312   * particular sampler channel.   * particular sampler channel.
313   */   */
314  String LSCPServer::SetAudioOutputType(audio_output_type_t AudioOutputType, uint SamplerChannel) {  String LSCPServer::SetAudioOutputType(AudioOutputDevice::type_t AudioOutputType, uint uiSamplerChannel) {
315      dmsg(2,("LSCPServer: SetAudioOutputType(AudioOutputType=%d, SamplerChannel=%d)\n", AudioOutputType, SamplerChannel));      dmsg(2,("LSCPServer: SetAudioOutputType(AudioOutputType=%d, SamplerChannel=%d)\n", AudioOutputType, uiSamplerChannel));
316      return "ERR:0:Not implemented yet.\r\n";      LSCPResultSet result;
317        try {
318            SamplerChannel* pSamplerChannel = pSampler->GetSamplerChannel(uiSamplerChannel);
319            if (!pSamplerChannel) throw LinuxSamplerException("Index out of bounds");
320            pSamplerChannel->SetAudioOutputDevice(AudioOutputType);
321        }
322        catch (LinuxSamplerException e) {
323             result.Error(e);
324        }
325        return result.Produce();
326  }  }
327    
328  /**  /**
329   * Will be called by the parser to change the audio output channel for   * Will be called by the parser to change the audio output channel for
330   * playback on a particular sampler channel.   * playback on a particular sampler channel.
331   */   */
332  String LSCPServer::SetAudioOutputChannel(uint AudioOutputChannel, uint SamplerChannel) {  String LSCPServer::SetAudioOutputChannel(uint AudioOutputChannel, uint uiSamplerChannel) {
333      dmsg(2,("LSCPServer: SetAudioOutputChannel(AudioOutputChannel=%d, SamplerChannel=%d)\n", AudioOutputChannel, SamplerChannel));      dmsg(2,("LSCPServer: SetAudioOutputChannel(AudioOutputChannel=%d, SamplerChannel=%d)\n", AudioOutputChannel, uiSamplerChannel));
334      return "ERR:0:Not implemented yet.\r\n";      return "ERR:0:Not implemented yet.\r\n"; //FIXME: Add support for this in resultset class?
335    }
336    
337    String LSCPServer::SetMIDIInputType(MidiInputDevice::type_t MidiInputType, uint uiSamplerChannel) {
338        dmsg(2,("LSCPServer: SetMIDIInputType(MidiInputType=%d, SamplerChannel=%d)\n", MidiInputType, uiSamplerChannel));
339        LSCPResultSet result;
340        try {
341            SamplerChannel* pSamplerChannel = pSampler->GetSamplerChannel(uiSamplerChannel);
342            if (!pSamplerChannel) throw LinuxSamplerException("Index out of bounds");
343            pSamplerChannel->SetMidiInputDevice(MidiInputType);
344        }
345        catch (LinuxSamplerException e) {
346             result.Error(e);
347        }
348        return result.Produce();
349  }  }
350    
351  /**  /**
352   * Will be called by the parser to change the MIDI input port on which the   * Will be called by the parser to change the MIDI input port on which the
353   * engine of a particular sampler channel should listen to.   * engine of a particular sampler channel should listen to.
354   */   */
355  String LSCPServer::SetMIDIInputPort(String MIDIInputPort, uint Samplerchannel) {  String LSCPServer::SetMIDIInputPort(String MIDIInputPort, uint uiSamplerChannel) {
356      dmsg(2,("LSCPServer: SetMIDIInputPort(MIDIInputPort=%s, Samplerchannel=%d)\n", MIDIInputPort.c_str(), Samplerchannel));      dmsg(2,("LSCPServer: SetMIDIInputPort(MIDIInputPort=%s, Samplerchannel=%d)\n", MIDIInputPort.c_str(), uiSamplerChannel));
357      return "ERR:0:Not implemented yet.\r\n";      LSCPResultSet result;
358        try {
359            SamplerChannel* pSamplerChannel = pSampler->GetSamplerChannel(uiSamplerChannel);
360            if (!pSamplerChannel) throw LinuxSamplerException("Index out of bounds");
361            if (!pSamplerChannel->GetMidiInputDevice()) throw LinuxSamplerException("No MIDI input device connected yet");
362            pSamplerChannel->GetMidiInputDevice()->SetInputPort(MIDIInputPort.c_str());
363        }
364        catch (LinuxSamplerException e) {
365             result.Error(e);
366        }
367        return result.Produce();
368  }  }
369    
370  /**  /**
371   * Will be called by the parser to change the MIDI input channel on which the   * Will be called by the parser to change the MIDI input channel on which the
372   * engine of a particular sampler channel should listen to.   * engine of a particular sampler channel should listen to.
373   */   */
374  String LSCPServer::SetMIDIInputChannel(uint MIDIChannel, uint SamplerChannel) {  String LSCPServer::SetMIDIInputChannel(uint MIDIChannel, uint uiSamplerChannel) {
375      dmsg(2,("LSCPServer: SetMIDIInputChannel(MIDIChannel=%d, SamplerChannel=%d)\n", MIDIChannel, SamplerChannel));      dmsg(2,("LSCPServer: SetMIDIInputChannel(MIDIChannel=%d, SamplerChannel=%d)\n", MIDIChannel, uiSamplerChannel));
376      return "ERR:0:Not implemented yet.\r\n";      LSCPResultSet result;
377        try {
378            SamplerChannel* pSamplerChannel = pSampler->GetSamplerChannel(uiSamplerChannel);
379            if (!pSamplerChannel) throw LinuxSamplerException("Index out of bounds");
380            if (!pSamplerChannel->GetMidiInputDevice()) throw LinuxSamplerException("No MIDI input device connected yet");
381            MidiInputDevice::type_t oldtype = pSamplerChannel->GetMidiInputDevice()->Type();
382            pSamplerChannel->SetMidiInputDevice(oldtype, (MidiInputDevice::midi_chan_t) MIDIChannel);
383        }
384        catch (LinuxSamplerException e) {
385             result.Error(e);
386        }
387        return result.Produce();
388  }  }
389    
390  /**  /**
391   * Will be called by the parser to change the global volume factor on a   * Will be called by the parser to change the global volume factor on a
392   * particular sampler channel.   * particular sampler channel.
393   */   */
394  String LSCPServer::SetVolume(double Volume, uint SamplerChannel) {  String LSCPServer::SetVolume(double Volume, uint uiSamplerChannel) {
395      dmsg(2,("LSCPServer: SetVolume(Volume=%f, SamplerChannel=%d)\n", Volume, SamplerChannel));      dmsg(2,("LSCPServer: SetVolume(Volume=%f, SamplerChannel=%d)\n", Volume, uiSamplerChannel));
396      pEngine->Volume = Volume;      LSCPResultSet result;
397      return "OK\r\n";      try {
398            SamplerChannel* pSamplerChannel = pSampler->GetSamplerChannel(uiSamplerChannel);
399            if (!pSamplerChannel) throw LinuxSamplerException("Index out of bounds");
400            Engine* pEngine = pSamplerChannel->GetEngine();
401            if (!pEngine) throw LinuxSamplerException("No engine loaded on channel");
402            pEngine->Volume(Volume);
403        }
404        catch (LinuxSamplerException e) {
405             result.Error(e);
406        }
407        return result.Produce();
408  }  }
409    
410  /**  /**
411   * Will be called by the parser to reset a particular sampler channel.   * Will be called by the parser to reset a particular sampler channel.
412   */   */
413  String LSCPServer::ResetChannel(uint SamplerChannel) {  String LSCPServer::ResetChannel(uint uiSamplerChannel) {
414      dmsg(2,("LSCPServer: ResetChannel(SamplerChannel=%d)\n", SamplerChannel));      dmsg(2,("LSCPServer: ResetChannel(SamplerChannel=%d)\n", uiSamplerChannel));
415      pEngine->Reset();      LSCPResultSet result;
416      return "OK\r\n";      try {
417            SamplerChannel* pSamplerChannel = pSampler->GetSamplerChannel(uiSamplerChannel);
418            if (!pSamplerChannel) throw LinuxSamplerException("Index out of bounds");
419            Engine* pEngine = pSamplerChannel->GetEngine();
420            if (!pEngine) throw LinuxSamplerException("No engine loaded on channel");
421            pEngine->Reset();
422        }
423        catch (LinuxSamplerException e) {
424             result.Error(e);
425        }
426        return result.Produce();
427  }  }
428    
429  /**  /**

Legend:
Removed from v.35  
changed lines
  Added in v.121

  ViewVC Help
Powered by ViewVC